Compare commits

..

32 Commits

Author SHA1 Message Date
Paddy Xu
ce8cfacee1 we need to set the Source anyway 2017-06-02 20:12:19 +03:00
Paddy Xu
00c98acee6 Disable js alerts in WebBrowser 2017-06-02 19:37:57 +03:00
Paddy Xu
20fbda1e44 Go back from Webkit to IE render engine 2017-06-02 19:11:15 +03:00
Paddy Xu
69cad9c841 update README.md 2017-06-02 19:06:01 +03:00
Paddy Xu
c320249f20 Revert "upx -1 libcef.dll" as it makes release package even larger
This reverts commit 903c90472b.
2017-06-02 19:04:03 +03:00
Paddy Xu
96a46e3a23 Swirth to ImageMagick which supports PSD and RAW images 2017-06-02 19:01:45 +03:00
Paddy Xu
903c90472b upx -1 libcef.dll 2017-06-01 21:05:52 +03:00
Paddy Xu
8deddb2537 Revert "Go back from Webkit to IE render engine"
This reverts commit f58ef0dcb6.
2017-06-01 20:58:38 +03:00
Paddy Xu
f58ef0dcb6 Go back from Webkit to IE render engine 2017-06-01 19:49:54 +03:00
Paddy Xu
134c164a7e Fix 25: do not set Handled flag 2017-06-01 18:24:18 +03:00
Paddy Xu
6ed5b9b6d9 Update README.md 2017-05-31 15:10:00 +03:00
Paddy Xu
4dede66da9 Fix error when previous preview window is closed in Max mode 2017-05-30 22:08:33 +03:00
Paddy Xu
43e67356f2 Fix scrolling to fast on a precision touchpad 2017-05-30 00:19:40 +03:00
Paddy Xu
4dafe7eebe Dispose Bitmap after have converted to BitmapSource 2017-05-29 21:25:41 +03:00
Paddy Xu
3be1c221c5 Update README.md 2017-05-29 12:09:49 +03:00
Paddy Xu
ad5fd47c26 update README file 2017-05-29 00:25:54 +03:00
Paddy Xu
5ad20b8a53 Mooore aggressive GC; rewrite native dll 2017-05-29 00:11:16 +03:00
Paddy Xu
13dfa7a815 show current version in tray icon 2017-05-28 16:11:23 +03:00
Paddy Xu
a7bd0ceb91 remove template file type 2017-05-28 16:02:43 +03:00
Paddy Xu
3af6448b50 move to .net 4.6.2 2017-05-28 15:59:53 +03:00
Paddy Xu
3768470a33 Aggressive GC after window closed 2017-05-28 15:59:31 +03:00
Paddy Xu
7e95d5ea2b keep stack trace when rethrowing exception 2017-05-28 00:19:51 +03:00
Paddy Xu
f4e68c109e append to last commit: correct UI actions 2017-05-27 23:25:47 +03:00
Paddy Xu
93e1512b94 Response to all selectionchanged event by using a task 2017-05-27 23:06:55 +03:00
Paddy Xu
6471125a0b improve ENTER key handle 2017-05-27 19:37:18 +03:00
Paddy Xu
16f20dc79d Fix #16: Use MS Office preview handler (will LibreOffice has this in the future?) for OpenDocument files. 2017-05-27 19:08:39 +03:00
Paddy Xu
9ffb4a0e91 Fix #22: disable all JS dialogs 2017-05-27 09:50:09 +03:00
Paddy Xu
7d713682ba the plug-in might be disposed before COM finishes when pressing SPACE key tooooo fast 2017-05-27 00:41:10 +03:00
Paddy Xu
cdddb303bd do not show useless texts e.g. "... and 0 files" 2017-05-27 00:39:29 +03:00
Paddy Xu
250e796095 use Dispatcher instead of Loaded event 2017-05-27 00:38:29 +03:00
Paddy Xu
4c82f1a9f4 handle ENTER key and refactoring invoke routine 2017-05-27 00:37:26 +03:00
Paddy Xu
9fbf33b4aa #20: remove dependency on vc_redist 2017-05-26 20:10:41 +03:00
79 changed files with 1143 additions and 15139 deletions

View File

@@ -5,22 +5,12 @@
#define EXPORT extern "C" __declspec(dllexport)
EXPORT int GetFocusedWindowType()
EXPORT Shell32::FocusedWindowType GetFocusedWindowType()
{
return Shell32::GetFocusedWindowType();
}
EXPORT void SaveCurrentSelection()
EXPORT void GetCurrentSelection(PWCHAR buffer)
{
Shell32::SaveCurrentSelection();
}
EXPORT int GetCurrentSelectionCount()
{
return Shell32::GetCurrentSelectionCount();
}
EXPORT void GetCurrentSelectionBuffer(PWCHAR buffer)
{
Shell32::GetCurrentSelectionBuffer(buffer);
Shell32::GetCurrentSelection(buffer);
}

View File

@@ -58,6 +58,7 @@
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;QUICKLOOKSHELL32HELPER_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@@ -72,6 +73,7 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;QUICKLOOKSHELL32HELPER_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>

View File

@@ -1,201 +1,19 @@
#include "stdafx.h"
#include "Shell32.h"
#include <atlcomcli.h>
using namespace std;
vector<wstring> Shell32::vector_items;
void Shell32::SaveCurrentSelection()
{
vector_items.clear();
switch (GetFocusedWindowType())
{
case EXPLORER:
SaveSelectedFromExplorer();
break;
case DESKTOP:
SaveSelectedFromDesktop();
break;
default:
break;
}
}
UINT Shell32::GetCurrentSelectionCount()
{
return vector_items.size();
}
void Shell32::GetCurrentSelectionBuffer(PWCHAR buffer)
{
PWCHAR pos = buffer;
for (vector<wstring>::iterator it = vector_items.begin(); it < vector_items.end(); ++it)
{
int l = it->length();
wcscpy_s(pos, l + 1, it->c_str());
pos += l;
// overwrite NULL
wcscpy_s(pos++, 2, L"|");
}
// remove last "|"
wcscpy_s(pos - 1, 1, L"");
}
void Shell32::SaveSelectedFromExplorer()
{
CoInitialize(nullptr);
CComPtr<IShellWindows> psw;
HRESULT ret = psw.CoCreateInstance(CLSID_ShellWindows);
auto hwndFGW = GetForegroundWindow();
auto fFound = FALSE;
for (int i = 0; !fFound; i++)
{
VARIANT vi;
V_VT(&vi) = VT_I4;
V_I4(&vi) = i;
CComPtr<IDispatch> pdisp;
// ReSharper disable once CppSomeObjectMembersMightNotBeInitialized
if (SUCCEEDED(psw->Item(vi, &pdisp)))
{
CComPtr<IWebBrowserApp> pwba;
if (SUCCEEDED(pdisp->QueryInterface(IID_IWebBrowserApp, reinterpret_cast<void**>(&pwba))))
{
HWND hwndWBA;
if (SUCCEEDED(pwba->get_HWND(reinterpret_cast<LONG_PTR*>(&hwndWBA))) && hwndWBA == hwndFGW)
{
fFound = TRUE;
CComPtr<IDispatch> ppdisp;
if (SUCCEEDED(pwba->get_Document(&ppdisp)))
{
CComPtr<IShellFolderViewDual2> pshvd;
if (SUCCEEDED(ppdisp->QueryInterface(IID_IShellFolderViewDual2, reinterpret_cast<void**>(&pshvd))))
{
CComPtr<FolderItems> pfis;
if (SUCCEEDED(pshvd->SelectedItems(&pfis)))
{
LONG pCount = 0L;
pfis->get_Count(&pCount);
for (int ii = 0; ii < pCount; ii++)
{
VARIANT vii;
V_VT(&vii) = VT_I4;
V_I4(&vii) = ii;
CComPtr<FolderItem> pfi;
// ReSharper disable once CppSomeObjectMembersMightNotBeInitialized
if (SUCCEEDED(pfis->Item(vii, &pfi)))
{
CComBSTR pbs = SysAllocStringLen(L"", MAX_PATH);
pfi->get_Path(&pbs);
wstring ws = wstring(pbs);
ws.shrink_to_fit();
vector_items.push_back(ws);
}
}
}
}
}
}
}
}
}
}
CComQIPtr<IWebBrowser2> Shell32::AttachDesktopShellWindow()
{
CoInitialize(nullptr);
CComPtr<IShellWindows> psw;
CComQIPtr<IWebBrowser2> pdispOut;
if (SUCCEEDED(psw.CoCreateInstance(CLSID_ShellWindows)))
{
VARIANT pvarLoc = {VT_EMPTY};
long phwnd;
psw->FindWindowSW(&pvarLoc, &pvarLoc, SWC_DESKTOP, &phwnd, SWFO_NEEDDISPATCH, reinterpret_cast<IDispatch**>(&pdispOut));
}
return pdispOut;
}
void Shell32::SaveSelectedFromDesktop()
{
auto pWebBrowser2 = AttachDesktopShellWindow();
if (!pWebBrowser2)
return;
CComQIPtr<IServiceProvider> psp(pWebBrowser2);
if (!psp) return;
CComPtr<IShellBrowser> psb;
if (SUCCEEDED(psp->QueryService(SID_STopLevelBrowser, IID_IShellBrowser, reinterpret_cast<LPVOID*>(&psb))))
{
CComPtr<IShellView> psv;
if (SUCCEEDED(psb->QueryActiveShellView(&psv)))
{
CComPtr<IFolderView> pfv;
if (SUCCEEDED(psv->QueryInterface(IID_IFolderView, reinterpret_cast<void**>(&pfv))))
{
CComPtr<IDataObject> dao;
if (SUCCEEDED(psv->GetItemObject(SVGIO_SELECTION, IID_IDataObject, reinterpret_cast<void**>(&dao))))
vectorFromDataObject(dao);
}
}
}
}
void Shell32::vectorFromDataObject(CComPtr<IDataObject> dao)
{
FORMATETC formatetc;
STGMEDIUM medium;
formatetc.cfFormat = CF_HDROP;
formatetc.ptd = nullptr;
formatetc.dwAspect = DVASPECT_CONTENT;
formatetc.lindex = -1;
formatetc.tymed = TYMED_HGLOBAL;
medium.tymed = TYMED_HGLOBAL;
if (SUCCEEDED(dao->GetData(&formatetc, &medium)))
{
int n = DragQueryFile(HDROP(medium.hGlobal), 0xFFFFFFFF, nullptr, 0);
for (int i = 0; i < n; i++)
{
WCHAR buffer[MAX_PATH];
DragQueryFile(HDROP(medium.hGlobal), i, buffer, MAX_PATH - 1);
wstring ws = wstring(buffer);
ws.shrink_to_fit();
vector_items.push_back(ws);
}
}
}
Shell32::FocusedWindowType Shell32::GetFocusedWindowType()
{
auto type = INVALID;
auto hwndfg = GetForegroundWindow();
if (isCursorActivated(hwndfg))
return INVALID;
auto classBuffer = new WCHAR[MAX_PATH];
if (SUCCEEDED(GetClassName(hwndfg, classBuffer, MAX_PATH)))
{
@@ -215,3 +33,132 @@ Shell32::FocusedWindowType Shell32::GetFocusedWindowType()
return type;
}
void Shell32::GetCurrentSelection(PWCHAR buffer)
{
switch (GetFocusedWindowType())
{
case EXPLORER:
getSelectedFromExplorer(buffer);
break;
case DESKTOP:
getSelectedFromDesktop(buffer);
break;
default:
break;
}
}
void Shell32::getSelectedFromExplorer(PWCHAR buffer)
{
CoInitialize(nullptr);
CComPtr<IShellWindows> psw;
if (FAILED(psw.CoCreateInstance(CLSID_ShellWindows)))
return;
auto hwndFGW = GetForegroundWindow();
auto count = 0L;
psw->get_Count(&count);
for (auto i = 0; i < count; i++)
{
VARIANT vi;
V_VT(&vi) = VT_I4;
V_I4(&vi) = i;
CComPtr<IDispatch> pdisp;
// ReSharper disable once CppSomeObjectMembersMightNotBeInitialized
if (FAILED(psw->Item(vi, &pdisp)))
continue;
CComQIPtr<IWebBrowserApp> pwba;
if (FAILED(pdisp->QueryInterface(IID_IWebBrowserApp, reinterpret_cast<void**>(&pwba))))
continue;
HWND hwndWBA;
if (FAILED(pwba->get_HWND(reinterpret_cast<LONG_PTR*>(&hwndWBA))))
continue;
if (hwndWBA != hwndFGW || isCursorActivated(hwndWBA))
continue;
getSelectedInternal(pwba, buffer);
}
}
void Shell32::getSelectedFromDesktop(PWCHAR buffer)
{
CoInitialize(nullptr);
CComPtr<IShellWindows> psw;
CComQIPtr<IWebBrowserApp> pwba;
if (FAILED(psw.CoCreateInstance(CLSID_ShellWindows)))
return;
VARIANT pvarLoc = {VT_EMPTY};
long phwnd;
if (FAILED(psw->FindWindowSW(&pvarLoc, &pvarLoc, SWC_DESKTOP, &phwnd, SWFO_NEEDDISPATCH, reinterpret_cast<IDispatch**>(&pwba))))
return;
if (isCursorActivated(reinterpret_cast<HWND>(phwnd)))
return;
getSelectedInternal(pwba, buffer);
}
void Shell32::getSelectedInternal(CComQIPtr<IWebBrowserApp> pwba, PWCHAR buffer)
{
CComQIPtr<IServiceProvider> psp;
if (FAILED(pwba->QueryInterface(IID_IServiceProvider, reinterpret_cast<void**>(&psp))))
return;
CComPtr<IShellBrowser> psb;
if (FAILED(psp->QueryService(SID_STopLevelBrowser, IID_IShellBrowser, reinterpret_cast<LPVOID*>(&psb))))
return;
CComPtr<IShellView> psv;
if (FAILED(psb->QueryActiveShellView(&psv)))
return;
CComPtr<IDataObject> dao;
if (FAILED(psv->GetItemObject(SVGIO_SELECTION, IID_IDataObject, reinterpret_cast<void**>(&dao))))
return;
return obtainFirstItem(dao, buffer);
}
void Shell32::obtainFirstItem(CComPtr<IDataObject> dao, PWCHAR buffer)
{
FORMATETC formatetc;
STGMEDIUM medium;
formatetc.cfFormat = CF_HDROP;
formatetc.ptd = nullptr;
formatetc.dwAspect = DVASPECT_CONTENT;
formatetc.lindex = -1;
formatetc.tymed = TYMED_HGLOBAL;
medium.tymed = TYMED_HGLOBAL;
if (FAILED(dao->GetData(&formatetc, &medium)))
return;
int n = DragQueryFile(HDROP(medium.hGlobal), 0xFFFFFFFF, nullptr, 0);
if (n < 1)
return;
DragQueryFile(HDROP(medium.hGlobal), 0, buffer, MAX_PATH - 1);
}
bool Shell32::isCursorActivated(HWND hwnd)
{
auto tId = GetWindowThreadProcessId(hwnd, nullptr);
GUITHREADINFO gui = {sizeof gui};
GetGUIThreadInfo(tId, &gui);
return gui.flags || gui.hwndCaret;
}

View File

@@ -13,16 +13,13 @@ public:
};
static FocusedWindowType GetFocusedWindowType();
static void SaveCurrentSelection();
static UINT GetCurrentSelectionCount();
static void GetCurrentSelectionBuffer(PWCHAR buffer);
static void GetCurrentSelection(PWCHAR buffer);
private:
static std::vector<std::wstring> vector_items;
static void getSelectedFromDesktop(PWCHAR buffer);
static void getSelectedFromExplorer(PWCHAR buffer);
static void SaveSelectedFromDesktop();
static void SaveSelectedFromExplorer();
static CComQIPtr<IWebBrowser2> AttachDesktopShellWindow();
static void vectorFromDataObject(CComPtr<IDataObject> dao);
static void getSelectedInternal(CComQIPtr<IWebBrowserApp> pWebBrowserApp, PWCHAR buffer);
static void obtainFirstItem(CComPtr<IDataObject> dao, PWCHAR buffer);
static bool isCursorActivated(HWND hwndfg);
};

