Compare commits

...

17 Commits

Author SHA1 Message Date
ema
5cedcff912 Support .pdn in ThumbnailViewer #1708
Some checks failed
MSBuild / build (push) Has been cancelled
MSBuild / publish (push) Has been cancelled
Implementation still under adjustment.
2025-08-02 02:34:32 +08:00
Copilot
9fe37520d3 Support Mermaid diagram rendering in MarkdownViewer (#1730)
* Implement Mermaid diagram support for MarkdownViewer

Co-authored-by: emako <24737061+emako@users.noreply.github.com>

* Replace the mermaid.min.js

https://cdn.jsdelivr.net/npm/mermaid@11.4.1/dist/mermaid.min.js

* Fix Mermaid diagram rendering by updating markdown-it highlight function

Co-authored-by: emako <24737061+emako@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: emako <24737061+emako@users.noreply.github.com>
Co-authored-by: ema <mccoy39082@163.com>
2025-08-02 02:09:52 +08:00
ema
67b5dbf310 Add UseNativeProvider option #1726
Use following option in `QuickLook.Plugin.ImageViewer.config` to fix the issue:
```xml
<?xml version="1.0" encoding="utf-8"?>
<Settings>
  <UseColorProfile>false</UseColorProfile>
  <UseNativeProvider>false</UseNativeProvider>
</Settings>
```
2025-08-02 01:36:13 +08:00
Piteriuz
efe47ff43b Update Polish translation file (#1727)
- Add missing translations for MW_OpenWithMenu and InfoPanel_CantPreventClosing
- Remove duplicate entry for MW_BrowseFolder
2025-08-01 22:21:44 +08:00
ema
417876edd2 Prepare support for .pdn
Some checks failed
MSBuild / build (push) Has been cancelled
MSBuild / publish (push) Has been cancelled
2025-07-31 01:27:43 +08:00
ema
03b1ca557f Fix image .jxr error reading from UseColorProfile 2025-07-31 01:22:13 +08:00
ema
f39b53a5c6 Support Point Cloud Data (.pcd) for 3D spatial
Some checks failed
MSBuild / build (push) Has been cancelled
MSBuild / publish (push) Has been cancelled
1. Only support PCD files with PointXYZ format.
2. Not supported for Color or Intensity formats.
2025-07-28 16:48:13 +08:00
ema
68cb555bad Unsupported .pmx incorrectly handled in advance 2025-07-28 16:19:30 +08:00
dependabot[bot]
d88c8ab8de Bump Magick.NET-Q8-AnyCPU from 14.6.0 to 14.7.0
Some checks failed
MSBuild / build (push) Has been cancelled
MSBuild / publish (push) Has been cancelled
---
updated-dependencies:
- dependency-name: Magick.NET-Q8-AnyCPU
  dependency-version: 14.7.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-24 16:35:58 +08:00
copilot-swe-agent[bot]
a38b7a450a Fix code alignment by adding space to align .Replace method calls
Some checks failed
MSBuild / build (push) Has been cancelled
MSBuild / publish (push) Has been cancelled
Co-authored-by: emako <24737061+emako@users.noreply.github.com>
2025-07-23 23:57:13 +08:00
copilot-swe-agent[bot]
8ad5f39eab Add missing using System.Linq to fix compile error
Co-authored-by: emako <24737061+emako@users.noreply.github.com>
2025-07-23 23:57:13 +08:00
copilot-swe-agent[bot]
83cfd2a3d8 Remove unnecessary Translations.config and simplify RTL detection code
Co-authored-by: emako <24737061+emako@users.noreply.github.com>
2025-07-23 23:57:13 +08:00
copilot-swe-agent[bot]
4840a87858 Add RTL support for markdown files
Co-authored-by: emako <24737061+emako@users.noreply.github.com>
2025-07-23 23:57:13 +08:00
VenusGirl❤
a4d118e11f Update Korean (#1710)
Some checks failed
MSBuild / build (push) Has been cancelled
MSBuild / publish (push) Has been cancelled
2025-07-23 15:07:39 +08:00
ema
79b002c0b3 Unified changelog style
Some checks failed
MSBuild / build (push) Has been cancelled
MSBuild / publish (push) Has been cancelled
2025-07-20 23:00:53 +08:00
ema
b26575581a Update changelog for 4.1.1 release 2025-07-20 22:55:20 +08:00
ema
8c95a42a64 Refactor tray icon to use TrayIconHost 2025-07-20 21:54:32 +08:00
28 changed files with 2916 additions and 457 deletions

View File

@@ -1,3 +1,18 @@
## 4.1.1
- Add built-in ThumbnailViewer plugin [#1662](https://github.com/QL-Win/QuickLook/issues/1662)
- Add built-in HelixViewer for 3d models [#1662](https://github.com/QL-Win/QuickLook/issues/1662)
- Add FBX model support using AssimpNet [#1479](https://github.com/QL-Win/QuickLook/issues/1479)
- Add `SVGA` and `Lottie Files` animation preview support
- Add MathJax inline math support to Markdown [#1640](https://github.com/QL-Win/QuickLook/issues/1640)
- Add `SubRip Subtitle (.srt) files`, `Protobuf`, `NSIS`, `.gitmodules`, `.dotsettings`, `.gitignore`, `.gitattributes`, `Markdown`, `reStructuredText`, `simple QML syntax`, `.env`, `Configuration (.conf;.config;.cfg)` highlighting [#1002](https://github.com/QL-Win/QuickLook/issues/1002)
- Add dark mode highlighting for `PowerShell`, `Registry`, `C`, `C++`, `Java`, `Rust`, `SQL`, `Ruby`, `R`, `PHP`, `Pascal`, `Objective-C`, `Lisp`, `Kotlin`, `Erlang`, `Dart`, `Swift`, `VisualSolution`, `CMake`
- Add `MakefileDetector`, `CMakeListsDetector for CMakeLists.txt`, `DockerfileDetector`, `HostsDetector for hosts` for text viewer
- Improve QuickLook initialization speed
- Optimize JSONDetector with Span
- Set RichTextBox background to transparent
- Revert Add Sandbox detection from 4.1.0 which will call crash
## 4.1.0
- Add built-in AppViewer plugin for `.msi`, `.appx`, `.msix`, `.wgt`, `.wgtu`, `.apk`, `.ipa`, `.hap`, `.deb`, `.dmg`, `.appimage`, `.rpm`, `.aab`
@@ -34,15 +49,11 @@
## 4.0.2
[Major Change]
- Support .pcx image [#1638](https://github.com/QL-Win/QuickLook/issues/1638)
- Improve PE parsing with extended buffer size
- Fix flickering [#1628](https://github.com/QL-Win/QuickLook/issues/1628)
- Fix DpiAwareness for PerMonitor [#1626](https://github.com/QL-Win/QuickLook/issues/1626)
[Small Change]
- Hide PEViewer Title just like InfoPanel
- Avoid audio cover null exception in xaml
@@ -59,8 +70,8 @@
## 4.0.0
[Common]
- Add built-in PE viewer plugin
- Add built-in font viewer plugin
- Update translations
- Update dependent packages
- Add support for Multi Commander
@@ -75,53 +86,27 @@
- Fix plugin installer description length limit
- Prevent crash when WMI fails [#1379](https://github.com/QL-Win/QuickLook/issues/1379)
- Show toast when "Prevent Closing" cannot be cancelled [#1368](https://github.com/QL-Win/QuickLook/issues/1368)
[ImageViewer]
- Add support for multi-layer GIMP .xcf files [#1224](https://github.com/QL-Win/QuickLook/issues/1224)
- Fix .xcf file extension check [#1229](https://github.com/QL-Win/QuickLook/issues/1229)
- Fix HEIC preview rendering [#1470](https://github.com/QL-Win/QuickLook/issues/1470)
- Add support for .qoi, .icns, .dds, .svgz, .psb, .cur, and .ani formats
- Improve animated WebP support (x64 only) [#1024](https://github.com/QL-Win/QuickLook/issues/1024) [#1324](https://github.com/QL-Win/QuickLook/issues/1324)
- Improve GIF decoding performance [#993](https://github.com/QL-Win/QuickLook/issues/993)
- Add copy button to image viewer [#1399](https://github.com/QL-Win/QuickLook/issues/1399)
- Fix SVG rendering error [#1430](https://github.com/QL-Win/QuickLook/issues/1430)
[TextViewer]
- Add double-encoding detection [#471](https://github.com/QL-Win/QuickLook/issues/471) [#600](https://github.com/QL-Win/QuickLook/issues/600) ...
- Improve dark mode rendering
- Catch exceptions from XSHD loader
- Add syntax highlighting for shell scripts [#668](https://github.com/QL-Win/QuickLook/issues/668)
- Add dark mode support for C# syntax highlighting
[ArchiveViewer]
- Improve support for comic archive formats [#1276](https://github.com/QL-Win/QuickLook/issues/1276)
- Redesign file list with Fluent UI
[CsvViewer]
- Change default background color to blue
- Fix issue with non-UTF8 CSV encoding
[MarkdownViewer]
- Improve rendering and stability
[PDFViewer]
- Add support for password-protected PDFs [#155](https://github.com/QL-Win/QuickLook/issues/155)
- Enable auto-resizing of the viewer window
[VideoViewer]
- Fix audio cover parsing error for multiple embedded images
- Add lyric (.lrc) support for audio files [#1506](https://github.com/QL-Win/QuickLook/issues/1506)
- Add support for .mid audio format [#931](https://github.com/QL-Win/QuickLook/issues/931)
- Fix time label overflow in long videos
[PEViewer & FontViewer]
- Add built-in PE viewer plugin
- Add built-in font viewer plugin
- Add support for multi-layer GIMP .xcf files [#1224](https://github.com/QL-Win/QuickLook/issues/1224) for ImageViewer
- Fix .xcf file extension check [#1229](https://github.com/QL-Win/QuickLook/issues/1229) for ImageViewer
- Fix HEIC preview rendering [#1470](https://github.com/QL-Win/QuickLook/issues/1470) for ImageViewer
- Add support for .qoi, .icns, .dds, .svgz, .psb, .cur, and .ani formats for ImageViewer
- Improve animated WebP support (x64 only) [#1024](https://github.com/QL-Win/QuickLook/issues/1024) [#1324](https://github.com/QL-Win/QuickLook/issues/1324) for ImageViewer
- Improve GIF decoding performance [#993](https://github.com/QL-Win/QuickLook/issues/993) for ImageViewer
- Add copy button to image viewer [#1399](https://github.com/QL-Win/QuickLook/issues/1399) for ImageViewer
- Fix SVG rendering error [#1430](https://github.com/QL-Win/QuickLook/issues/1430) for ImageViewer
- Add double-encoding detection [#471](https://github.com/QL-Win/QuickLook/issues/471) [#600](https://github.com/QL-Win/QuickLook/issues/600) for TextViewer
- Improve dark mode rendering for TextViewer
- Catch exceptions from XSHD loader for TextViewer
- Add syntax highlighting for shell scripts [#668](https://github.com/QL-Win/QuickLook/issues/668) for TextViewer
- Add dark mode support for C# syntax highlighting for TextViewer
- Improve support for comic archive formats [#1276](https://github.com/QL-Win/QuickLook/issues/1276) for ArchiveViewer
- Redesign file list with Fluent UI for ArchiveViewer
- Change default background color to blue for CsvViewer
- Fix issue with non-UTF8 CSV encoding for CsvViewer
- Improve rendering and stability for MarkdownViewer
- Add support for password-protected PDFs [#155](https://github.com/QL-Win/QuickLook/issues/155) for PDFViewer
- Enable auto-resizing of the viewer window for PDFViewer
- Fix audio cover parsing error for multiple embedded images for VideoViewer
- Add lyric (.lrc) support for audio files [#1506](https://github.com/QL-Win/QuickLook/issues/1506) for VideoViewer
- Add support for .mid audio format [#931](https://github.com/QL-Win/QuickLook/issues/931) for VideoViewer
- Fix time label overflow in long videos for VideoViewer

View File

@@ -34,6 +34,39 @@
<TERMINAL>Terminal</TERMINAL>
<VENDOR>Vendor</VENDOR>
</en>
<ko>
<PRODUCT_VERSION>제품 버전</PRODUCT_VERSION>
<PRODUCT_NAME>제품 이름</PRODUCT_NAME>
<MANUFACTURER>제조업체</MANUFACTURER>
<TOTAL_SIZE>전체 크기</TOTAL_SIZE>
<LAST_MODIFIED>최종 수정됨</LAST_MODIFIED>
<PUBLISHER>발행자</PUBLISHER>
<CAPABILITIES>성능</CAPABILITIES>
<ABI>ABIs</ABI>
<APP_NAME>응용 프로그램</APP_NAME>
<APP_VERSION>버전</APP_VERSION>
<APP_VERSION_NAME>버전 이름</APP_VERSION_NAME>
<APP_VERSION_CODE>버전 코드</APP_VERSION_CODE>
<PERMISSIONS>권한</PERMISSIONS>
<PACKAGE_NAME>패키지 이름</PACKAGE_NAME>
<APP_MIN_SDK_VERSION>최소 SDK 버전</APP_MIN_SDK_VERSION>
<APP_TARGET_SDK_VERSION>대상 SDK 버전</APP_TARGET_SDK_VERSION>
<APP_MIN_OS_VERSION>최소 OS 버전</APP_MIN_OS_VERSION>
<APP_TARGET_OS_VERSION>대상 OS 버전</APP_TARGET_OS_VERSION>
<DEVICE_FAMILY>장치 제품군</DEVICE_FAMILY>
<BUNDLE_NAME>번들 이름</BUNDLE_NAME>
<DEVICE_TYPES>장치 유형</DEVICE_TYPES>
<APP_MIN_API_VERSION>최소 API 버전</APP_MIN_API_VERSION>
<APP_TARGET_API_VERSION>대상 API 버전</APP_TARGET_API_VERSION>
<COMPILE_SDK_VERSION>SDK 버전 컴파일</COMPILE_SDK_VERSION>
<ARCHITECTURE>아키텍쳐</ARCHITECTURE>
<MAINTAINER>유지 관리자</MAINTAINER>
<DESCRIPTION>설명</DESCRIPTION>
<ENVIRONMENT>환경</ENVIRONMENT>
<TYPE>유형</TYPE>
<TERMINAL>터미널</TERMINAL>
<VENDOR>공급업체</VENDOR>
</ko>
<pt-BR>
<PRODUCT_VERSION>Versão do produto</PRODUCT_VERSION>
<PRODUCT_NAME>Nome do produto</PRODUCT_NAME>

View File

@@ -7,6 +7,12 @@
<ConfirmDeleteText>Are you sure you want to permanently delete these {0} item(s)?</ConfirmDeleteText>
<RecycleBinSizeText>Total size {0}, {1} items</RecycleBinSizeText>
</en>
<ko>
<RecycleBinEmptyText>휴지통이 비어 있습니다</RecycleBinEmptyText>
<RecycleBinButton>휴지통 비우기</RecycleBinButton>
<ConfirmDeleteText>{0}개 항목을 영구적으로 삭제하시겠습니까?</ConfirmDeleteText>
<RecycleBinSizeText>전체 크기 {0}, {1}개 항목</RecycleBinSizeText>
</ko>
<zh-CN>
<RecycleBinEmptyText>回收站是空的</RecycleBinEmptyText>
<RecycleBinButton>清空回收站</RecycleBinButton>
@@ -25,12 +31,6 @@
<ConfirmDeleteText>これらの {0} 項目を完全に削除してもよろしいですか?</ConfirmDeleteText>
<RecycleBinSizeText>合計サイズ {0}、{1} 項目</RecycleBinSizeText>
</ja>
<ko>
<RecycleBinEmptyText>휴지통이 비어 있습니다</RecycleBinEmptyText>
<RecycleBinButton>휴지통 비우기</RecycleBinButton>
<ConfirmDeleteText>{0}개의 항목을 영구적으로 삭제하시겠습니까?</ConfirmDeleteText>
<RecycleBinSizeText>총 크기 {0}, 총 {1}개 항목</RecycleBinSizeText>
</ko>
<fr>
<RecycleBinEmptyText>La corbeille est vide</RecycleBinEmptyText>
<RecycleBinButton>Vider la corbeille</RecycleBinButton>

View File

@@ -7,6 +7,12 @@
<FORMAT_PROFILE>Format Profile</FORMAT_PROFILE>
<NAME>Name</NAME>
</en>
<ko>
<TOTAL_SIZE>전체 크기</TOTAL_SIZE>
<FORMAT>형식</FORMAT>
<FORMAT_PROFILE>형식 프로필</FORMAT_PROFILE>
<NAME>이름</NAME>
</ko>
<pt-BR>
<TOTAL_SIZE>Tamanho total</TOTAL_SIZE>
<FORMAT>Formato</FORMAT>

View File

@@ -4,6 +4,9 @@
<en>
<SAMPLE_TEXT>The quick brown fox jumped over the lazy dog. 1234567890</SAMPLE_TEXT>
</en>
<ko>
<SAMPLE_TEXT>지금 이 순간, 나의 아름다움은 가장 빛난다 - 비너스걸. 1234567890</SAMPLE_TEXT>
</ko>
<zh-CN>
<SAMPLE_TEXT>Innovation in China 中国智造,惠及全球 0123456789</SAMPLE_TEXT>
</zh-CN>

View File

@@ -17,6 +17,8 @@
using Assimp;
using HelixToolkit.Wpf;
using PcdSharp.IO;
using PcdSharp.Struct;
using System;
using System.IO;
using System.Linq;
@@ -54,6 +56,86 @@ public partial class HelixPanel
modelVisual.Content = model;
}
}
if (importerType == ImporterType.Extended_PCD)
{
// Only support PCD files with PointXYZ format
// Not supported for Color or Intensity formats
var xyzCloud = PCDReader.Read<PointXYZ>(_path);
// Create a single geometry for all points to improve performance
var pointCloudGeometry = new MeshGeometry3D();
var positions = new Point3DCollection();
var triangleIndices = new Int32Collection();
// Create material for points
var pointMaterial = new DiffuseMaterial(new SolidColorBrush(Color.FromRgb(0x60, 0x80, 0xFF)));
// Adaptive point size based on total number of points
var totalPoints = xyzCloud.Points.Count;
var pointSize = totalPoints > 10000 ? 0.05d : totalPoints > 1000 ? 0.1d : 0.2d;
// Limit points for performance (show every Nth point if too many)
var step = Math.Max(1, totalPoints / 50000); // Limit to ~50k points max
var vertexIndex = 0;
for (int i = 0; i < xyzCloud.Points.Count; i += step)
{
var point = xyzCloud.Points[i];
// Create a small cube for each point
var halfSize = pointSize / 2;
// Add 8 vertices for the cube
var baseIndex = vertexIndex;
// Front face vertices
positions.Add(new Point3D(point.X - halfSize, point.Y - halfSize, point.Z + halfSize));
positions.Add(new Point3D(point.X + halfSize, point.Y - halfSize, point.Z + halfSize));
positions.Add(new Point3D(point.X + halfSize, point.Y + halfSize, point.Z + halfSize));
positions.Add(new Point3D(point.X - halfSize, point.Y + halfSize, point.Z + halfSize));
// Back face vertices
positions.Add(new Point3D(point.X - halfSize, point.Y - halfSize, point.Z - halfSize));
positions.Add(new Point3D(point.X + halfSize, point.Y - halfSize, point.Z - halfSize));
positions.Add(new Point3D(point.X + halfSize, point.Y + halfSize, point.Z - halfSize));
positions.Add(new Point3D(point.X - halfSize, point.Y + halfSize, point.Z - halfSize));
// Add triangle indices for the cube (12 triangles, 36 indices)
// Front face
triangleIndices.Add(baseIndex + 0); triangleIndices.Add(baseIndex + 1); triangleIndices.Add(baseIndex + 2);
triangleIndices.Add(baseIndex + 0); triangleIndices.Add(baseIndex + 2); triangleIndices.Add(baseIndex + 3);
// Back face
triangleIndices.Add(baseIndex + 4); triangleIndices.Add(baseIndex + 6); triangleIndices.Add(baseIndex + 5);
triangleIndices.Add(baseIndex + 4); triangleIndices.Add(baseIndex + 7); triangleIndices.Add(baseIndex + 6);
// Left face
triangleIndices.Add(baseIndex + 4); triangleIndices.Add(baseIndex + 0); triangleIndices.Add(baseIndex + 3);
triangleIndices.Add(baseIndex + 4); triangleIndices.Add(baseIndex + 3); triangleIndices.Add(baseIndex + 7);
// Right face
triangleIndices.Add(baseIndex + 1); triangleIndices.Add(baseIndex + 5); triangleIndices.Add(baseIndex + 6);
triangleIndices.Add(baseIndex + 1); triangleIndices.Add(baseIndex + 6); triangleIndices.Add(baseIndex + 2);
// Top face
triangleIndices.Add(baseIndex + 3); triangleIndices.Add(baseIndex + 2); triangleIndices.Add(baseIndex + 6);
triangleIndices.Add(baseIndex + 3); triangleIndices.Add(baseIndex + 6); triangleIndices.Add(baseIndex + 7);
// Bottom face
triangleIndices.Add(baseIndex + 4); triangleIndices.Add(baseIndex + 5); triangleIndices.Add(baseIndex + 1);
triangleIndices.Add(baseIndex + 4); triangleIndices.Add(baseIndex + 1); triangleIndices.Add(baseIndex + 0);
vertexIndex += 8;
}
pointCloudGeometry.Positions = positions;
pointCloudGeometry.TriangleIndices = triangleIndices;
// Create the model
var pointCloudModel = new GeometryModel3D
{
Geometry = pointCloudGeometry,
Material = pointMaterial,
BackMaterial = pointMaterial
};
modelVisual.Content = pointCloudModel;
}
else
{
var modelImporter = new ModelImporter();
@@ -90,6 +172,7 @@ file static class Importer
".stl" or ".obj" or ".3ds" or ".lwo" or ".ply" => ImporterType.Default,
".fbx" or ".3mf" or ".glb" or ".gltf" or ".dae" or ".dxf" => ImporterType.Extended,
".pmx" => ImporterType.Extended_MMD,
".pcd" => ImporterType.Extended_PCD,
_ => ImporterType.Unknown,
};
}
@@ -97,8 +180,28 @@ file static class Importer
file enum ImporterType
{
/// <summary>
/// Reserved or unspecified import type
/// </summary>
Unknown,
/// <summary>
/// Default importer supported by HelixToolkit
/// </summary>
Default,
/// <summary>
/// Extended importer supported by Assimp
/// </summary>
Extended,
/// <summary>
/// Extended MMD (MikuMikuDance) importer
/// </summary>
Extended_MMD,
/// <summary>
/// Extended PCD (Point Cloud Data) importer for 3D spatial data
/// </summary>
Extended_PCD,
}

View File

@@ -31,17 +31,20 @@ public class Plugin : IViewer
/// </summary>
private static readonly HashSet<string> WellKnownExtensions = new(
[
// Default
// Default supported by HelixToolkit
".stl", ".obj", ".3ds", ".lwo", ".ply",
// Extended
// Extended supported by Assimp
".fbx", ".3mf", ".blend", ".glb", ".gltf", ".dae",
#if S_DXF
".dxf",
#endif
// Extended_MMD
".pmx",
// TBD: MMD (MikuMikuDance)
//".pmx",
// PCD (Point Cloud Data)
".pcd",
]);
private HelixPanel _hp;

View File

@@ -55,6 +55,7 @@
<PackageReference Include="HelixToolkit" Version="2.27.0" />
<PackageReference Include="HelixToolkit.Wpf" Version="2.27.0" />
<PackageReference Include="AssimpNet" Version="5.0.0-beta1" />
<PackageReference Include="PcdSharp" Version="1.0.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -4,6 +4,9 @@
<en>
<WEBVIEW2_NOT_AVAILABLE>Viewing this file requires Microsoft Edge WebView2 to be installed.&#10;Click here to download it.</WEBVIEW2_NOT_AVAILABLE>
</en>
<ko>
<WEBVIEW2_NOT_AVAILABLE>이 파일을 보려면 Microsoft Edge WebView2를 설치해야 합니다.&#10;여기를 클릭하여 다운로드하세요.</WEBVIEW2_NOT_AVAILABLE>
</ko>
<de>
<WEBVIEW2_NOT_AVAILABLE>Für das Anzeigen dieses Dateityps wird MS Edge WebView2 benötigt.&#10;Hier klicken zum Downloaden.</WEBVIEW2_NOT_AVAILABLE>
</de>
@@ -13,9 +16,6 @@
<ja>
<WEBVIEW2_NOT_AVAILABLE>このファイルを閲覧するには、Microsoft Edge WebView2 がインストールされている必要があります。&#10;ここをクリックしてダウンロードを開始する。</WEBVIEW2_NOT_AVAILABLE>
</ja>
<ko-KR>
<WEBVIEW2_NOT_AVAILABLE>이 파일을 표시하려면 Microsoft Edge WebView2가 필요합니다.&#10;여기를 클릭해서 다운로드 하세요.</WEBVIEW2_NOT_AVAILABLE>
</ko-KR>
<zh-CN>
<WEBVIEW2_NOT_AVAILABLE>查看此文件需要安装 Microsoft Edge WebView2。&#10;点击这里开始下载。</WEBVIEW2_NOT_AVAILABLE>
</zh-CN>

View File

@@ -17,6 +17,7 @@
using LibAPNG;
using QuickLook.Common.ExtensionMethods;
using QuickLook.Common.Helpers;
using QuickLook.Common.Plugin;
using System;
using System.Collections.Generic;
@@ -30,20 +31,28 @@ using System.Windows.Media.Imaging;
namespace QuickLook.Plugin.ImageViewer.AnimatedImage.Providers;
/// <summary>
/// This provider is only for Animated PNG.
/// The others will fall back to another provider.
/// </summary>
internal class APngProvider : AnimationProvider
{
private readonly Frame _baseFrame;
private readonly List<FrameInfo> _frames;
private readonly List<BitmapSource> _renderedFrames;
private int _lastEffectivePreviousPreviousFrameIndex;
private NativeProvider _nativeImageProvider;
private AnimationProvider _fallbackImageProvider;
public APngProvider(Uri path, MetaProvider meta, ContextObject contextObject) : base(path, meta, contextObject)
{
if (!IsAnimatedPng(path.LocalPath))
{
_nativeImageProvider = new NativeProvider(path, meta, contextObject);
Animator = _nativeImageProvider.Animator;
var useNativeProvider = SettingHelper.Get("UseNativeProvider", true, "QuickLook.Plugin.ImageViewer");
_fallbackImageProvider = useNativeProvider ?
new NativeProvider(path, meta, contextObject) :
new ImageMagickProvider(path, meta, contextObject);
Animator = _fallbackImageProvider.Animator;
return;
}
@@ -71,8 +80,8 @@ internal class APngProvider : AnimationProvider
public override Task<BitmapSource> GetThumbnail(Size renderSize)
{
if (_nativeImageProvider != null)
return _nativeImageProvider.GetThumbnail(renderSize);
if (_fallbackImageProvider != null)
return _fallbackImageProvider.GetThumbnail(renderSize);
return new Task<BitmapSource>(() =>
{
@@ -85,8 +94,8 @@ internal class APngProvider : AnimationProvider
public override Task<BitmapSource> GetRenderedFrame(int index)
{
if (_nativeImageProvider != null)
return _nativeImageProvider.GetRenderedFrame(index);
if (_fallbackImageProvider != null)
return _fallbackImageProvider.GetRenderedFrame(index);
if (_renderedFrames[index] != null)
return new Task<BitmapSource>(() => _renderedFrames[index]);
@@ -102,10 +111,10 @@ internal class APngProvider : AnimationProvider
public override void Dispose()
{
if (_nativeImageProvider != null)
if (_fallbackImageProvider != null)
{
_nativeImageProvider.Dispose();
_nativeImageProvider = null;
_fallbackImageProvider.Dispose();
_fallbackImageProvider = null;
return;
}
@@ -214,7 +223,7 @@ internal class APngProvider : AnimationProvider
return false;
}
uint ToUInt32BE(byte[] data)
static uint ToUInt32BE(byte[] data)
{
Array.Reverse(data);
return BitConverter.ToUInt32(data, 0);

View File

@@ -40,11 +40,6 @@ internal class NativeProvider : AnimationProvider
public override Task<BitmapSource> GetThumbnail(Size renderSize)
{
var fullSize = Meta.GetSize();
//var decodeWidth = (int) Math.Round(fullSize.Width *
// Math.Min(renderSize.Width / 2 / fullSize.Width,
// renderSize.Height / 2 / fullSize.Height));
//var decodeHeight = (int) Math.Round(fullSize.Height / fullSize.Width * decodeWidth);
var decodeWidth =
(int)Math.Round(Math.Min(Meta.GetSize().Width, Math.Max(1d, Math.Floor(renderSize.Width))));
var decodeHeight =

View File

@@ -65,8 +65,16 @@ public class Plugin : IViewer
public void Init()
{
// Option of UseColorProfile:
// Default is False (disable color profile conversion)
// Note that enabling this feature will slow down image previewing, especially on large images.
var useColorProfile = SettingHelper.Get("UseColorProfile", false, "QuickLook.Plugin.ImageViewer");
// Option of UseNativeProvider:
// Default is True (disable precise colors and choose faster response)
// Note that disabling this feature may slightly slow down image previewing but you can get precise colors.
var useNativeProvider = SettingHelper.Get("UseNativeProvider", true, "QuickLook.Plugin.ImageViewer");
AnimatedImage.AnimatedImage.Providers.Add(
new KeyValuePair<string[], Type>(
useColorProfile ? [".apng"] : [".apng", ".png"],
@@ -76,11 +84,10 @@ public class Plugin : IViewer
typeof(GifProvider)));
AnimatedImage.AnimatedImage.Providers.Add(
new KeyValuePair<string[], Type>(
useColorProfile ? [] : [".bmp", ".jpg", ".jpeg", ".jfif", ".tif", ".tiff"],
useColorProfile ? [] : (useNativeProvider ? [".bmp", ".jpg", ".jpeg", ".jfif", ".tif", ".tiff"] : []),
typeof(NativeProvider)));
AnimatedImage.AnimatedImage.Providers.Add(
new KeyValuePair<string[], Type>(
useColorProfile ? [] : [".jxr"],
new KeyValuePair<string[], Type>([".jxr"],
typeof(WmpProvider)));
AnimatedImage.AnimatedImage.Providers.Add(
new KeyValuePair<string[], Type>([".icns"],

View File

@@ -59,7 +59,7 @@
<PackageReference Include="QuickLook.ImageGlass.WebP" Version="1.4.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Magick.NET-Q8-AnyCPU" Version="14.6.0">
<PackageReference Include="Magick.NET-Q8-AnyCPU" Version="14.7.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3351.48">

View File

@@ -16,10 +16,12 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using Microsoft.Web.WebView2.Core;
using QuickLook.Common.Helpers;
using QuickLook.Plugin.HtmlViewer;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -78,7 +80,21 @@ public class MarkdownPanel : WebpagePanel
var content = encoding.GetString(bytes);
var template = ReadString("/md2html.html");
var html = template.Replace("{{content}}", content);
// Support automatic RTL for markdown files
bool isRtl = false;
if (CultureInfo.CurrentUICulture.TextInfo.IsRightToLeft)
{
string isSupportRTL = TranslationHelper.Get("IsSupportRTL",
failsafe: bool.TrueString,
domain: Assembly.GetExecutingAssembly().GetName().Name);
if (bool.TrueString.Equals(isSupportRTL, StringComparison.OrdinalIgnoreCase))
isRtl = true;
}
var html = template.Replace("{{content}}", content)
.Replace("{{rtl}}", isRtl ? "rtl" : "ltr");
return html;
}

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html>
<html dir="{{rtl}}">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
@@ -19,6 +19,7 @@
<script src="highlight.js/highlight.min.js"></script>
<script src="js/markdownItAnchor.umd.js"></script>
<script src="js/mermaid.min.js"></script>
</head>
<body class="markdown-body">
<link rel="stylesheet" href="css/github-markdown.css" />
@@ -144,6 +145,61 @@
cursor: ew-resize;
user-select: none;
}
/* RTL support */
html[dir="rtl"] .container {
flex-direction: row-reverse;
}
html[dir="rtl"] #toc {
right: 0;
left: auto;
}
html[dir="rtl"] .table-of-contents a {
border-right: 2px solid transparent;
border-left: none;
margin-right: -1em;
margin-left: 0;
padding-right: calc(1em - 2px);
padding-left: 0;
}
html[dir="rtl"] .table-of-contents .active {
border-right: 2px solid var(--fgColor-accent);
border-left: none;
margin-right: -1em;
margin-left: 0;
padding-right: calc(1em - 2px);
padding-left: 0;
}
/* Mermaid diagram styles */
.mermaid {
display: flex;
justify-content: center;
margin: 1em 0;
}
.mermaid svg {
max-width: 100%;
height: auto;
border-radius: 6px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.mermaid-placeholder {
border: 2px dashed var(--borderColor-default);
background: var(--bgColor-muted);
color: var(--fgColor-muted);
border-radius: 6px;
}
.mermaid-placeholder pre {
background: var(--bgColor-default);
border: 1px solid var(--borderColor-default);
color: var(--fgColor-default);
}
</style>
<textarea id="text-input" style="display: none">{{content}}</textarea>
@@ -160,6 +216,9 @@
typographer: false,
quotes: "“”‘’",
highlight: function (str, lang) {
if (lang === 'mermaid') {
return '<div class="mermaid">' + str + '</div>';
}
if (lang && hljs.getLanguage(lang)) {
try {
return (
@@ -190,6 +249,62 @@
document.getElementById("text-input").value
);
// Initialize Mermaid after markdown rendering
if (window.mermaid) {
// Determine theme based on system preference
const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
mermaid.initialize({
startOnLoad: false,
theme: isDarkMode ? 'dark' : 'default',
securityLevel: 'loose', // Allow HTML in diagrams for enhanced features
fontFamily: 'Segoe UI, Helvetica, Arial, sans-serif',
themeVariables: {
primaryColor: isDarkMode ? '#58a6ff' : '#0969da',
primaryTextColor: isDarkMode ? '#f0f6fc' : '#24292f',
primaryBorderColor: isDarkMode ? '#30363d' : '#d0d7de',
lineColor: isDarkMode ? '#484f58' : '#656d76',
sectionBkgColor: isDarkMode ? '#21262d' : '#f6f8fa',
altSectionBkgColor: isDarkMode ? '#161b22' : '#ffffff',
gridColor: isDarkMode ? '#21262d' : '#f6f8fa',
cScale0: isDarkMode ? '#58a6ff' : '#0969da',
cScale1: isDarkMode ? '#a5f3fc' : '#54aeff',
cScale2: isDarkMode ? '#ff7b72' : '#d1242f'
}
});
// Render Mermaid diagrams with error handling
setTimeout(() => {
try {
mermaid.init(undefined, document.querySelectorAll('.mermaid'));
} catch (error) {
console.warn('Mermaid rendering error:', error);
}
}, 100);
// Listen for theme changes and re-render diagrams
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
if (window.mermaid) {
const newTheme = e.matches ? 'dark' : 'default';
mermaid.initialize({
startOnLoad: false,
theme: newTheme,
securityLevel: 'loose',
fontFamily: 'Segoe UI, Helvetica, Arial, sans-serif'
});
// Re-render all Mermaid diagrams
setTimeout(() => {
try {
mermaid.init(undefined, document.querySelectorAll('.mermaid'));
} catch (error) {
console.warn('Mermaid re-rendering error:', error);
}
}, 100);
}
});
}
/* codes below are adopted from https://codepen.io/jtojnar/full/Juiop */
var ToC = `<nav role='navigation' class='table-of-contents'>
<h2>Contents</h2>
@@ -337,6 +452,21 @@
if (e.code === "Space") {
e.preventDefault();
}
// Support keyboard shortcuts for RTL and LTR text direction
// RTL: Ctrl + RShift
// LTR: Ctrl + LShift
if ((e.ctrlKey || e.metaKey)) {
if (e.shiftKey && e.location === KeyboardEvent.DOM_KEY_LOCATION_RIGHT) {
// Right Shift + Ctrl: RTL
document.documentElement.setAttribute('dir', 'rtl');
e.preventDefault();
} else if (e.shiftKey && e.location === KeyboardEvent.DOM_KEY_LOCATION_LEFT) {
// Left Shift + Ctrl: LTR
document.documentElement.setAttribute('dir', 'ltr');
e.preventDefault();
}
}
});
});
</script>

View File

@@ -7,6 +7,12 @@
<ConfirmDeleteText>Are you sure you want to permanently delete these {0} item(s)?</ConfirmDeleteText>
<RecycleBinSizeText>Total size {0}, {1} items</RecycleBinSizeText>
</en>
<ko>
<RecycleBinEmptyText>휴지통이 비어 있습니다</RecycleBinEmptyText>
<RecycleBinButton>휴지통 비우기</RecycleBinButton>
<ConfirmDeleteText>{0}개 항목을 영구적으로 삭제하시겠습니까?</ConfirmDeleteText>
<RecycleBinSizeText>전체 크기 {0}, {1}개 항목</RecycleBinSizeText>
</ko>
<zh-CN>
<RecycleBinEmptyText>回收站是空的</RecycleBinEmptyText>
<RecycleBinButton>清空回收站</RecycleBinButton>

View File

@@ -8,6 +8,13 @@
<BTN_OpenFile>Open File</BTN_OpenFile>
<BTN_Cancel>Cancel</BTN_Cancel>
</en>
<ko>
<PW_Title>비밀번호 입력</PW_Title>
<Pw_Hint>이 파일은 비밀번호로 보호되어 있습니다. 파일을 열려면 비밀번호를 입력하세요.</Pw_Hint>
<PW_Error>비밀번호 확인</PW_Error>
<BTN_OpenFile>파일 열기</BTN_OpenFile>
<BTN_Cancel>취소</BTN_Cancel>
</ko>
<ja-JP>
<PW_Title>パスワード入力</PW_Title>
<Pw_Hint>このファイルではパスワードで保護されています。ファイルを開くにはパスワードを入力してください。</Pw_Hint>

View File

@@ -6,6 +6,11 @@
<FILE_VERSION>File Version</FILE_VERSION>
<PRODUCT_VERSION>Product Version</PRODUCT_VERSION>
</en>
<ko>
<TOTAL_SIZE>전체 크기</TOTAL_SIZE>
<FILE_VERSION>파일 버전</FILE_VERSION>
<PRODUCT_VERSION>제품 버전</PRODUCT_VERSION>
</ko>
<pt-BR>
<TOTAL_SIZE>Tamanho total</TOTAL_SIZE>
<FILE_VERSION>Versão do arquivo</FILE_VERSION>

View File

@@ -39,9 +39,11 @@
<Editor_Copy>コピー</Editor_Copy>
<Editor_SelectAll>すべて選択</Editor_SelectAll>
</ja-JP>
<ko-KR>
<Editor_FontFamily>Consolas,Malgun Gothic,Batang</Editor_FontFamily>
</ko-KR>
<ko>
<Editor_FontFamily>Consolas,Malgun Gothic,Gulim</Editor_FontFamily>
<Editor_Copy>복사</Editor_Copy>
<Editor_SelectAll>모두 선택</Editor_SelectAll>
</ko>
<nb-NO>
<Editor_FontFamily>Consolas</Editor_FontFamily>
</nb-NO>

View File

@@ -21,6 +21,7 @@ using PureSharpCompress.Readers;
using QuickLook.Common.Helpers;
using QuickLook.Common.Plugin;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Windows;
@@ -30,8 +31,18 @@ namespace QuickLook.Plugin.ThumbnailViewer;
internal static class Handler
{
// List<Pair<formats, type>>
public static List<KeyValuePair<string[], Type>> Providers = [];
public static void Prepare(string path, ContextObject context)
{
// Temporary codes
if (path.EndsWith(".pdn", StringComparison.OrdinalIgnoreCase))
{
new PdnProvider().Prepare(path, context);
return;
}
try
{
using Stream imageData = ViewImage(path);
@@ -47,6 +58,12 @@ internal static class Handler
public static Stream ViewImage(string path)
{
// Temporary codes
if (path.EndsWith(".pdn", StringComparison.OrdinalIgnoreCase))
{
return new PdnProvider().ViewImage(path);
}
try
{
using ZipArchive archive = ZipArchive.Open(path, new());

View File

@@ -34,6 +34,7 @@ public class Plugin : IViewer
".cdr", // CorelDraw
".fig", // Figma
".kra", // Krita
".pdn", // Paint.NET
".pip", ".pix", // Pixso
".sketch", // Sketch
".xd", // AdobeXD
@@ -46,6 +47,8 @@ public class Plugin : IViewer
public void Init()
{
Handler.Providers.Add(new KeyValuePair<string[], Type>(
[".pdn"], typeof(PdnProvider)));
}
public bool CanHandle(string path)

View File

@@ -0,0 +1,93 @@
// Copyright © 2017-2025 QL-Win Contributors
//
// This file is part of QuickLook program.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using QuickLook.Common.Helpers;
using QuickLook.Common.Plugin;
using QuickLook.Plugin.ImageViewer;
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Xml.Linq;
namespace QuickLook.Plugin.ThumbnailViewer;
internal class PdnProvider
{
public void Prepare(string path, ContextObject context)
{
try
{
using Stream imageData = ViewImage(path);
BitmapImage bitmap = imageData.ReadAsBitmapImage();
context.SetPreferredSizeFit(new Size(bitmap.PixelWidth, bitmap.PixelHeight), 0.8d);
}
catch (Exception ex)
{
Debug.WriteLine($"Error reading thumbnail from {path}: {ex.Message}");
context.PreferredSize = new Size { Width = 800, Height = 600 };
}
}
public Stream ViewImage(string path)
{
try
{
using TextReader reader = new StreamReader(path, Encoding.UTF8);
string line = reader.ReadLine();
if (!line.StartsWith("PDN"))
return null;
int indexOfStart = line.IndexOf("<");
int indexOfEnd = line.LastIndexOf(">");
if (indexOfStart < 0 || indexOfEnd < 0)
return null;
string xml = line.Substring(indexOfStart, indexOfEnd - indexOfStart + 1);
// <pdnImage>
// <custom>
// <thumb png="..." />
// </custom>
// </pdnImage>
XDocument doc = XDocument.Parse(xml);
var pngBase64 = doc.Root
?.Element("custom")
?.Element("thumb")
?.Attribute("png")
?.Value;
if (pngBase64 != null)
{
byte[] imageBytes = Convert.FromBase64String(pngBase64);
MemoryStream ms = new(imageBytes);
return ms;
}
return null;
}
catch (Exception e)
{
ProcessHelper.WriteLog(e.ToString());
return null;
}
}
}

View File

@@ -213,9 +213,6 @@ public partial class App : Application
// Initialize MessageBox patching
bool modernMessageBox = SettingHelper.Get("ModernMessageBox", true, "QuickLook");
if (modernMessageBox) MessageBoxPatcher.Initialize();
// Initialize TrayIcon patching
bool modernTrayIcon = SettingHelper.Get("ModernTrayIcon", true, "QuickLook");
if (modernTrayIcon) TrayIconPatcher.Initialize();
// Set initial theme based on system settings
ThemeManager.Apply(OSThemeHelper.AppsUseDarkTheme() ? ApplicationTheme.Dark : ApplicationTheme.Light);
@@ -223,6 +220,9 @@ public partial class App : Application
ThemeManager.Apply(OSThemeHelper.AppsUseDarkTheme() ? ApplicationTheme.Dark : ApplicationTheme.Light);
UxTheme.ApplyPreferredAppMode();
// Initialize TrayIcon
_ = TrayIconManager.GetInstance();
base.OnStartup(e);
}
@@ -240,7 +240,7 @@ public partial class App : Application
CheckUpdate();
RunListener(e);
// first instance: run and preview this file
// First instance: run and preview this file
if (e.Args.Any() && (Directory.Exists(e.Args.First()) || File.Exists(e.Args.First())))
PipeServerManager.SendMessage(PipeMessages.Toggle, e.Args.First());
}
@@ -318,12 +318,12 @@ public partial class App : Application
if (isFirst)
return true;
// second instance: preview this file
// Second instance: preview this file
if (args.Any() && (Directory.Exists(args.First()) || File.Exists(args.First())))
{
PipeServerManager.SendMessage(PipeMessages.Toggle, args.First(), [.. args.Skip(1)]);
}
// second instance: duplicate
// Second instance: duplicate
else
MessageBox.Show(TranslationHelper.Get("APP_SECOND_TEXT"), TranslationHelper.Get("APP_SECOND"),
MessageBoxButton.OK, MessageBoxImage.Information);

View File

@@ -1,298 +0,0 @@
// Copyright © 2017-2025 QL-Win Contributors
//
// This file is part of QuickLook program.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Windows.Forms;
namespace QuickLook.Helpers;
public static class TrayIconPatcher
{
private static readonly Harmony Harmony = new("com.quicklook.trayicon.patch");
public static void Initialize()
{
var targetMethod = typeof(NotifyIcon).GetMethod("ShowContextMenu", BindingFlags.NonPublic | BindingFlags.Instance);
if (targetMethod != null)
{
_ = Harmony.Patch(targetMethod, transpiler: new HarmonyMethod(typeof(TrayIconPatcher).GetMethod(nameof(ShowContextMenuTranspiler))));
}
}
public static void ShowContextMenu(this NotifyIcon icon)
{
var targetMethod = typeof(NotifyIcon).GetMethod("ShowContextMenu", BindingFlags.NonPublic | BindingFlags.Instance);
targetMethod?.Invoke(icon, []);
}
/// <summary>
/// We need to change the NotifyIcon.ShowContextMenu to use TPM_RIGHTBUTTON instead of TPM_VERTICAL.
/// private void ShowContextMenu()
/// {
/// if (contextMenu != null || contextMenuStrip != null)
/// {
/// NativeMethods.POINT pOINT = new NativeMethods.POINT();
/// UnsafeNativeMethods.GetCursorPos(pOINT);
/// UnsafeNativeMethods.SetForegroundWindow(new HandleRef(window, window.Handle));
/// if (contextMenu != null)
/// {
/// contextMenu.OnPopup(EventArgs.Empty);
/// SafeNativeMethods.TrackPopupMenuEx(new HandleRef(contextMenu, contextMenu.Handle), 72, pOINT.x, pOINT.y, new HandleRef(window, window.Handle), null);
/// UnsafeNativeMethods.PostMessage(new HandleRef(window, window.Handle), 0, IntPtr.Zero, IntPtr.Zero);
/// }
/// else if (contextMenuStrip != null)
/// {
/// contextMenuStrip.ShowInTaskbar(pOINT.x, pOINT.y);
/// }
/// }
/// }
/// ---
/// Opcode: ldarg.0, Operand:
/// Opcode: ldfld, Operand: System.Windows.Forms.ContextMenu contextMenu
/// Opcode: brtrue.s, Operand: System.Reflection.Emit.Label
/// Opcode: ldarg.0, Operand:
/// Opcode: ldfld, Operand: System.Windows.Forms.ContextMenuStrip contextMenuStrip
/// Opcode: brfalse, Operand: System.Reflection.Emit.Label
/// Opcode: newobj, Operand: Void.ctor()
/// Opcode: stloc.0, Operand:
/// Opcode: ldloc.0, Operand:
/// Opcode: call, Operand: Boolean GetCursorPos(POINT)
/// Opcode: pop, Operand:
/// Opcode: ldarg.0, Operand:
/// Opcode: ldfld, Operand: NotifyIconNativeWindow window
/// Opcode: ldarg.0, Operand:
/// Opcode: ldfld, Operand: NotifyIconNativeWindow window
/// Opcode: callvirt, Operand: IntPtr get_Handle()
/// Opcode: newobj, Operand: Void.ctor(System.Object, IntPtr)
/// Opcode: call, Operand: Boolean SetForegroundWindow(System.Runtime.InteropServices.HandleRef)
/// Opcode: pop, Operand:
/// Opcode: ldarg.0, Operand:
/// Opcode: ldfld, Operand: System.Windows.Forms.ContextMenu contextMenu
/// Opcode: brfalse.s, Operand: System.Reflection.Emit.Label
/// Opcode: ldarg.0, Operand:
/// Opcode: ldfld, Operand: System.Windows.Forms.ContextMenu contextMenu
/// Opcode: ldsfld, Operand: System.EventArgs Empty
/// Opcode: callvirt, Operand: Void OnPopup(System.EventArgs)
/// Opcode: ldarg.0, Operand:
/// Opcode: ldfld, Operand: System.Windows.Forms.ContextMenu contextMenu
/// Opcode: ldarg.0, Operand:
/// Opcode: ldfld, Operand: System.Windows.Forms.ContextMenu contextMenu
/// Opcode: callvirt, Operand: IntPtr get_Handle()
/// Opcode: newobj, Operand: Void.ctor(System.Object, IntPtr)
/// Opcode: ldc.i4.s, Operand: 72
/// Opcode: ldloc.0, Operand:
/// Opcode: ldfld, Operand: Int32 x
/// Opcode: ldloc.0, Operand:
/// Opcode: ldfld, Operand: Int32 y
/// Opcode: ldarg.0, Operand:
/// Opcode: ldfld, Operand: NotifyIconNativeWindow window
/// Opcode: ldarg.0, Operand:
/// Opcode: ldfld, Operand: NotifyIconNativeWindow window
/// Opcode: callvirt, Operand: IntPtr get_Handle()
/// Opcode: newobj, Operand: Void.ctor(System.Object, IntPtr)
/// Opcode: ldnull, Operand:
/// Opcode: call, Operand: Boolean TrackPopupMenuEx(System.Runtime.InteropServices.HandleRef, Int32, Int32, Int32, System.Runtime.InteropServices.HandleRef, TPMPARAMS)
/// Opcode: pop, Operand:
/// Opcode: ldarg.0, Operand:
/// Opcode: ldfld, Operand: NotifyIconNativeWindow window
/// Opcode: ldarg.0, Operand:
/// Opcode: ldfld, Operand: NotifyIconNativeWindow window
/// Opcode: callvirt, Operand: IntPtr get_Handle()
/// Opcode: newobj, Operand: Void.ctor(System.Object, IntPtr)
/// Opcode: ldc.i4.0, Operand:
/// Opcode: ldsfld, Operand: IntPtr Zero
/// Opcode: ldsfld, Operand: IntPtr Zero
/// Opcode: call, Operand: Boolean PostMessage(System.Runtime.InteropServices.HandleRef, Int32, IntPtr, IntPtr)
/// Opcode: pop, Operand:
/// Opcode: ret, Operand:
/// Opcode: ldarg.0, Operand:
/// Opcode: ldfld, Operand: System.Windows.Forms.ContextMenuStrip contextMenuStrip
/// Opcode: brfalse.s, Operand: System.Reflection.Emit.Label
/// Opcode: ldarg.0, Operand:
/// Opcode: ldfld, Operand: System.Windows.Forms.ContextMenuStrip contextMenuStrip
/// Opcode: ldloc.0, Operand:
/// Opcode: ldfld, Operand: Int32 x
/// Opcode: ldloc.0, Operand:
/// Opcode: ldfld, Operand: Int32 y
/// Opcode: callvirt, Operand: Void ShowInTaskbar(Int32, Int32)
/// Opcode: ret, Operand:
/// </summary>
/// <param name="instructions"></param>
/// <returns></returns>
[HarmonyTranspiler]
public static IEnumerable<CodeInstruction> ShowContextMenuTranspiler(IEnumerable<CodeInstruction> instructions)
{
try
{
var codes = instructions.ToArray();
if (Debugger.IsAttached)
{
// Test code to print the IL codes
foreach (var code in codes)
{
Debug.WriteLine($"Opcode: {code.opcode}, Operand: {code.operand}");
}
}
// How to change source code for proxy:
// from SafeNativeMethods.TrackPopupMenuEx(new HandleRef(contextMenu, contextMenu.Handle), 72, pOINT.x, pOINT.y, new HandleRef(window, window.Handle), null);
// to SafeNativeMethods.TrackPopupMenuEx(new HandleRef(contextMenu, contextMenu.Handle), 66, pOINT.x, pOINT.y, new HandleRef(window, window.Handle), null);
// [TrackPopupMenuEx] uFlags: A set of flags that determine how the menu behaves.
// The number 72 in binary is 0100 1000. Breaking it down:
// * TPM_VERTICAL(0x0040)
// * TPM_RIGHTALIGN(0x0008)
// The number 64 in binary is 0100 0000. Breaking it down:
// * TPM_VERTICAL(0x0040)
// * TPM_LEFTALIGN(0x0000)
const sbyte sourceFlag = (sbyte)(TrackPopupMenuFlags.TPM_VERTICAL | TrackPopupMenuFlags.TPM_RIGHTALIGN);
const sbyte targetFlag = (sbyte)(TrackPopupMenuFlags.TPM_VERTICAL | TrackPopupMenuFlags.TPM_LEFTALIGN);
for (int i = 0; i < codes.Length; i++)
{
if (codes[i].opcode == OpCodes.Ldc_I4_S && (sbyte)codes[i].operand == sourceFlag)
{
codes[i].operand = targetFlag;
break;
}
}
return codes;
}
catch
{
// No fallback needed in this case
}
return instructions;
}
/// <summary>
/// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-trackpopupmenuex
/// </summary>
[Flags]
private enum TrackPopupMenuFlags : uint
{
/// <summary>
/// The user can select menu items with only the left mouse button.
/// </summary>
TPM_LEFTBUTTON = 0x0000,
/// <summary>
/// The user can select menu items with both the left and right mouse buttons.
/// </summary>
TPM_RIGHTBUTTON = 0x0002,
/// <summary>
/// Positions the shortcut menu so that its left side is aligned with the coordinate specified by the x parameter.
/// </summary>
TPM_LEFTALIGN = 0x0000,
/// <summary>
/// Centers the shortcut menu horizontally relative to the coordinate specified by the x parameter.
/// </summary>
TPM_CENTERALIGN = 0x0004,
/// <summary>
/// Positions the shortcut menu so that its right side is aligned with the coordinate specified by the x parameter.
/// </summary>
TPM_RIGHTALIGN = 0x0008,
/// <summary>
/// Positions the shortcut menu so that its top side is aligned with the coordinate specified by the y parameter.
/// </summary>
TPM_TOPALIGN = 0x0000,
/// <summary>
/// Centers the shortcut menu vertically relative to the coordinate specified by the y parameter.
/// </summary>
TPM_VCENTERALIGN = 0x0010,
/// <summary>
/// Positions the shortcut menu so that its bottom side is aligned with the coordinate specified by the y parameter.
/// </summary>
TPM_BOTTOMALIGN = 0x0020,
/// <summary>
/// TPM_HORIZONTAL
/// </summary>
TPM_HORIZONTAL = 0x0000,
/// <summary>
/// TPM_VERTICAL
/// </summary>
TPM_VERTICAL = 0x0040,
/// <summary>
/// The function does not send notification messages when the user clicks a menu item.
/// </summary>
TPM_NONOTIFY = 0x0080,
/// <summary>
/// The function returns the menu item identifier of the user's selection in the return value.
/// </summary>
TPM_RETURNCMD = 0x0100,
/// <summary>
/// TPM_RECURSE
/// </summary>
TPM_RECURSE = 0x0001,
/// <summary>
/// Animates the menu from left to right.
/// </summary>
TPM_HORPOSANIMATION = 0x0400,
/// <summary>
/// Animates the menu from right to left.
/// </summary>
TPM_HORNEGANIMATION = 0x0800,
/// <summary>
/// Animates the menu from top to bottom.
/// </summary>
TPM_VERPOSANIMATION = 0x1000,
/// <summary>
/// Animates the menu from bottom to top.
/// </summary>
TPM_VERNEGANIMATION = 0x2000,
/// <summary>
/// Displays menu without animation.
/// </summary>
TPM_NOANIMATION = 0x4000,
/// <summary>
/// TPM_LAYOUTRTL
/// </summary>
TPM_LAYOUTRTL = 0x8000,
/// <summary>
/// TPM_WORKAREA
/// </summary>
TPM_WORKAREA = 0x10000,
}
}

View File

@@ -102,7 +102,7 @@
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="WPF-UI.Violeta" Version="4.0.3">
<PackageReference Include="WPF-UI.Violeta" Version="4.0.3.2">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Lib.Harmony" Version="2.3.6">

View File

@@ -109,15 +109,20 @@
<InfoPanel_Files>{0} berkas</InfoPanel_Files>
<InfoPanel_FolderAndFile>({0} dan {1})</InfoPanel_FolderAndFile>
</id-ID>
<ko-KR>
<ko>
<UI_FontFamily>Segoe UI</UI_FontFamily>
<APP_START>QuickLook이 백그라운드에서 실행 중입니다.</APP_START>
<APP_SECOND>QuickLook이 이미 실행 중입니다</APP_SECOND>
<APP_SECOND_TEXT>QuickLook은 지원되는 파일 형식에 한해 파일 선택 후 스페이스바를 사용하여 미리보기를 할 수 있습니다.</APP_SECOND_TEXT>
<APP_SECOND_TEXT>QuickLook은 강조 표시된 상태에서 스페이스바를 눌러 특정 유형의 파일을 빠르게 미리 볼 수 있게 해줍니다.</APP_SECOND_TEXT>
<APP_PATH_NOT_WRITABLE>데이터 경로 "{0}"에 쓸 수 없습니다. 충분한 권한이 있는지 확인하세요.</APP_PATH_NOT_WRITABLE>
<MW_StayTop>맨 위에 유지</MW_StayTop>
<MW_BrowseFolder>{0} 열기</MW_BrowseFolder>
<MW_PreventClosing>닫힘 방지</MW_PreventClosing>
<MW_BrowseFolder>브라우저 {0}</MW_BrowseFolder>
<MW_Open>{0} 열기</MW_Open>
<MW_OpenWith>다음으로 열기 : {0}</MW_OpenWith>
<MW_OpenWith>{0}(으)로 열기</MW_OpenWith>
<MW_Run>{0} 실행</MW_Run>
<MW_Share>공유</MW_Share>
<Icon_RunAtStartup>시작 시 실행(&amp;S)</Icon_RunAtStartup>
<Icon_ToolTip>QuickLook v{0}</Icon_ToolTip>
<Icon_CheckUpdate>업데이트 확인(&amp;U)</Icon_CheckUpdate>
@@ -125,17 +130,18 @@
<Icon_OpenDataFolder>데이터 폴더 열기</Icon_OpenDataFolder>
<Icon_Restart>다시 시작(&amp;R)</Icon_Restart>
<Icon_Quit>종료(&amp;Q)</Icon_Quit>
<Update_NoUpdate>최신 버전을 사용 중입니다.</Update_NoUpdate>
<Update_Found>QuickLook {0} 버전이 출시되었습니다. 다운로드 페이지로 가려면 이곳을 클릭하십시오.</Update_Found>
<Update_Error>업데이트 확인 중 오류가 발생하였습니다: {0}</Update_Error>
<InfoPanel_LastModified>수정한 날짜: {0}</InfoPanel_LastModified>
<InfoPanel_DriveSize>용량: {0}, 사용 가능한 공간: {1}</InfoPanel_DriveSize>
<InfoPanel_Folder>폴더 {0}개</InfoPanel_Folder>
<InfoPanel_Folders>폴더 {0}개</InfoPanel_Folders>
<InfoPanel_File>파일 {0}개</InfoPanel_File>
<InfoPanel_Files>파일 {0}개</InfoPanel_Files>
<InfoPanel_FolderAndFile>({0}, {1})</InfoPanel_FolderAndFile>
</ko-KR>
<Update_NoUpdate>현재 최신 버전을 사용하고 있습니다.</Update_NoUpdate>
<Update_Found>QuickLook {0}이 출시되었습니다. 여기를 클릭하여 다운로드 페이지를 여세요.</Update_Found>
<Update_Error>업데이트 확인하는 중 오류가 발생습니다: {0}</Update_Error>
<InfoPanel_LastModified>{0}에 마지막으로 수정됨</InfoPanel_LastModified>
<InfoPanel_DriveSize>사용 가능한 용량 {0}, {1}</InfoPanel_DriveSize>
<InfoPanel_Folder>{0}개 폴더</InfoPanel_Folder>
<InfoPanel_Folders>{0}개 폴더</InfoPanel_Folders>
<InfoPanel_File>{0}개 파일</InfoPanel_File>
<InfoPanel_Files>{0}개 파일</InfoPanel_Files>
<InfoPanel_FolderAndFile>({0} {1})</InfoPanel_FolderAndFile>
<InfoPanel_CantPreventClosing>"닫힘 방지" 취소는 지원되지 않습니다</InfoPanel_CantPreventClosing>
</ko>
<ca>
<UI_FontFamily>Segoe UI</UI_FontFamily>
<APP_START>El QuickLook s'està executant en segon pla.</APP_START>
@@ -412,9 +418,9 @@
<MW_BrowseFolder>Przeglądaj {0}</MW_BrowseFolder>
<MW_StayTop>Przypnij do ekranu</MW_StayTop>
<MW_PreventClosing>Zablokuj zamykanie</MW_PreventClosing>
<MW_BrowseFolder>Przeglądaj {0}</MW_BrowseFolder>
<MW_Open>Otwórz {0}</MW_Open>
<MW_OpenWith>Otwórz w {0}</MW_OpenWith>
<MW_OpenWithMenu>Otwórz za pomocą...</MW_OpenWithMenu>
<MW_Run>Uruchom {0}</MW_Run>
<MW_Share>Udostępnij</MW_Share>
<MW_Reload>Przeładuj</MW_Reload>
@@ -435,6 +441,7 @@
<InfoPanel_File>{0} plik</InfoPanel_File>
<InfoPanel_Files>{0} plików</InfoPanel_Files>
<InfoPanel_FolderAndFile>({0} i {1})</InfoPanel_FolderAndFile>
<InfoPanel_CantPreventClosing>Anulowanie dla funkcji blokady zamykania okna nie jest wspierane</InfoPanel_CantPreventClosing>
</pl>
<pt-BR>
<UI_FontFamily>Segoe UI</UI_FontFamily>

View File

@@ -15,79 +15,91 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using QuickLook.Common.Commands;
using QuickLook.Common.Helpers;
using QuickLook.Helpers;
using QuickLook.Properties;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using Wpf.Ui.Violeta.Win32;
using ToolTipIcon = Wpf.Ui.Violeta.Win32.ToolTipIcon;
namespace QuickLook;
internal class TrayIconManager : IDisposable
internal partial class TrayIconManager : IDisposable
{
private static TrayIconManager _instance;
private readonly NotifyIcon _icon;
private readonly TrayIconHost _icon;
private readonly MenuItem _itemAutorun =
new(TranslationHelper.Get("Icon_RunAtStartup"),
(sender, e) =>
private readonly TrayMenuItem _itemAutorun =
new()
{
Header = TranslationHelper.Get("Icon_RunAtStartup"),
Command = new RelayCommand(() =>
{
if (AutoStartupHelper.IsAutorun())
AutoStartupHelper.RemoveAutorunShortcut();
else
AutoStartupHelper.CreateAutorunShortcut();
})
{ Enabled = !App.IsUWP };
}),
IsEnabled = !App.IsUWP,
};
private TrayIconManager()
{
_icon = new NotifyIcon
_icon = new TrayIconHost
{
Text = string.Format(TranslationHelper.Get("Icon_ToolTip"),
ToolTipText = string.Format(TranslationHelper.Get("Icon_ToolTip"),
Application.ProductVersion),
Icon = GetTrayIconByDPI(),
ContextMenu = new ContextMenu(
Menu =
[
new MenuItem($"v{Application.ProductVersion}{(App.IsUWP ? " (UWP)" : string.Empty)}") {Enabled = false},
new MenuItem("-"),
new MenuItem(TranslationHelper.Get("Icon_CheckUpdate"), (_, _) => Updater.CheckForUpdates()),
new MenuItem(TranslationHelper.Get("Icon_GetPlugin"),
(_, _) => Process.Start("https://github.com/QL-Win/QuickLook/wiki/Available-Plugins")),
new MenuItem(TranslationHelper.Get("Icon_OpenDataFolder"), (_, _) => Process.Start("explorer.exe", SettingHelper.LocalDataPath)),
new TrayMenuItem()
{
Header = $"v{Application.ProductVersion}{(App.IsUWP ? " (UWP)" : string.Empty)}",
IsEnabled = false,
},
new TraySeparator(),
new TrayMenuItem()
{
Header = TranslationHelper.Get("Icon_CheckUpdate"),
Command = new RelayCommand(() => Updater.CheckForUpdates()),
},
new TrayMenuItem()
{
Header = TranslationHelper.Get("Icon_GetPlugin"),
Command = new RelayCommand(() => Process.Start("https://github.com/QL-Win/QuickLook/wiki/Available-Plugins")),
},
new TrayMenuItem()
{
Header = TranslationHelper.Get("Icon_OpenDataFolder"),
Command = new RelayCommand(() => Process.Start("explorer.exe", SettingHelper.LocalDataPath)),
},
_itemAutorun,
new MenuItem(TranslationHelper.Get("Icon_Restart"), (_, _) => Restart(forced: true)),
new MenuItem(TranslationHelper.Get("Icon_Quit"),
(_, _) => System.Windows.Application.Current.Shutdown())
]),
Visible = SettingHelper.Get("ShowTrayIcon", true)
new TrayMenuItem()
{
Header = TranslationHelper.Get("Icon_Restart"),
Command = new RelayCommand(() => Restart(forced: true)),
},
new TrayMenuItem()
{
Header = TranslationHelper.Get("Icon_Quit"),
Command = new RelayCommand(System.Windows.Application.Current.Shutdown),
}
],
IsVisible = SettingHelper.Get("ShowTrayIcon", true)
};
_icon.ContextMenu.Popup += (sender, e) => { _itemAutorun.Checked = AutoStartupHelper.IsAutorun(); };
// Readjust the display position of ContextMenu
if (SettingHelper.Get("ModernTrayIcon", true, "QuickLook"))
{
_icon.MouseDown += (_, e) =>
{
if (e.Button == MouseButtons.Right)
{
// Call ShowContextMenu here will be later than the native call,
// so here can readjust the ContextMenu position.
// You can check the source code to determine the behavior.
_icon.ShowContextMenu();
}
};
}
_icon.RightDown += (sender, e) => { _itemAutorun.IsChecked = AutoStartupHelper.IsAutorun(); };
}
public void Dispose()
{
_icon.Visible = false;
_icon.IsVisible = false;
}
public void Restart(string fileName = null, string dir = null, string args = null, int? exitCode = null, bool forced = false)
@@ -118,16 +130,16 @@ internal class TrayIconManager : IDisposable
Environment.Exit(exitCode ?? 'r' + 'e' + 's' + 't' + 'a' + 'r' + 't');
}
private Icon GetTrayIconByDPI()
private nint GetTrayIconByDPI()
{
var scale = DisplayDeviceHelper.GetCurrentScaleFactor().Vertical;
if (!App.IsWin10)
return scale > 1 ? Resources.app : Resources.app_16;
return scale > 1 ? Resources.app.Handle : Resources.app_16.Handle;
return OSThemeHelper.SystemUsesDarkTheme()
? (scale > 1 ? Resources.app_white : Resources.app_white_16)
: (scale > 1 ? Resources.app_black : Resources.app_black_16);
? (scale > 1 ? Resources.app_white.Handle : Resources.app_white_16.Handle)
: (scale > 1 ? Resources.app_black.Handle : Resources.app_black_16.Handle);
}
public static void ShowNotification(string title, string content, bool isError = false, int timeout = 5000,