View File

@@ -42,23 +42,33 @@ namespace QuickLook.Plugin.ArchiveViewer
{
LoadItemsFromArchive(path);
var folder = 0;
var folders = -1; // do not count root node
var files = 0;
ulong sizeU = 0L;
_fileEntries.ForEach(e =>
{
if (e.Value.IsFolder)
folder++;
folders++;
else
files++;
sizeU += e.Value.Size;
});
var s = _solid ? " solid," : "";
var s = _solid ? ", solid" : "";
string t;
var d = folders != 0 ? $"{folders} folders" : string.Empty;
var f = files != 0 ? $"{files} files" : string.Empty;
if (!string.IsNullOrEmpty(d) && !string.IsNullOrEmpty(f))
t = $", {d} and {f}";
else if (string.IsNullOrEmpty(d) && string.IsNullOrEmpty(f))
t = string.Empty;
else
t = $", {d}{f}";
archiveCount.Content =
$"{_type} archive,{s} {folder - 1} folders and {files} files"; // do not count root node
$"{_type} archive{s}{t}";
archiveSizeC.Content = $"Compressed size {_totalZippedSize.ToPrettySize(2)}";
archiveSizeU.Content = $"Uncompressed size {sizeU.ToPrettySize(2)}";
}

View File

@@ -9,8 +9,9 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>QuickLook.Plugin.ArchiveViewer</RootNamespace>
<AssemblyName>QuickLook.Plugin.ArchiveViewer</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>

View File

@@ -0,0 +1,50 @@
using System.IO;
using System.Text;
using Microsoft.Win32;
namespace QuickLook.Plugin.HtmlViewer
{
internal static class Helper
{
public static string FilePathToFileUrl(string filePath)
{
var uri = new StringBuilder();
foreach (var v in filePath)
if (v >= 'a' && v <= 'z' || v >= 'A' && v <= 'Z' || v >= '0' && v <= '9' ||
v == '+' || v == '/' || v == ':' || v == '.' || v == '-' || v == '_' || v == '~' ||
v > '\xFF')
uri.Append(v);
else if (v == Path.DirectorySeparatorChar || v == Path.AltDirectorySeparatorChar)
uri.Append('/');
else
uri.Append($"%{(int) v:X2}");
if (uri.Length >= 2 && uri[0] == '/' && uri[1] == '/') // UNC path
uri.Insert(0, "file:");
else
uri.Insert(0, "file:///");
return uri.ToString();
}
public static void SetBrowserFeatureControl()
{
var exeName = Path.GetFileName(App.AppFullPath);
// use latest engine
SetBrowserFeatureControlKey("FEATURE_BROWSER_EMULATION", exeName, 0);
//
SetBrowserFeatureControlKey("FEATURE_GPU_RENDERING", exeName, 0);
// turn on hi-dpi mode
SetBrowserFeatureControlKey("FEATURE_96DPI_PIXEL", exeName, 1);
}
private static void SetBrowserFeatureControlKey(string feature, string appName, uint value)
{
using (var key = Registry.CurrentUser.CreateSubKey(
string.Concat(@"Software\Microsoft\Internet Explorer\Main\FeatureControl\", feature),
RegistryKeyPermissionCheck.ReadWriteSubTree))
{
key?.SetValue(appName, value, RegistryValueKind.DWord);
}
}
}
}

View File

@@ -1,35 +0,0 @@
// Copyright © 2010-2017 The CefSharp Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
using CefSharp;
namespace QuickLook.Plugin.HtmlViewer
{
internal class MenuHandler : IContextMenuHandler
{
public void OnBeforeContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame,
IContextMenuParams parameters,
IMenuModel model)
{
}
public bool OnContextMenuCommand(IWebBrowser browserControl, IBrowser browser, IFrame frame,
IContextMenuParams parameters,
CefMenuCommand commandId, CefEventFlags eventFlags)
{
return false;
}
public void OnContextMenuDismissed(IWebBrowser browserControl, IBrowser browser, IFrame frame)
{
}
public bool RunContextMenu(IWebBrowser browserControl, IBrowser browser, IFrame frame,
IContextMenuParams parameters,
IMenuModel model, IRunContextMenuCallback callback)
{
return true;
}
}
}

View File

@@ -1,15 +1,16 @@
using System;
using System.IO;
using System.Windows;
using System.Windows.Threading;
namespace QuickLook.Plugin.HtmlViewer
{
public class Plugin : IViewer
{
private WebkitPanel _panel;
private WebpagePanel _panel;
public int Priority => int.MaxValue;
public bool AllowsTransparency => true;
public bool AllowsTransparency => false;
public bool CanHandle(string path)
{
@@ -36,12 +37,12 @@ namespace QuickLook.Plugin.HtmlViewer
public void View(string path, ContextObject context)
{
_panel = new WebkitPanel();
_panel = new WebpagePanel();
context.ViewerContent = _panel;
context.Title = Path.IsPathRooted(path) ? Path.GetFileName(path) : path;
_panel.Navigate(path);
context.IsBusy = false;
_panel.Dispatcher.Invoke(() => { context.IsBusy = false; }, DispatcherPriority.Loaded);
}
public void Cleanup()

View File

@@ -8,12 +8,13 @@
<OutputType>library</OutputType>
<RootNamespace>QuickLook.Plugin.HtmlViewer</RootNamespace>
<AssemblyName>QuickLook.Plugin.HtmlViewer</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
@@ -34,20 +35,10 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="CefSharp, Version=57.0.0.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=x86">
<SpecificVersion>False</SpecificVersion>
<HintPath>References\CefSharp.dll</HintPath>
</Reference>
<Reference Include="CefSharp.Core, Version=57.0.0.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=x86">
<SpecificVersion>False</SpecificVersion>
<HintPath>References\CefSharp.Core.dll</HintPath>
</Reference>
<Reference Include="CefSharp.Wpf, Version=57.0.0.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138, processorArchitecture=x86">
<SpecificVersion>False</SpecificVersion>
<HintPath>References\CefSharp.Wpf.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
@@ -56,19 +47,18 @@
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<Page Include="WebkitPanel.xaml">
<Compile Include="WpfBrowserWrapper.cs" />
<Page Include="WebpagePanel.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Compile Include="..\..\GitVersion.cs">
<Link>Properties\GitVersion.cs</Link>
</Compile>
<Compile Include="MenuHandler.cs" />
<Compile Include="Plugin.cs" />
<Compile Include="RequestHandler.cs" />
<Compile Include="UrlHelper.cs" />
<Compile Include="WebkitPanel.xaml.cs">
<DependentUpon>WebkitPanel.xaml</DependentUpon>
<Compile Include="Helper.cs" />
<Compile Include="WebpagePanel.xaml.cs">
<DependentUpon>WebpagePanel.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
</ItemGroup>
@@ -76,38 +66,6 @@
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<ContentWithTargetPath Include="CefSharp\cef.pak">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>cef.pak</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="CefSharp\cef_100_percent.pak">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>cef_100_percent.pak</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="CefSharp\cef_200_percent.pak">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>cef_200_percent.pak</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="CefSharp\cef_extensions.pak">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>cef_extensions.pak</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="CefSharp\icudtl.dat">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>icudtl.dat</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="CefSharp\locales\en-US.pak">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>locales\en-US.pak</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="CefSharp\natives_blob.bin">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>natives_blob.bin</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="CefSharp\snapshot_blob.bin">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>snapshot_blob.bin</TargetPath>
</ContentWithTargetPath>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\QuickLook\QuickLook.csproj">
@@ -117,38 +75,15 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<ContentWithTargetPath Include="CefSharp\CefSharp.BrowserSubprocess.Core.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>CefSharp.BrowserSubprocess.Core.dll</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="CefSharp\CefSharp.BrowserSubprocess.exe">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>CefSharp.BrowserSubprocess.exe</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="CefSharp\chrome_elf.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>chrome_elf.dll</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="CefSharp\d3dcompiler_47.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>d3dcompiler_47.dll</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="CefSharp\libcef.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>libcef.dll</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="CefSharp\libEGL.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>libEGL.dll</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="CefSharp\libGLESv2.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>libGLESv2.dll</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="CefSharp\widevinecdmadapter.dll">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<TargetPath>widevinecdmadapter.dll</TargetPath>
</ContentWithTargetPath>
<COMReference Include="SHDocVw">
<Guid>{EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}</Guid>
<VersionMajor>1</VersionMajor>
<VersionMinor>1</VersionMinor>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>False</Isolated>
<EmbedInteropTypes>True</EmbedInteropTypes>
</COMReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@@ -1,113 +0,0 @@
// Copyright © 2010-2017 The CefSharp Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
using System.Security.Cryptography.X509Certificates;
using CefSharp;
namespace QuickLook.Plugin.HtmlViewer
{
public class RequestHandler : IRequestHandler
{
bool IRequestHandler.OnBeforeBrowse(IWebBrowser browserControl, IBrowser browser, IFrame frame,
IRequest request, bool isRedirect)
{
return request.TransitionType != TransitionType.Explicit;
}
bool IRequestHandler.OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame,
string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture)
{
return OnOpenUrlFromTab(browserControl, browser, frame, targetUrl, targetDisposition, userGesture);
}
bool IRequestHandler.OnCertificateError(IWebBrowser browserControl, IBrowser browser, CefErrorCode errorCode,
string requestUrl, ISslInfo sslInfo, IRequestCallback callback)
{
callback.Dispose();
return false;
}
void IRequestHandler.OnPluginCrashed(IWebBrowser browserControl, IBrowser browser, string pluginPath)
{
}
CefReturnValue IRequestHandler.OnBeforeResourceLoad(IWebBrowser browserControl, IBrowser browser, IFrame frame,
IRequest request, IRequestCallback callback)
{
return CefReturnValue.Continue;
}
bool IRequestHandler.GetAuthCredentials(IWebBrowser browserControl, IBrowser browser, IFrame frame,
bool isProxy, string host, int port, string realm, string scheme, IAuthCallback callback)
{
callback.Dispose();
return false;
}
bool IRequestHandler.OnSelectClientCertificate(IWebBrowser browserControl, IBrowser browser, bool isProxy,
string host, int port, X509Certificate2Collection certificates, ISelectClientCertificateCallback callback)
{
//NOTE: If you do not wish to implement this method returning false is the default behaviour
// We also suggest you explicitly Dispose of the callback as it wraps an unmanaged resource.
return OnSelectClientCertificate(browserControl, browser, isProxy, host, port, certificates, callback);
}
void IRequestHandler.OnRenderProcessTerminated(IWebBrowser browserControl, IBrowser browser,
CefTerminationStatus status)
{
}
bool IRequestHandler.OnQuotaRequest(IWebBrowser browserControl, IBrowser browser, string originUrl,
long newSize, IRequestCallback callback)
{
callback.Dispose();
return false;
}
void IRequestHandler.OnResourceRedirect(IWebBrowser browserControl, IBrowser browser, IFrame frame,
IRequest request, IResponse response, ref string newUrl)
{
}
bool IRequestHandler.OnProtocolExecution(IWebBrowser browserControl, IBrowser browser, string url)
{
return false;
}
void IRequestHandler.OnRenderViewReady(IWebBrowser browserControl, IBrowser browser)
{
}
bool IRequestHandler.OnResourceResponse(IWebBrowser browserControl, IBrowser browser, IFrame frame,
IRequest request, IResponse response)
{
return false;
}
IResponseFilter IRequestHandler.GetResourceResponseFilter(IWebBrowser browserControl, IBrowser browser,
IFrame frame, IRequest request, IResponse response)
{
return null;
}
void IRequestHandler.OnResourceLoadComplete(IWebBrowser browserControl, IBrowser browser, IFrame frame,
IRequest request, IResponse response, UrlRequestStatus status, long receivedContentLength)
{
}
protected virtual bool OnOpenUrlFromTab(IWebBrowser browserControl, IBrowser browser, IFrame frame,
string targetUrl, WindowOpenDisposition targetDisposition, bool userGesture)
{
return false;
}
protected virtual bool OnSelectClientCertificate(IWebBrowser browserControl, IBrowser browser, bool isProxy,
string host, int port, X509Certificate2Collection certificates, ISelectClientCertificateCallback callback)
{
callback.Dispose();
return false;
}
}
}

View File

@@ -1,27 +0,0 @@
using System.IO;
using System.Text;
namespace QuickLook.Plugin.HtmlViewer
{
internal static class UrlHelper
{
internal static string FilePathToFileUrl(string filePath)
{
var uri = new StringBuilder();
foreach (var v in filePath)
if (v >= 'a' && v <= 'z' || v >= 'A' && v <= 'Z' || v >= '0' && v <= '9' ||
v == '+' || v == '/' || v == ':' || v == '.' || v == '-' || v == '_' || v == '~' ||
v > '\xFF')
uri.Append(v);
else if (v == Path.DirectorySeparatorChar || v == Path.AltDirectorySeparatorChar)
uri.Append('/');
else
uri.Append($"%{(int) v:X2}");
if (uri.Length >= 2 && uri[0] == '/' && uri[1] == '/') // UNC path
uri.Insert(0, "file:");
else
uri.Insert(0, "file:///");
return uri.ToString();
}
}
}

View File

@@ -1,62 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using CefSharp;
namespace QuickLook.Plugin.HtmlViewer
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class WebkitPanel : UserControl, IDisposable
{
private readonly string _cefPath =
Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
public WebkitPanel()
{
var libraryLoader = new CefLibraryHandle(Path.Combine(_cefPath, "libcef.dll"));
if (!Cef.IsInitialized)
Cef.Initialize(new CefSettings
{
BrowserSubprocessPath = Path.Combine(_cefPath, "CefSharp.BrowserSubprocess.exe"),
LocalesDirPath = Path.Combine(_cefPath, "locales"),
ResourcesDirPath = _cefPath,
LogSeverity = LogSeverity.Disable,
CefCommandLineArgs = {new KeyValuePair<string, string>("disable-gpu", "1")}
});
InitializeComponent();
Application.Current.Exit += (sender, e) => Cef.Shutdown();
browser.RequestHandler = new RequestHandler();
browser.MenuHandler = new MenuHandler();
}
public void Dispose()
{
browser?.Dispose();
}
public void Navigate(string path)
{
if (Path.IsPathRooted(path))
path = UrlHelper.FilePathToFileUrl(path);
browser.IsBrowserInitializedChanged += (sender, e) => browser.Load(path);
}
public void LoadHtml(string html, string path)
{
if (Path.IsPathRooted(path))
path = UrlHelper.FilePathToFileUrl(path);
browser.IsBrowserInitializedChanged += (sender, e) => browser.LoadHtml(html, path);
}
}
}

View File

@@ -1,13 +1,12 @@
<UserControl x:Class="QuickLook.Plugin.HtmlViewer.WebkitPanel"
<UserControl x:Class="QuickLook.Plugin.HtmlViewer.WebpagePanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:QuickLook.Plugin.HtmlViewer"
xmlns:cef="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<cef:ChromiumWebBrowser x:Name="browser" />
<local:WpfWebBrowserWrapper x:Name="browser" Zoom="200" />
</Grid>
</UserControl>

View File

@@ -0,0 +1,42 @@
using System;
using System.IO;
using System.Text;
using System.Windows.Controls;
using System.Windows.Threading;
namespace QuickLook.Plugin.HtmlViewer
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class WebpagePanel : UserControl, IDisposable
{
public WebpagePanel()
{
InitializeComponent();
Helper.SetBrowserFeatureControl();
}
public void Dispose()
{
browser?.Dispose();
browser = null;
}
public void Navigate(string path)
{
if (Path.IsPathRooted(path))
path = Helper.FilePathToFileUrl(path);
browser.Dispatcher.Invoke(() => { browser.Navigate(path); }, DispatcherPriority.Loaded);
}
public void LoadHtml(string html)
{
var s = new MemoryStream(Encoding.UTF8.GetBytes(html ?? ""));
browser.Dispatcher.Invoke(() => { browser.Navigate(s); }, DispatcherPriority.Loaded);
}
}
}

View File

@@ -0,0 +1,233 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms;
using System.Windows.Navigation;
using SHDocVw;
using HorizontalAlignment = System.Windows.HorizontalAlignment;
using WebBrowser = System.Windows.Controls.WebBrowser;
namespace QuickLook.Plugin.HtmlViewer
{
/// <summary>
/// Class wraps a Browser (which itself is a bad designed WPF control) and presents itself as
/// a better designed WPF control. For example provides a bindable source property or commands.
/// </summary>
public class WpfWebBrowserWrapper : ContentControl, IDisposable
{
private static readonly Guid SidSWebBrowserApp = new Guid("0002DF05-0000-0000-C000-000000000046");
private WebBrowser _innerBrowser;
private bool _loaded;
private int _zoom;
public WpfWebBrowserWrapper()
{
_innerBrowser = new WebBrowser
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch
};
Content = _innerBrowser;
_innerBrowser.Navigated += InnerBrowserNavigated;
_innerBrowser.Navigating += InnerBrowserNavigating;
_innerBrowser.LoadCompleted += InnerBrowserLoadCompleted;
_innerBrowser.Loaded += InnerBrowserLoaded;
_innerBrowser.SizeChanged += InnerBrowserSizeChanged;
}
public string Url { get; private set; }
public int Zoom
{
get => _zoom;
set
{
_zoom = value;
ApplyZoom();
}
}
// gets the browser control's underlying activeXcontrol. Ready only from within Loaded-event but before loaded Document!
// do not use prior loaded event.
public InternetExplorer ActiveXControl
{
get
{
// this is a brilliant way to access the WebBrowserObject prior to displaying the actual document (eg. Document property)
var fiComWebBrowser =
typeof(WebBrowser).GetField("_axIWebBrowser2", BindingFlags.Instance | BindingFlags.NonPublic);
if (fiComWebBrowser == null) return null;
var objComWebBrowser = fiComWebBrowser.GetValue(_innerBrowser);
return objComWebBrowser as InternetExplorer;
}
}
public void Dispose()
{
_innerBrowser.Source = null;
_innerBrowser.Dispose();
_innerBrowser = null;
Content = null;
}
private void InnerBrowserSizeChanged(object sender, SizeChangedEventArgs e)
{
ApplyZoom();
}
private void InnerBrowserLoaded(object sender, EventArgs e)
{
var ie = ActiveXControl;
ie.Silent = true;
}
// called when the loading of a web page is done
private void InnerBrowserLoadCompleted(object sender, NavigationEventArgs e)
{
ApplyZoom(); // apply later and not only at changed event, since only works if browser is rendered.
}
// called when the browser started to load and retrieve data.
private void InnerBrowserNavigating(object sender, NavigatingCancelEventArgs e)
{
if (_loaded)
e.Cancel = true;
_loaded = true;
}
// re query the commands once done navigating.
private void InnerBrowserNavigated(object sender, NavigationEventArgs e)
{
RegisterWindowErrorHanlder_();
var alertBlocker =
"window.print = window.alert = window.open = null;document.oncontextmenu=function(){return false;}";
_innerBrowser.InvokeScript("execScript", alertBlocker, "JavaScript");
}
public void Navigate(string uri)
{
Url = uri;
if (_innerBrowser == null)
return;
if (!string.IsNullOrWhiteSpace(uri) && Uri.IsWellFormedUriString(uri, UriKind.Absolute))
try
{
_innerBrowser.Source = new Uri(uri);
}
catch (UriFormatException)
{
// just don't crash because of a malformed url
}
else
_innerBrowser.Source = null;
}
public void Navigate(Stream stream)
{
if (_innerBrowser == null)
return;
try
{
_innerBrowser.NavigateToStream(stream);
}
catch (Exception)
{
// ignored
}
}
// register script errors handler on DOM - document.window
private void RegisterWindowErrorHanlder_()
{
object parwin = ((dynamic) _innerBrowser.Document).parentWindow;
var cookie = new AxHost.ConnectionPointCookie(parwin, new HtmlWindowEvents2Impl(this),
typeof(IIntHTMLWindowEvents2));
// MemoryLEAK? No: cookie has a Finalize() to Disconnect istelf. We'll rely on that. If disconnected too early,
// though (eg. in LoadCompleted-event) scripts continue to run and can cause error messages to appear again.
// --> forget cookie and be happy.
}
private void ApplyZoom()
{
if (_innerBrowser == null || !_innerBrowser.IsLoaded)
return;
// grab a handle to the underlying ActiveX object
IServiceProvider serviceProvider = null;
if (_innerBrowser.Document != null)
serviceProvider = (IServiceProvider) _innerBrowser.Document;
if (serviceProvider == null)
return;
var serviceGuid = SidSWebBrowserApp;
var iid = typeof(IWebBrowser2).GUID;
var browserInst =
(IWebBrowser2) serviceProvider.QueryService(ref serviceGuid, ref iid);
try
{
object zoomPercObj = _zoom;
// send the zoom command to the ActiveX object
browserInst.ExecWB(OLECMDID.OLECMDID_OPTICAL_ZOOM,
OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER,
ref zoomPercObj,
IntPtr.Zero);
}
catch (Exception)
{
// ignore this dynamic call if it fails.
}
}
// needed to implement the Event for script errors
[Guid("3050f625-98b5-11cf-bb82-00aa00bdce0b")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[TypeLibType(TypeLibTypeFlags.FHidden)]
[ComImport]
private interface IIntHTMLWindowEvents2
{
[DispId(1002)]
bool onerror(string description, string url, int line);
}
// needed to implement the Event for script errors
private class HtmlWindowEvents2Impl : IIntHTMLWindowEvents2
{
private readonly WpfWebBrowserWrapper _control;
public HtmlWindowEvents2Impl(WpfWebBrowserWrapper control)
{
_control = control;
}
// implementation of the onerror Javascript error. Return true to indicate a "Handled" state.
public bool onerror(string description, string urlString, int line)
{
Debug.WriteLine(description + "@" + urlString + ": " + line);
// Handled:
return true;
}
}
// Needed to expose the WebBrowser's underlying ActiveX control for zoom functionality
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("6d5140c1-7436-11ce-8034-00aa006009fa")]
internal interface IServiceProvider
{
[return: MarshalAs(UnmanagedType.IUnknown)]
object QueryService(ref Guid guidService, ref Guid riid);
}
}
}

View File

@@ -18,17 +18,25 @@ namespace QuickLook.Plugin.IPreviewHandlers
switch (Path.GetExtension(path).ToLower())
{
// Word
case ".doc":
case ".docx":
case ".docm":
// Excel
case ".xls":
case ".xlsx":
case ".bbb":
case ".xlsm":
case ".xlsb":
// Visio Viewer will not quit after preview, which cause serious memory issue
//case ".vsd":
//case ".vsdx":
// PowerPoint
case ".ppt":
case ".pptx":
// OpenDocument
case ".odt":
case ".ods":
case ".odp":
return PreviewHandlerHost.GetPreviewHandlerGUID(path) != Guid.Empty;
}

View File

@@ -127,6 +127,9 @@ namespace QuickLook.Plugin.IPreviewHandlers
if (_mCurrentPreviewHandler == null)
return false;
if (IsDisposed)
return false;
// bind the preview handler to the control's bounds and preview the content
var r = ClientRectangle;
_mCurrentPreviewHandler.SetWindow(Handle, ref r);

View File

@@ -1,7 +1,6 @@
using System;
using System.Runtime.InteropServices;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Threading;
namespace QuickLook.Plugin.IPreviewHandlers
@@ -37,10 +36,10 @@ namespace QuickLook.Plugin.IPreviewHandlers
_control = new PreviewHandlerHost();
presenter.Child = _control;
_control.Open(file);
}), DispatcherPriority.Render);
}), DispatcherPriority.Loaded);
SetForegroundWindow(new WindowInteropHelper(context.ViewerWindow).Handle);
SetActiveWindow(presenter.Handle);
//SetForegroundWindow(new WindowInteropHelper(context.ViewerWindow).Handle);
//SetActiveWindow(presenter.Handle);
}
[DllImport("user32.dll")]

View File

@@ -8,10 +8,11 @@
<OutputType>library</OutputType>
<RootNamespace>QuickLook.Plugin.IPreviewHandlers</RootNamespace>
<AssemblyName>QuickLook.Plugin.IPreviewHandlers</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>

View File

@@ -1,53 +1,48 @@
using System.IO;
using System;
using System.Windows;
using System.Windows.Media.Imaging;
using ExifLib;
using ImageMagick;
namespace QuickLook.Plugin.ImageViewer
{
internal static class ImageFileHelper
{
internal static Size GetImageSize(string path)
internal static Size? GetImageSize(string path)
{
var ori = GetOrientationFromExif(path);
using (var stream = File.OpenRead(path))
try
{
var decoder = BitmapDecoder.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.None);
var frame = decoder.Frames[0];
var info = new MagickImageInfo(path);
if (ori == ExifOrientation.Rotate90CW || ori == ExifOrientation.Rotate270CW)
return new Size {Width = frame.PixelHeight, Height = frame.PixelWidth};
return new Size {Width = frame.PixelWidth, Height = frame.PixelHeight};
if (ori == OrientationType.RightTop || ori == OrientationType.LeftBotom)
return new Size {Width = info.Height, Height = info.Width};
return new Size {Width = info.Width, Height = info.Height};
}
catch (MagickException)
{
return null;
}
}
internal static ExifOrientation GetOrientationFromExif(string path)
private static OrientationType GetOrientationFromExif(string path)
{
using (var stream = File.OpenRead(path))
try
{
var decoder = BitmapDecoder.Create(stream, BitmapCreateOptions.None, BitmapCacheOption.None);
var frame = decoder.Frames[0];
using (var re = new ExifReader(path))
{
re.GetTagValue(ExifTags.Orientation, out ushort orientation);
var orientation = ((BitmapMetadata) frame.Metadata)?.GetQuery(@"/app1/{ushort=0}/{ushort=274}");
if (orientation == 0)
return OrientationType.Undefined;
if (orientation == null)
return ExifOrientation.Horizontal;
return (ExifOrientation) (ushort) orientation;
return (OrientationType) orientation;
}
}
catch (Exception)
{
return OrientationType.Undefined;
}
}
internal enum ExifOrientation
{
Horizontal = 1,
MirrorHorizontal = 2,
Rotate180 = 3,
MirrorVertical = 4,
MirrorHorizontal270CW = 5,
Rotate90CW = 6,
MirrorHorizontal90CW = 7,
Rotate270CW = 8
}
}
}

View File

@@ -4,7 +4,7 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using ImageMagick;
using XamlAnimatedGif;
namespace QuickLook.Plugin.ImageViewer
@@ -30,37 +30,25 @@ namespace QuickLook.Plugin.ImageViewer
viewPanel.PreviewMouseLeftButtonDown += ViewPanel_PreviewMouseLeftButtonDown;
viewPanel.PreviewMouseMove += ViewPanel_PreviewMouseMove;
viewPanel.TouchDown += ViewPanel_TouchDown;
}
private void ViewPanel_TouchDown(object sender, TouchEventArgs e)
{
// TODO: touch support
}
private void LoadImage(string path)
{
var ori = ImageFileHelper.GetOrientationFromExif(path);
var bitmap = new BitmapImage();
using (var fs = File.OpenRead(path))
{
bitmap.BeginInit();
bitmap.StreamSource = fs;
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.Rotation = ori == ImageFileHelper.ExifOrientation.Rotate90CW
? Rotation.Rotate90
: ori == ImageFileHelper.ExifOrientation.Rotate270CW
? Rotation.Rotate270
: Rotation.Rotate0;
bitmap.EndInit();
}
viewPanelImage.Source = bitmap;
if (Path.GetExtension(path).ToLower() == ".gif")
AnimationBehavior.SetSourceUri(viewPanelImage, new Uri(path));
using (var image = new MagickImage(path))
{
image.Rotate(image.Orientation == OrientationType.RightTop
? 90
: image.Orientation == OrientationType.BottomRight
? 180
: image.Orientation == OrientationType.LeftBotom
? 270
: 0);
viewPanelImage.Source = image.ToBitmapSource();
}
}
private void ViewPanel_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
@@ -92,12 +80,18 @@ namespace QuickLook.Plugin.ImageViewer
private void ViewPanel_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
if ((Keyboard.Modifiers & ModifierKeys.Control) == 0)
return;
e.Handled = true;
var newZoom = _zoomFactor + e.Delta / 120 * 0.1;
if ((Keyboard.Modifiers & ModifierKeys.Control) == 0)
{
// normal scroll
viewPanel.ScrollToVerticalOffset(viewPanel.VerticalOffset - e.Delta);
return;
}
// zoom
var newZoom = _zoomFactor + (double) e.Delta / 120 * 0.1;
newZoom = Math.Max(newZoom, _minZoomFactor);
newZoom = Math.Min(newZoom, 3);

View File

@@ -1,10 +1,22 @@
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
namespace QuickLook.Plugin.ImageViewer
{
public class Plugin : IViewer
{
private static readonly string[] _formats =
{
// camera raw
".3fr", ".ari", ".arw", ".bay", ".crw", ".cr2", ".cap", ".data", ".dcs", ".dcr", ".dng", ".drf", ".eip",
".erf", ".fff", ".gpr", ".iiq", ".k25", ".kdc", ".mdc", ".mef", ".mos", ".mrw", ".nef", ".nrw", ".obm",
".orf", ".pef", ".ptx", ".pxn", ".r3d", ".raf", ".raw", ".rwl", ".rw2", ".rwz", ".sr2", ".srf", ".srw",
".tif", ".x3f",
// normal
".bmp", ".gif", ".ico", ".jpg", ".jpeg", ".png", ".psd", ".svg", ".wdp", ".tiff", ".tga"
};
private Size _imageSize;
private ImagePanel _ip;
@@ -13,47 +25,39 @@ namespace QuickLook.Plugin.ImageViewer
public bool CanHandle(string path)
{
// TODO: determine file type by content
if (Directory.Exists(path))
return false;
switch (Path.GetExtension(path).ToLower())
{
case ".bmp":
case ".gif":
case ".ico":
case ".jpg":
case ".jpeg":
case ".png":
case ".wdp":
case ".tiff":
return true;
default:
return false;
}
return _formats.Contains(Path.GetExtension(path).ToLower());
}
public void Prepare(string path, ContextObject context)
{
_imageSize = ImageFileHelper.GetImageSize(path);
// ImageMagick want to have dcraw.exe
Directory.SetCurrentDirectory(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
context.SetPreferredSizeFit(_imageSize, 0.8);
_imageSize = ImageFileHelper.GetImageSize(path) ?? Size.Empty;
if (!_imageSize.IsEmpty)
context.SetPreferredSizeFit(_imageSize, 0.8);
else
context.PreferredSize = new Size(1024, 768);
}
public void View(string path, ContextObject context)
{
_ip = new ImagePanel(path);
context.ViewerContent = _ip;
context.Title = $"{Path.GetFileName(path)} ({_imageSize.Width}×{_imageSize.Height})";
context.Title = _imageSize.IsEmpty
? $"{Path.GetFileName(path)}"
: $"{Path.GetFileName(path)} ({_imageSize.Width}×{_imageSize.Height})";
context.IsBusy = false;
}
public void Cleanup()
{
Directory.SetCurrentDirectory(App.AppPath);
_ip = null;
}
}

View File

@@ -9,8 +9,11 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>QuickLook.Plugin.ImageViewer</RootNamespace>
<AssemblyName>QuickLook.Plugin.ImageViewer</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
@@ -31,10 +34,17 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="ExifLib, Version=1.7.0.0, Culture=neutral, PublicKeyToken=30284005913968db, processorArchitecture=MSIL">
<HintPath>..\..\packages\ExifLib.1.7.0.0\lib\net45\ExifLib.dll</HintPath>
</Reference>
<Reference Include="Magick.NET-Q8-x86, Version=7.0.0.0, Culture=neutral, PublicKeyToken=2004825badfa91ec, processorArchitecture=x86">
<HintPath>..\..\packages\Magick.NET-Q8-x86.7.0.5.900\lib\net40-client\Magick.NET-Q8-x86.dll</HintPath>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Xaml" />
<Reference Include="WindowsBase" />
<Reference Include="XamlAnimatedGif, Version=1.1.9.0, Culture=neutral, processorArchitecture=MSIL">
@@ -68,5 +78,17 @@
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Content Include="dcraw.exe">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="..\..\packages\Magick.NET-Q8-x86.7.0.5.900\build\net40-client\Magick.NET-Q8-x86.targets" Condition="Exists('..\..\packages\Magick.NET-Q8-x86.7.0.5.900\build\net40-client\Magick.NET-Q8-x86.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\Magick.NET-Q8-x86.7.0.5.900\build\net40-client\Magick.NET-Q8-x86.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Magick.NET-Q8-x86.7.0.5.900\build\net40-client\Magick.NET-Q8-x86.targets'))" />
</Target>
</Project>

View File

@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="XamlAnimatedGif" version="1.1.9" targetFramework="net452" />
<package id="ExifLib" version="1.7.0.0" targetFramework="net462" />
<package id="Magick.NET-Q8-x86" version="7.0.5.900" targetFramework="net462" />
<package id="XamlAnimatedGif" version="1.1.9" targetFramework="net462" />
</packages>

View File

@@ -1,16 +1,17 @@
using System;
using System.IO;
using System.Windows;
using System.Windows.Threading;
using QuickLook.Plugin.HtmlViewer;
namespace QuickLook.Plugin.MarkdownViewer
{
public class Plugin : IViewer
{
private WebkitPanel _panel;
private WebpagePanel _panel;
public int Priority => int.MaxValue;
public bool AllowsTransparency => true;
public bool AllowsTransparency => false;
public bool CanHandle(string path)
{
@@ -20,6 +21,7 @@ namespace QuickLook.Plugin.MarkdownViewer
switch (Path.GetExtension(path).ToLower())
{
case ".md":
case ".rmd":
return true;
default:
@@ -36,13 +38,12 @@ namespace QuickLook.Plugin.MarkdownViewer
public void View(string path, ContextObject context)
{
_panel = new WebkitPanel();
_panel = new WebpagePanel();
context.ViewerContent = _panel;
context.Title = Path.GetFileName(path);
_panel.LoadHtml(GenerateMarkdownHtml(path), path);
context.IsBusy = false;
_panel.LoadHtml(GenerateMarkdownHtml(path));
_panel.Dispatcher.Invoke(() => { context.IsBusy = false; }, DispatcherPriority.Loaded);
}
public void Cleanup()

View File

@@ -9,8 +9,9 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>QuickLook.Plugin.MarkdownViewer</RootNamespace>
<AssemblyName>QuickLook.Plugin.MarkdownViewer</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
@@ -37,12 +36,8 @@ namespace QuickLook.Plugin.PDFViewer
return foundElement;
}
[DllImport("gdi32")]
private static extern int DeleteObject(IntPtr o);
public static BitmapSource ToBitmapSource(this Bitmap source)
{
var ip = source.GetHbitmap();
BitmapSource bs = null;
try
{
@@ -57,9 +52,9 @@ namespace QuickLook.Plugin.PDFViewer
bs.Freeze();
}
finally
catch
{
DeleteObject(ip);
// ignored
}
return bs;

View File

@@ -11,7 +11,7 @@ namespace QuickLook.Plugin.PDFViewer
if (values.Length < 2)
throw new Exception("PageIdToImageConverter");
var zoom = 0.5f;
var zoom = 0.3f;
if (parameter != null)
float.TryParse((string) parameter, out zoom);
@@ -21,7 +21,13 @@ namespace QuickLook.Plugin.PDFViewer
var pageId = (int) values[1];
if (pageId < 0) return null;
return handle.GetPage(pageId, zoom).ToBitmapSource();
var bitmap = handle.GetPage(pageId, zoom);
var bs = bitmap.ToBitmapSource();
bitmap.Dispose();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Optimized);
return bs;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)

View File

@@ -23,7 +23,7 @@
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<ListBox x:Name="listThumbnails" Grid.Column="0" VirtualizingPanel.ScrollUnit="Item"
ScrollViewer.IsDeferredScrollingEnabled="False"
VirtualizingPanel.IsVirtualizing="True"
SelectedIndex="0"
Focusable="False"
Background="#00FFFFFF"

View File

@@ -79,19 +79,28 @@ namespace QuickLook.Plugin.PDFViewer
if (Keyboard.Modifiers != ModifierKeys.None)
return;
e.Handled = true;
if (e.Delta > 0) // up
{
if (pageViewPanel.VerticalOffset != 0) return;
if (pageViewPanel.VerticalOffset != 0)
{
pageViewPanel.ScrollToVerticalOffset(pageViewPanel.VerticalOffset - e.Delta); // normal scroll
return;
}
PrevPage();
e.Handled = true;
}
else // down
{
if (pageViewPanel.VerticalOffset != pageViewPanel.ScrollableHeight) return;
if (pageViewPanel.VerticalOffset != pageViewPanel.ScrollableHeight)
{
pageViewPanel.ScrollToVerticalOffset(pageViewPanel.VerticalOffset - e.Delta); // normal scroll
return;
}
NextPage();
e.Handled = true;
}
}
@@ -143,7 +152,9 @@ namespace QuickLook.Plugin.PDFViewer
if (!PdfLoaded)
return;
var image = PdfHandle.GetPage(CurrentPage, ZoomFactor).ToBitmapSource();
var bitmap = PdfHandle.GetPage(CurrentPage, ZoomFactor);
var image = bitmap.ToBitmapSource();
bitmap.Dispose();
pageViewPanelImage.Source = image;
pageViewPanelImage.Width = pageViewPanelImage.Source.Width;
@@ -276,7 +287,7 @@ namespace QuickLook.Plugin.PDFViewer
e.Handled = true;
newZoom = newZoom + e.Delta / 120 * 0.1;
newZoom = newZoom + (double) e.Delta / 120 * 0.1;
newZoom = Math.Max(newZoom, MinZoomFactor);
newZoom = Math.Min(newZoom, 3);

View File

@@ -1,5 +1,7 @@
using System;
using System.IO;
using System.Runtime.ExceptionServices;
using System.Windows.Threading;
namespace QuickLook.Plugin.PDFViewer
{
@@ -37,16 +39,27 @@ namespace QuickLook.Plugin.PDFViewer
_pdfControl = new PdfViewerControl();
context.ViewerContent = _pdfControl;
_pdfControl.Loaded += (sender, e) =>
Exception exception = null;
_pdfControl.Dispatcher.BeginInvoke(new Action(() =>
{
_pdfControl.LoadPdf(path);
try
{
_pdfControl.LoadPdf(path);
context.Title = $"{Path.GetFileName(path)} (1 / {_pdfControl.TotalPages})";
_pdfControl.CurrentPageChanged += (sender2, e2) => context.Title =
$"{Path.GetFileName(path)} ({_pdfControl.CurrentPage + 1} / {_pdfControl.TotalPages})";
context.Title = $"{Path.GetFileName(path)} (1 / {_pdfControl.TotalPages})";
_pdfControl.CurrentPageChanged += (sender2, e2) => context.Title =
$"{Path.GetFileName(path)} ({_pdfControl.CurrentPage + 1} / {_pdfControl.TotalPages})";
context.IsBusy = false;
}
catch (Exception e)
{
exception = e;
}
}), DispatcherPriority.Loaded).Wait();
context.IsBusy = false;
};
if (exception != null)
ExceptionDispatchInfo.Capture(exception).Throw();
}
public void Cleanup()

View File

@@ -9,8 +9,9 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>QuickLook.Plugin.PDFViewer</RootNamespace>
<AssemblyName>QuickLook.Plugin.PDFViewer</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>

View File

@@ -9,8 +9,9 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>QuickLook.Plugin.TextViewer</RootNamespace>
<AssemblyName>QuickLook.Plugin.TextViewer</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>

View File

@@ -8,10 +8,11 @@
<OutputType>library</OutputType>
<RootNamespace>QuickLook.Plugin.VideoViewer</RootNamespace>
<AssemblyName>QuickLook.Plugin.VideoViewer</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>

View File

@@ -2,7 +2,7 @@
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
</startup>
<runtime>
<legacyCorruptedStateExceptionsPolicy enabled="true" />

View File

@@ -4,6 +4,7 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
using QuickLook.Helpers;
namespace QuickLook
{

View File

@@ -11,12 +11,13 @@ namespace QuickLook
protected BackgroundListener()
{
InstallHook(HotkeyEventHandler);
InstallKeyHook(HotkeyEventHandler);
}
public void Dispose()
{
_hook?.Dispose();
_hook = null;
}
private void HotkeyEventHandler(object sender, KeyEventArgs e)
@@ -24,21 +25,22 @@ namespace QuickLook
if (e.Modifiers != Keys.None)
return;
ViewWindowManager.GetInstance().InvokeRoutine(e.KeyCode);
ViewWindowManager.GetInstance().InvokeRoutine(e);
}
private void InstallHook(KeyEventHandler handler)
private void InstallKeyHook(KeyEventHandler handler)
{
_hook = GlobalKeyboardHook.GetInstance();
_hook.HookedKeys.Add(Keys.Space);
//_hook.HookedKeys.Add(Keys.Enter);
_hook.HookedKeys.Add(Keys.Up);
_hook.HookedKeys.Add(Keys.Down);
_hook.HookedKeys.Add(Keys.Left);
_hook.HookedKeys.Add(Keys.Right);
_hook.HookedDownKeys.Add(Keys.Enter);
_hook.KeyDown += handler;
_hook.HookedUpKeys.Add(Keys.Space);
_hook.HookedUpKeys.Add(Keys.Escape);
_hook.HookedUpKeys.Add(Keys.Up);
_hook.HookedUpKeys.Add(Keys.Down);
_hook.HookedUpKeys.Add(Keys.Left);
_hook.HookedUpKeys.Add(Keys.Right);
_hook.KeyUp += handler;
}

59
QuickLook/FocusMonitor.cs Normal file
View File

@@ -0,0 +1,59 @@
using System;
using System.Threading;
using System.Threading.Tasks;
namespace QuickLook
{
internal class FocusMonitor
{
public delegate void HeartbeatEventHandler(HeartbeatEventArgs e);
private static FocusMonitor _instance;
public bool IsRunning { get; private set; }
public event HeartbeatEventHandler Heartbeat;
public void Start()
{
IsRunning = true;
new Task(() =>
{
while (IsRunning)
{
Thread.Sleep(500);
if (NativeMethods.QuickLook.GetFocusedWindowType() ==
NativeMethods.QuickLook.FocusedWindowType.Invalid)
continue;
var file = NativeMethods.QuickLook.GetCurrentSelection();
Heartbeat?.Invoke(new HeartbeatEventArgs(DateTime.Now.Ticks, file));
}
}).Start();
}
public void Stop()
{
IsRunning = false;
}
internal static FocusMonitor GetInstance()
{
return _instance ?? (_instance = new FocusMonitor());
}
}
internal class HeartbeatEventArgs : EventArgs
{
public HeartbeatEventArgs(long tick, string files)
{
InvokeTick = tick;
FocusedFile = files;
}
public long InvokeTick { get; }
public string FocusedFile { get; }
}
}

View File

@@ -14,7 +14,8 @@ namespace QuickLook
private User32.KeyboardHookProc _callback;
private IntPtr _hhook = IntPtr.Zero;
internal List<Keys> HookedKeys = new List<Keys>();
internal List<Keys> HookedDownKeys = new List<Keys>();
internal List<Keys> HookedUpKeys = new List<Keys>();
protected GlobalKeyboardHook()
{
@@ -63,13 +64,21 @@ namespace QuickLook
if (code >= 0)
{
var key = (Keys) lParam.vkCode;
if (HookedKeys.Contains(key))
if (HookedDownKeys.Contains(key))
{
key = AddModifiers(key);
var kea = new KeyEventArgs(key);
if (wParam == User32.WM_KEYDOWN || wParam == User32.WM_SYSKEYDOWN)
KeyDown?.Invoke(this, kea);
if (kea.Handled)
return 1;
}
else if (HookedUpKeys.Contains(key))
{
key = AddModifiers(key);
var kea = new KeyEventArgs(key);
if (wParam == User32.WM_KEYUP || wParam == User32.WM_SYSKEYUP)
KeyUp?.Invoke(this, kea);
if (kea.Handled)

View File

@@ -1,7 +1,7 @@
using System;
using System.IO;
using System.Runtime.InteropServices.ComTypes;
using QuickLook.NativeMethods;
using QuickLook.NativeMethods.Shell32;
namespace QuickLook.Helpers
{

View File

@@ -2,9 +2,10 @@
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using QuickLook.NativeMethods;
using QuickLook.NativeMethods.Shell32;
namespace QuickLook.Helpers
{
@@ -20,9 +21,8 @@ namespace QuickLook.Helpers
return ret[0];
}
public static bool? GetAssocApplication(string path, out string executePath, out string appFriendlyName)
public static bool? GetAssocApplication(string path, out string appFriendlyName)
{
executePath = string.Empty;
appFriendlyName = string.Empty;
if (string.IsNullOrEmpty(path))
@@ -42,14 +42,13 @@ namespace QuickLook.Helpers
}
var ext = Path.GetExtension(path).ToLower();
var isExe = ext.ToLower() == ".exe";
var isExe = new[] {".cmd", ".bat", ".pif", ".scf", ".exe", ".com", ".scr"}.Contains(ext.ToLower());
// no assoc. app. found
if (string.IsNullOrEmpty(GetAssocApplicationNative(ext, AssocStr.Command)))
if (string.IsNullOrEmpty(GetAssocApplicationNative(ext, AssocStr.AppId))) // UWP
return null;
executePath = path;
appFriendlyName = isExe
? FileVersionInfo.GetVersionInfo(path).FileDescription
: GetAssocApplicationNative(ext, AssocStr.FriendlyAppName);

View File

@@ -1,7 +1,7 @@
using System.Diagnostics;
using System.IO;
namespace QuickLook
namespace QuickLook.Helpers
{
internal static class PidHelper
{

View File

@@ -0,0 +1,15 @@
using System;
using System.Threading.Tasks;
namespace QuickLook.Helpers
{
internal class ProcessHelper
{
// ReSharper disable once InconsistentNaming
public static void PerformAggressiveGC()
{
// delay some time to make sure that all windows are closed
Task.Delay(1000).ContinueWith(t => GC.Collect(GC.MaxGeneration));
}
}
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Diagnostics;
using System.Text;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
@@ -61,65 +60,21 @@ namespace QuickLook.Helpers
pixelY = (int) (matrix.M22 * unitY);
}
internal static bool IsForegroundWindowBelongToSelf()
{
var hwnd = User32.GetForegroundWindow();
if (hwnd == IntPtr.Zero)
return false;
User32.GetWindowThreadProcessId(hwnd, out uint procId);
return procId == Process.GetCurrentProcess().Id;
}
internal static void SetNoactivate(WindowInteropHelper window)
{
User32.SetWindowLong(window.Handle, User32.GWL_EXSTYLE,
User32.GetWindowLong(window.Handle, User32.GWL_EXSTYLE) |
User32.WS_EX_NOACTIVATE);
}
internal static string GetWindowClassName(IntPtr window)
{
var pClassName = new StringBuilder(256);
User32.GetClassName(window, pClassName, pClassName.Capacity);
return pClassName.ToString();
}
internal static IntPtr GetParentWindow(IntPtr child)
{
return User32.GetParent(child);
}
internal static IntPtr GetFocusedWindow()
{
var activeWindowHandle = User32.GetForegroundWindow();
var activeWindowThread = User32.GetWindowThreadProcessId(activeWindowHandle, IntPtr.Zero);
var currentThread = Kernel32.GetCurrentThreadId();
User32.AttachThreadInput(activeWindowThread, currentThread, true);
var focusedControlHandle = User32.GetFocus();
User32.AttachThreadInput(activeWindowThread, currentThread, false);
return focusedControlHandle;
}
internal static bool IsFocusedWindowSelf()
{
var procId = Process.GetCurrentProcess().Id;
uint activeProcId;
User32.GetWindowThreadProcessId(GetFocusedWindow(), out activeProcId);
return activeProcId == procId;
}
internal static bool IsFocusedControlExplorerItem()
{
if (NativeMethods.QuickLook.GetFocusedWindowType() == 0)
return false;
var focusedWindowClass = GetWindowClassName(GetFocusedWindow());
var focusedWindowParentClass =
GetWindowClassName(GetParentWindow(GetFocusedWindow()));
if (focusedWindowClass != "SysListView32" && focusedWindowClass != "DirectUIHWND")
return false;
if (focusedWindowParentClass != "SHELLDLL_DefView")
return false;
return true;
}
}
}

View File

@@ -62,11 +62,17 @@
WindowChrome.IsHitTestVisibleInChrome="True"
Height="15" Margin="10,0" Foreground="Gray"
Cursor="Hand" />
<Button x:Name="buttonOpenWith" DockPanel.Dock="Right" Content="Open with ..." Height="20"
<Button x:Name="buttonOpenWith" DockPanel.Dock="Right" Content="Open with..." Height="20"
Margin="10,0,0,0" Padding="5,0"
Focusable="False" Cursor="Hand"
Background="#E5EEEEEE" BorderBrush="#E59A9A9A"
WindowChrome.IsHitTestVisibleInChrome="True" Foreground="#FF404040" />
WindowChrome.IsHitTestVisibleInChrome="True" Foreground="#FF404040">
<Button.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding}" RecognizesAccessKey="False" />
</DataTemplate>
</Button.ContentTemplate>
</Button>
<!-- set grid.background colour makes it clickable -->
<Grid x:Name="titleArea" Background="Transparent">
<TextBlock Text="{Binding ContextObject.Title, ElementName=mainWindow}" FontSize="14"
@@ -75,7 +81,8 @@
</Grid>
</DockPanel>
<Grid>
<ContentControl x:Name="container" />
<ContentControl x:Name="container"
Content="{Binding ContextObject.ViewerContent, ElementName=mainWindow}" />
</Grid>
</DockPanel>
<Grid x:Name="busyIndicatorLayer">

View File

@@ -1,7 +1,9 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.ExceptionServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using QuickLook.Helpers;
using QuickLook.Helpers.BlurLibrary;
@@ -15,6 +17,7 @@ namespace QuickLook
public partial class MainWindowTransparent : Window
{
private string _path = string.Empty;
private IViewer _plugin;
internal MainWindowTransparent()
{
@@ -33,26 +36,25 @@ namespace QuickLook
BlurWindow.EnableWindowBlur(this);
};
buttonCloseWindow.MouseLeftButtonUp += (sender, e) => BeginHide(true);
buttonCloseWindow.MouseLeftButtonUp += (sender, e) =>
ViewWindowManager.GetInstance().ClosePreview();
/*PreviewKeyUp += (sender, e) =>
{
if (e.Key == Key.Enter)
OpenWithAssocApp();
};*/
buttonOpenWith.Click += (sender, e) => OpenWithAssocApp();
buttonOpenWith.Click += (sender, e) =>
ViewWindowManager.GetInstance().RunAndClosePreview();
}
public ContextObject ContextObject { get; private set; }
private void OpenWithAssocApp()
internal void RunAndHide()
{
if (string.IsNullOrEmpty(_path))
return;
Process.Start(new ProcessStartInfo(_path) {WorkingDirectory = Path.GetDirectoryName(_path)});
BeginHide(true);
Process.Start(new ProcessStartInfo(_path)
{
WorkingDirectory = Path.GetDirectoryName(_path)
});
BeginHide();
}
private void ResizeAndCenter(Size size)
@@ -79,22 +81,31 @@ namespace QuickLook
internal void UnloadPlugin()
{
container.Content = null;
// the focused element will not processed by GC: https://stackoverflow.com/questions/30848939/memory-leak-due-to-window-efectivevalues-retention
FocusManager.SetFocusedElement(this, null);
Keyboard.DefaultRestoreFocusMode =
RestoreFocusMode.None; // WPF will put the focused item into a "_restoreFocus" list ... omg
Keyboard.ClearFocus();
// clean up plugin and refresh ContextObject for next use
ContextObject.ViewerPlugin?.Cleanup();
ContextObject.Reset();
_plugin?.Cleanup();
_plugin = null;
ProcessHelper.PerformAggressiveGC();
}
internal void BeginShow(IViewer matchedPlugin, string path)
{
ContextObject.CurrentContentContainer = container;
ContextObject.ViewerPlugin = matchedPlugin;
_path = path;
_plugin = matchedPlugin;
ContextObject.ViewerWindow = this;
// get window size before showing it
ContextObject.ViewerPlugin.Prepare(path, ContextObject);
_plugin.Prepare(path, ContextObject);
SetOpenWithButtonAndPath(path);
SetOpenWithButtonAndPath();
// revert UI changes
ContextObject.IsBusy = true;
@@ -116,7 +127,7 @@ namespace QuickLook
{
try
{
ContextObject.ViewerPlugin.View(path, ContextObject);
_plugin.View(path, ContextObject);
}
catch (Exception e)
{
@@ -126,43 +137,33 @@ namespace QuickLook
DispatcherPriority.Render).Wait();
if (thrown != null)
throw thrown;
ExceptionDispatchInfo.Capture(thrown).Throw();
}
private void SetOpenWithButtonAndPath(string path)
private void SetOpenWithButtonAndPath()
{
var isExe = FileHelper.GetAssocApplication(path, out string executePath, out string appFriendlyName);
var isExe = FileHelper.GetAssocApplication(_path, out string appFriendlyName);
_path = executePath;
buttonOpenWith.Visibility = isExe == null ? Visibility.Collapsed : Visibility.Visible;
buttonOpenWith.Content = isExe == true ? $"Run {appFriendlyName}" : $"Open with {appFriendlyName}";
buttonOpenWith.Content = isExe == null
? Directory.Exists(_path)
? $"Browse “{Path.GetFileName(_path)}”"
: "Select ..."
: isExe == true
? $"Run “{appFriendlyName}”"
: $"Open with “{appFriendlyName}”";
}
internal bool BeginHide(bool quitIfViewer = false, bool disposePluginOnly = false)
internal void BeginHide()
{
if (quitIfViewer && App.RunningAsViewer)
{
Application.Current.Shutdown();
return true;
}
if (Visibility != Visibility.Visible)
return false;
UnloadPlugin();
ContextObject.Reset();
GC.Collect();
// revert UI changes
ContextObject.IsBusy = true;
// if the this window is hidden in Max state, new show() will results in failure:
// "Cannot show Window when ShowActivated is false and WindowState is set to Maximized"
WindowState = WindowState.Normal;
if (!disposePluginOnly)
{
Hide();
return true;
}
Hide();
return false;
ProcessHelper.PerformAggressiveGC();
}
}
}

View File

@@ -1,20 +1,36 @@
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace QuickLook.NativeMethods
{
internal static class QuickLook
{
[DllImport("QuickLook.Native.Shell32.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern int GetFocusedWindowType();
internal static extern FocusedWindowType GetFocusedWindowType();
[DllImport("QuickLook.Native.Shell32.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern void SaveCurrentSelection();
[DllImport("QuickLook.Native.Shell32.dll", EntryPoint = "GetCurrentSelection",
CallingConvention = CallingConvention.Cdecl)]
internal static extern void GetCurrentSelectionNative([MarshalAs(UnmanagedType.LPWStr)] StringBuilder sb);
[DllImport("QuickLook.Native.Shell32.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern int GetCurrentSelectionCount();
internal static string GetCurrentSelection()
{
StringBuilder sb = null;
// communicate with COM in a separate thread
Task.Run(() =>
{
sb = new StringBuilder(255 + 1);
GetCurrentSelectionNative(sb);
}).Wait();
[DllImport("QuickLook.Native.Shell32.dll", CallingConvention = CallingConvention.Cdecl)]
internal static extern void GetCurrentSelectionBuffer([MarshalAs(UnmanagedType.LPWStr)] StringBuilder buffer);
return sb?.ToString() ?? string.Empty;
}
internal enum FocusedWindowType
{
Invalid,
Desktop,
Explorer
}
}
}

View File

@@ -2,14 +2,8 @@
using System.Runtime.InteropServices;
using System.Text;
namespace QuickLook.NativeMethods
namespace QuickLook.NativeMethods.Shell32
{
[ComImport]
[Guid("00021401-0000-0000-C000-000000000046")]
internal class ShellLink
{
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("000214F9-0000-0000-C000-000000000046")]
@@ -42,36 +36,4 @@ namespace QuickLook.NativeMethods
void Resolve(IntPtr hwnd, int fFlags);
void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
}
[ComImport]
[Guid("72C24DD5-D70A-438B-8A42-98424B88AFB8")]
internal class WshShell
{
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[Guid("F935DC21-1CF0-11D0-ADB9-00C04FD58A0B")]
internal interface IWshShell
{
IWshShortcut CreateShortcut(string pathLink);
}
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[Guid("F935DC23-1CF0-11D0-ADB9-00C04FD58A0B")]
internal interface IWshShortcut
{
string FullName { get; }
string Arguments { get; set; }
string Description { get; set; }
string Hotkey { get; set; }
string IconLocation { get; set; }
string RelativePath { set; }
string TargetPath { get; set; }
int WindowStyle { get; set; }
string WorkingDirectory { get; set; }
void Load([In] string pathLink);
void Save();
}
}

View File

@@ -0,0 +1,12 @@
using System.Runtime.InteropServices;
namespace QuickLook.NativeMethods.Shell32
{
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[Guid("F935DC21-1CF0-11D0-ADB9-00C04FD58A0B")]
internal interface IWshShell
{
IWshShortcut CreateShortcut(string pathLink);
}
}

View File

@@ -0,0 +1,22 @@
using System.Runtime.InteropServices;
namespace QuickLook.NativeMethods.Shell32
{
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
[Guid("F935DC23-1CF0-11D0-ADB9-00C04FD58A0B")]
internal interface IWshShortcut
{
string FullName { get; }
string Arguments { get; set; }
string Description { get; set; }
string Hotkey { get; set; }
string IconLocation { get; set; }
string RelativePath { set; }
string TargetPath { get; set; }
int WindowStyle { get; set; }
string WorkingDirectory { get; set; }
void Load([In] string pathLink);
void Save();
}
}

View File

@@ -0,0 +1,10 @@
using System.Runtime.InteropServices;
namespace QuickLook.NativeMethods.Shell32
{
[ComImport]
[Guid("00021401-0000-0000-C000-000000000046")]
internal class ShellLink
{
}
}

View File

@@ -0,0 +1,10 @@
using System.Runtime.InteropServices;
namespace QuickLook.NativeMethods.Shell32
{
[ComImport]
[Guid("72C24DD5-D70A-438B-8A42-98424B88AFB8")]
internal class WshShell
{
}
}

View File

@@ -2,7 +2,6 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using QuickLook.Annotations;
using QuickLook.Helpers;
@@ -18,8 +17,7 @@ namespace QuickLook.Plugin
private bool _isBusy = true;
private string _title = "";
internal ContentControl CurrentContentContainer;
internal IViewer ViewerPlugin;
private object _viewerContent;
/// <summary>
/// Get the viewer window.
@@ -44,8 +42,12 @@ namespace QuickLook.Plugin
/// </summary>
public object ViewerContent
{
get => CurrentContentContainer.Content;
set => CurrentContentContainer.Content = value;
get => _viewerContent;
set
{
_viewerContent = value;
OnPropertyChanged();
}
}
/// <summary>
@@ -94,12 +96,6 @@ namespace QuickLook.Plugin
public event PropertyChangedEventHandler PropertyChanged;
public void DisposePlugin()
{
ViewerPlugin?.Cleanup();
ViewerPlugin = null;
}
/// <summary>
/// Show a notification balloon.
/// </summary>

View File

@@ -1,7 +1,5 @@
using System;
using System.Drawing;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Media;
using System.Windows.Media.Imaging;
@@ -9,16 +7,12 @@ namespace QuickLook.Plugin.InfoPanel
{
public static class Extensions
{
[DllImport("gdi32")]
private static extern int DeleteObject(IntPtr o);
public static BitmapSource ToBitmapSource(this Bitmap source)
{
// BitmapSource.Create throws an exception when the image is scanned backward.
// The Clone() will make it back scanning forward.
source = (Bitmap) source.Clone();
var ip = source.GetHbitmap();
BitmapSource bs = null;
try
{
@@ -33,9 +27,9 @@ namespace QuickLook.Plugin.InfoPanel
bs.Freeze();
}
finally
catch
{
DeleteObject(ip);
// ignored
}
return bs;

View File

@@ -31,8 +31,9 @@
</Image>
<Grid Grid.Column="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="10" />
@@ -42,7 +43,11 @@
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock x:Name="filename" Grid.Row="1" Grid.Column="1" FontSize="19" FontWeight="SemiBold" Padding="3">Filename.ext</TextBlock>
<TextBlock x:Name="filename" Grid.Row="1" Grid.Column="1" FontSize="19" FontWeight="SemiBold" Padding="3"
TextWrapping="Wrap"
LineHeight="25" MaxHeight="60" TextTrimming="CharacterEllipsis">
FilenameFilenameFilenameFilenameFilenameFilenameFilenameFilenameFilenameFilename.ext
</TextBlock>
<TextBlock x:Name="modDate" Grid.Row="3" Grid.Column="1" Padding="3">Last modified at 01/01/2017 00:00:00</TextBlock>
<TextBlock x:Name="totalSize" Grid.Row="4" Grid.Column="1" Padding="3">Calculating size...</TextBlock>
</Grid>

View File

@@ -71,17 +71,24 @@ namespace QuickLook.Plugin.InfoPanel
}
else if (Directory.Exists(path))
{
long totalDirsL;
long totalFilesL;
long totalSizeL;
FileHelper.CountFolder(path, ref _stop, out totalDirsL, out totalFilesL, out totalSizeL);
FileHelper.CountFolder(path, ref _stop,
out long totalDirsL, out long totalFilesL, out long totalSizeL);
if (!Stop)
Dispatcher.Invoke(() =>
{
string t;
var d = totalDirsL != 0 ? $"{totalDirsL} folders" : string.Empty;
var f = totalFilesL != 0 ? $"{totalFilesL} files" : string.Empty;
if (!string.IsNullOrEmpty(d) && !string.IsNullOrEmpty(f))
t = $"({d} and {f})";
else if (string.IsNullOrEmpty(d) && string.IsNullOrEmpty(f))
t = string.Empty;
else
t = $"({d}{f})";
totalSize.Text =
$"{totalSizeL.ToPrettySize(2)} ({totalDirsL} folders and {totalFilesL} files)";
$"{totalSizeL.ToPrettySize(2)} {t}";
});
}
});

View File

@@ -8,21 +8,17 @@
// </auto-generated>
//------------------------------------------------------------------------------
namespace QuickLook.Properties
{
namespace QuickLook.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.1.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default
{
get
{
public static Settings Default {
get {
return defaultInstance;
}
}

View File

@@ -8,7 +8,7 @@
<OutputType>WinExe</OutputType>
<RootNamespace>QuickLook</RootNamespace>
<AssemblyName>QuickLook</AssemblyName>
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
@@ -28,6 +28,7 @@
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>Resources\app.ico</ApplicationIcon>
@@ -83,6 +84,7 @@
<Compile Include="Converters\BooleanToResizeModeConverter.cs" />
<Compile Include="Converters\BooleanToVisibilityCollapsedConverter.cs" />
<Compile Include="Converters\BooleanToResizeBorderThicknessConverter.cs" />
<Compile Include="FocusMonitor.cs" />
<Compile Include="Helpers\AutoStartupHelper.cs" />
<Compile Include="Controls\BackgroundVisualHost.cs" />
<Compile Include="Controls\BusyDecorator.cs" />
@@ -107,9 +109,14 @@
<Compile Include="Helpers\BlurLibrary\PlatformsImpl\Windows8WindowBlurController.cs" />
<Compile Include="Helpers\BlurLibrary\PlatformsImpl\WindowsVistaWindowBlurController.cs" />
<Compile Include="Helpers\FileHelper.cs" />
<Compile Include="Helpers\ProcessHelper.cs" />
<Compile Include="MainWindowNoTransparent.cs" />
<Compile Include="NativeMethods\ShellLink.cs" />
<Compile Include="PidHelper.cs" />
<Compile Include="NativeMethods\Shell32\IShellLink.cs" />
<Compile Include="NativeMethods\Shell32\IWshShell.cs" />
<Compile Include="NativeMethods\Shell32\IWshShortcut.cs" />
<Compile Include="NativeMethods\Shell32\ShellLink.cs" />
<Compile Include="NativeMethods\Shell32\WshShell.cs" />
<Compile Include="Helpers\PidHelper.cs" />
<Compile Include="PluginManager.cs" />
<Compile Include="Plugin\InfoPanel\DpiHelpers.cs" />
<Compile Include="Plugin\InfoPanel\Extensions.cs" />

View File

@@ -3,7 +3,6 @@ using System.Diagnostics;
using System.Windows.Forms;
using QuickLook.Helpers;
using QuickLook.Properties;
using Application = System.Windows.Application;
namespace QuickLook
{
@@ -26,14 +25,17 @@ namespace QuickLook
{
_icon = new NotifyIcon
{
Text = $"QuickLook v{Application.ProductVersion}",
Icon = Resources.app,
Visible = true,
ContextMenu = new ContextMenu(new[]
{
new MenuItem($"v{Application.ProductVersion}") {Enabled = false},
new MenuItem("-"),
new MenuItem("Check for &Updates...",
(sender, e) => Process.Start(@"http://pooi.moe/QuickLook/")),
_itemAutorun,
new MenuItem("&Quit", (sender, e) => Application.Current.Shutdown())
new MenuItem("&Quit", (sender, e) => System.Windows.Application.Current.Shutdown())
})
};

View File

@@ -1,9 +1,7 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.ExceptionServices;
using System.Windows;
using System.Windows.Forms;
using QuickLook.Helpers;
@@ -11,13 +9,16 @@ using QuickLook.Plugin;
namespace QuickLook
{
internal class ViewWindowManager
internal class ViewWindowManager : IDisposable
{
private static ViewWindowManager _instance;
private readonly MainWindowNoTransparent _viewWindowNoTransparent;
private readonly MainWindowTransparent _viewWindowTransparentTransparent;
private MainWindowTransparent _currentMainWindow;
private long _lastSwitchTick;
private string _path = string.Empty;
internal ViewWindowManager()
{
@@ -27,37 +28,151 @@ namespace QuickLook
_currentMainWindow = _viewWindowTransparentTransparent;
}
internal void InvokeRoutine(Keys key)
public void Dispose()
{
// do we need switch to another file?
var replaceView = key == Keys.Up || key == Keys.Down || key == Keys.Left || key == Keys.Right;
if (replaceView && _currentMainWindow.Visibility != Visibility.Visible)
return;
if (!WindowHelper.IsFocusedControlExplorerItem())
if (replaceView || !WindowHelper.IsFocusedWindowSelf())
return;
// should the window be closed (replaceView == false), return without showing new one
if (_currentMainWindow.BeginHide(disposePluginOnly: replaceView))
return;
var path = GetCurrentSelection();
InvokeViewer(path);
StopFocusMonitor();
}
internal void InvokeViewer(string path)
internal void InvokeRoutine(KeyEventArgs kea)
{
switch (kea.KeyCode)
{
case Keys.Up:
case Keys.Down:
case Keys.Left:
case Keys.Right:
SwitchPreview();
break;
case Keys.Space:
TogglePreview();
break;
case Keys.Escape:
ClosePreview();
break;
case Keys.Enter:
RunAndClosePreview();
break;
default:
break;
}
}
internal void RunAndClosePreview()
{
if (NativeMethods.QuickLook.GetFocusedWindowType() ==
NativeMethods.QuickLook.FocusedWindowType.Invalid)
if (!WindowHelper.IsForegroundWindowBelongToSelf())
return;
if (_currentMainWindow.Visibility != Visibility.Visible)
return;
StopFocusMonitor();
_currentMainWindow.RunAndHide();
}
internal void ClosePreview()
{
if (NativeMethods.QuickLook.GetFocusedWindowType() ==
NativeMethods.QuickLook.FocusedWindowType.Invalid)
if (!WindowHelper.IsForegroundWindowBelongToSelf())
return;
if (_currentMainWindow.Visibility != Visibility.Visible)
return;
StopFocusMonitor();
_currentMainWindow.BeginHide();
}
private void TogglePreview()
{
_lastSwitchTick = DateTime.Now.Ticks;
if (NativeMethods.QuickLook.GetFocusedWindowType() ==
NativeMethods.QuickLook.FocusedWindowType.Invalid)
if (!WindowHelper.IsForegroundWindowBelongToSelf())
return;
if (_currentMainWindow.Visibility == Visibility.Visible)
{
ClosePreview();
}
else
{
_path = NativeMethods.QuickLook.GetCurrentSelection();
InvokeViewer();
}
}
private void SwitchPreview()
{
if (_currentMainWindow.Visibility != Visibility.Visible)
return;
_lastSwitchTick = DateTime.Now.Ticks;
if (NativeMethods.QuickLook.GetFocusedWindowType() ==
NativeMethods.QuickLook.FocusedWindowType.Invalid)
return;
_path = NativeMethods.QuickLook.GetCurrentSelection();
InvokeViewer();
}
private void SwitchPreviewRemoteInvoke(HeartbeatEventArgs e)
{
// sleep for 1.5s
if (e.InvokeTick - _lastSwitchTick < 1.5 * TimeSpan.TicksPerSecond)
return;
if (e.FocusedFile == _path)
return;
Debug.WriteLine("SwitchPreviewRemoteInvoke:" + (e.InvokeTick - _lastSwitchTick));
if (string.IsNullOrEmpty(e.FocusedFile))
return;
_currentMainWindow?.Dispatcher.Invoke(() => SwitchPreview());
}
private void RunFocusMonitor()
{
if (!FocusMonitor.GetInstance().IsRunning)
{
FocusMonitor.GetInstance().Start();
FocusMonitor.GetInstance().Heartbeat += SwitchPreviewRemoteInvoke;
}
}
private void StopFocusMonitor()
{
if (FocusMonitor.GetInstance().IsRunning)
{
FocusMonitor.GetInstance().Stop();
FocusMonitor.GetInstance().Heartbeat -= SwitchPreviewRemoteInvoke;
}
}
internal bool InvokeViewer(string path = null)
{
if (path == null)
path = _path;
if (string.IsNullOrEmpty(path))
return;
return false;
if (!Directory.Exists(path) && !File.Exists(path))
return;
return false;
RunFocusMonitor();
var matchedPlugin = PluginManager.GetInstance().FindMatch(path);
BeginShowNewWindow(matchedPlugin, path);
return true;
}
private void BeginShowNewWindow(IViewer matchedPlugin, string path)
@@ -93,39 +208,11 @@ namespace QuickLook
}
else
{
throw;
ExceptionDispatchInfo.Capture(e).Throw();
}
}
}
private string GetCurrentSelection()
{
var path = string.Empty;
// communicate with COM in a separate thread
Task.Run(() =>
{
var paths = GetCurrentSelectionNative();
if (paths.Any())
path = paths.First();
})
.Wait();
return string.IsNullOrEmpty(path) ? string.Empty : path;
}
private string[] GetCurrentSelectionNative()
{
NativeMethods.QuickLook.SaveCurrentSelection();
var n = NativeMethods.QuickLook.GetCurrentSelectionCount();
var sb = new StringBuilder(n * 261); // MAX_PATH + NULL = 261
NativeMethods.QuickLook.GetCurrentSelectionBuffer(sb);
return sb.Length == 0 ? new string[0] : sb.ToString().Split('|');
}
internal static ViewWindowManager GetInstance()
{
return _instance ?? (_instance = new ViewWindowManager());

View File

@@ -8,6 +8,8 @@
<img src="http://pooi.moe/QuickLook/sample.gif" width="400">
↑ The image above is caputred from an ancient version. It has changed a lot.
## Background
[Quick Look](https://en.wikipedia.org/wiki/Quick_Look) is among the few features I missed from Mac OS X. It enables *very* quick preview of file by pressing <kbd>Space</kbd> key while highlighting it, without opening its associated application. Then I decide to add this feature to Windows by myself, which results this “QuickLook” project.
@@ -16,11 +18,12 @@ You may ask, why you write this when there several alternatives available on the
## Features
Till now, QuickLook supports the preview of
- Images: e.g. `.png`, `.jpg`, `.bmp` and `.gif`
- Images: e.g. `.png`, `.jpg`, `.bmp`, `.gif`, `.psd` and Camera RAW formats
- Compressed archives: `.zip`, `.rar`, `.7z` etc.
- Pdf file
- All kinds of text files (determined by file content)
- Microsoft Word (`.doc`, `.docx`), Excel (`.xls`, `.xlsx`) and PowerPoint (`.ppt`, `.pptx`) files (requires MS Office installation)
- OpenDocument (`odt`, `.ods` and `.odp`) files (requires MS Office installation)
- Video files (`.mp4`, `.mkv`, `.m2ts` etc.)
- HTML files (`.htm`, `.html`)
- Markdown file (`.md`)
@@ -33,8 +36,10 @@ In-place preview / Integrate with 3rd-party file manager
Hotkeys in preview window:
- <kbd>Space</kbd> Show/Hide the preview window
- <kbd></kbd> <kbd></kbd> <kbd></kbd> <kbd></kbd> Preview another file
- <kbd>Ctrl+Wheel</kbd> Zoom in/out
- <kbd>Esc</kbd> Hide the preview window
- <kbd>Enter</kbd> Open/Execute current file
- <kbd>Mouse</kbd> <kbd></kbd> <kbd></kbd> <kbd></kbd> <kbd></kbd> Preview another file
- <kbd>Ctrl-Wheel</kbd> Zoom in/out
## Development