mirror of
https://github.com/microsoft/PowerToys.git
synced 2024-12-19 22:37:58 +08:00
Merge remote-tracking branch 'origin/main' into dev/snickler/net8-upgrade
This commit is contained in:
commit
5867b12d67
2
.github/actions/spell-check/allow/names.txt
vendored
2
.github/actions/spell-check/allow/names.txt
vendored
@ -35,6 +35,7 @@ mshtang
|
||||
Myrvold
|
||||
naveensrinivasan
|
||||
nVidia
|
||||
phoboslab
|
||||
Ponten
|
||||
Pooja
|
||||
robmen
|
||||
@ -44,6 +45,7 @@ skycommand
|
||||
snickler
|
||||
sinclairinat
|
||||
streamjsonrpc
|
||||
Szablewski
|
||||
tilovell
|
||||
TheJoeFin
|
||||
Triet
|
||||
|
3
.github/actions/spell-check/expect.txt
vendored
3
.github/actions/spell-check/expect.txt
vendored
@ -1014,6 +1014,7 @@ lstrlen
|
||||
LTRB
|
||||
LTRREADING
|
||||
luid
|
||||
LUMA
|
||||
lusrmgr
|
||||
LVal
|
||||
LWA
|
||||
@ -1496,6 +1497,7 @@ qianlifeng
|
||||
qit
|
||||
QITAB
|
||||
QITABENT
|
||||
qoi
|
||||
qps
|
||||
Quarternary
|
||||
QUERYENDSESSION
|
||||
@ -2250,6 +2252,7 @@ xsi
|
||||
XStr
|
||||
XUP
|
||||
XVIRTUALSCREEN
|
||||
xxxxxx
|
||||
YAxis
|
||||
ycv
|
||||
Yeet
|
||||
|
28
NOTICE.md
28
NOTICE.md
@ -67,6 +67,34 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
### The Quite OK Image Format reference decoder
|
||||
|
||||
**Source**: https://github.com/phoboslab/qoi
|
||||
|
||||
**Note**: [@pedrolamas](https://github.com/pedrolamas) translated and adapted the reference decoder code to C# that is in PowerToys from the original C++ implementation.
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Dominic Szablewski
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
## Utility: ImageResizer
|
||||
|
||||
### Brice Lams's Image Resizer License
|
||||
|
@ -104,7 +104,6 @@ IFACEMETHODIMP ExplorerCommand::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataOb
|
||||
if (pdtobj)
|
||||
{
|
||||
m_data_obj = pdtobj;
|
||||
m_data_obj->AddRef();
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
@ -242,10 +241,6 @@ ExplorerCommand::ExplorerCommand()
|
||||
|
||||
ExplorerCommand::~ExplorerCommand()
|
||||
{
|
||||
if (m_data_obj)
|
||||
{
|
||||
m_data_obj->Release();
|
||||
}
|
||||
--globals::ref_count;
|
||||
}
|
||||
|
||||
|
@ -90,7 +90,19 @@ namespace MouseWithoutBorders
|
||||
lock (ThreadsLock)
|
||||
{
|
||||
#pragma warning disable 618 // Temporary
|
||||
threads.Where(t => t.IsAlive && t.ManagedThreadId != threadId).ToList().ForEach(t => t.Suspend());
|
||||
threads.Where(t => t.IsAlive && t.ManagedThreadId != threadId).ToList().ForEach(
|
||||
t =>
|
||||
{
|
||||
try
|
||||
{
|
||||
t.Suspend();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// This method is suspending every thread so that it can kill the process right after restarting.
|
||||
// Makes no sense to crash on a thread suspension fail, since we're killing the process afterwards, anyway.
|
||||
}
|
||||
});
|
||||
#pragma warning restore 618
|
||||
}
|
||||
}
|
||||
|
@ -48,6 +48,13 @@ int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto process = GetCurrentProcess();
|
||||
if (!SetPriorityClass(process, NORMAL_PRIORITY_CLASS))
|
||||
{
|
||||
std::wstring err = get_last_error_or_default(GetLastError());
|
||||
Logger::warn(L"Failed to set priority to FancyZones: {}", err);
|
||||
}
|
||||
|
||||
std::wstring pid = std::wstring(lpCmdLine);
|
||||
if (!pid.empty())
|
||||
{
|
||||
|
@ -71,6 +71,11 @@ public:
|
||||
PostMessageW(m_window, WM_PRIV_LOCATIONCHANGE, NULL, NULL);
|
||||
})
|
||||
{
|
||||
if (!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL))
|
||||
{
|
||||
Logger::warn("Failed to set main thread priority");
|
||||
}
|
||||
|
||||
this->disableModuleCallback = std::move(disableModuleCallbackFunction);
|
||||
|
||||
FancyZonesSettings::instance().LoadSettings();
|
||||
|
@ -171,6 +171,11 @@ namespace PowerLauncher
|
||||
_settings.GenerateThumbnailsFromFiles = overloadSettings.Properties.GenerateThumbnailsFromFiles;
|
||||
}
|
||||
|
||||
if (_settings.ShouldUsePinyin != overloadSettings.Properties.UsePinyin)
|
||||
{
|
||||
_settings.ShouldUsePinyin = overloadSettings.Properties.UsePinyin;
|
||||
}
|
||||
|
||||
retry = false;
|
||||
}
|
||||
|
||||
|
@ -2,14 +2,6 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
using Common;
|
||||
using Common.Utilities;
|
||||
using Microsoft.PowerToys.PreviewHandler.Gcode.Telemetry.Events;
|
||||
@ -67,7 +59,9 @@ namespace Microsoft.PowerToys.PreviewHandler.Gcode
|
||||
|
||||
using (var reader = new StreamReader(fs))
|
||||
{
|
||||
thumbnail = GetThumbnail(reader);
|
||||
var gcodeThumbnail = GcodeHelper.GetBestThumbnail(reader);
|
||||
|
||||
thumbnail = gcodeThumbnail?.GetBitmap();
|
||||
}
|
||||
|
||||
_infoBarAdded = false;
|
||||
@ -84,7 +78,13 @@ namespace Microsoft.PowerToys.PreviewHandler.Gcode
|
||||
|
||||
Resize += FormResized;
|
||||
base.DoPreview(fs);
|
||||
PowerToysTelemetry.Log.WriteEvent(new GcodeFilePreviewed());
|
||||
try
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new GcodeFilePreviewed());
|
||||
}
|
||||
catch
|
||||
{ // Should not crash if sending telemetry is failing. Ignore the exception.
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -92,66 +92,6 @@ namespace Microsoft.PowerToys.PreviewHandler.Gcode
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads the G-code content searching for thumbnails and returns the largest.
|
||||
/// </summary>
|
||||
/// <param name="reader">The TextReader instance for the G-code content.</param>
|
||||
/// <returns>A thumbnail extracted from the G-code content.</returns>
|
||||
public static Bitmap GetThumbnail(TextReader reader)
|
||||
{
|
||||
if (reader == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Bitmap thumbnail = null;
|
||||
|
||||
var bitmapBase64 = GetBase64Thumbnails(reader)
|
||||
.OrderByDescending(x => x.Length)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (!string.IsNullOrEmpty(bitmapBase64))
|
||||
{
|
||||
var bitmapBytes = Convert.FromBase64String(bitmapBase64);
|
||||
|
||||
thumbnail = new Bitmap(new MemoryStream(bitmapBytes));
|
||||
}
|
||||
|
||||
return thumbnail;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all thumbnails in base64 format found on the G-code data.
|
||||
/// </summary>
|
||||
/// <param name="reader">The TextReader instance for the G-code content.</param>
|
||||
/// <returns>An enumeration of thumbnails in base64 format found on the G-code.</returns>
|
||||
private static IEnumerable<string> GetBase64Thumbnails(TextReader reader)
|
||||
{
|
||||
string line;
|
||||
StringBuilder capturedText = null;
|
||||
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
if (line.StartsWith("; thumbnail begin", StringComparison.InvariantCulture))
|
||||
{
|
||||
capturedText = new StringBuilder();
|
||||
}
|
||||
else if (line == "; thumbnail end")
|
||||
{
|
||||
if (capturedText != null)
|
||||
{
|
||||
yield return capturedText.ToString();
|
||||
|
||||
capturedText = null;
|
||||
}
|
||||
}
|
||||
else if (capturedText != null)
|
||||
{
|
||||
capturedText.Append(line[2..]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when RichtextBox is resized.
|
||||
/// </summary>
|
||||
@ -214,7 +154,14 @@ namespace Microsoft.PowerToys.PreviewHandler.Gcode
|
||||
/// <param name="dataSource">Stream reference to access source file.</param>
|
||||
private void PreviewError<T>(Exception exception, T dataSource)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new GcodeFilePreviewError { Message = exception.Message });
|
||||
try
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new GcodeFilePreviewError { Message = exception.Message });
|
||||
}
|
||||
catch
|
||||
{ // Should not crash if sending telemetry is failing. Ignore the exception.
|
||||
}
|
||||
|
||||
Controls.Clear();
|
||||
_infoBarAdded = true;
|
||||
AddTextBoxControl(Properties.Resource.GcodeNotPreviewedError);
|
||||
|
@ -2,7 +2,7 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
using System.Drawing.Drawing2D;
|
||||
using System.Text;
|
||||
using Common.Utilities;
|
||||
|
||||
namespace Microsoft.PowerToys.ThumbnailHandler.Gcode
|
||||
{
|
||||
@ -45,64 +45,23 @@ namespace Microsoft.PowerToys.ThumbnailHandler.Gcode
|
||||
return null;
|
||||
}
|
||||
|
||||
Bitmap thumbnail = null;
|
||||
var gcodeThumbnail = GcodeHelper.GetBestThumbnail(reader);
|
||||
|
||||
var bitmapBase64 = GetBase64Thumbnails(reader)
|
||||
.OrderByDescending(x => x.Length)
|
||||
.FirstOrDefault();
|
||||
var thumbnail = gcodeThumbnail?.GetBitmap();
|
||||
|
||||
if (!string.IsNullOrEmpty(bitmapBase64))
|
||||
if (thumbnail != null && thumbnail.Width != cx && thumbnail.Height != cx)
|
||||
{
|
||||
var bitmapBytes = Convert.FromBase64String(bitmapBase64);
|
||||
|
||||
thumbnail = new Bitmap(new MemoryStream(bitmapBytes));
|
||||
|
||||
if (thumbnail.Width != cx && thumbnail.Height != cx)
|
||||
{
|
||||
// We are not the appropriate size for caller. Resize now while
|
||||
// respecting the aspect ratio.
|
||||
float scale = Math.Min((float)cx / thumbnail.Width, (float)cx / thumbnail.Height);
|
||||
int scaleWidth = (int)(thumbnail.Width * scale);
|
||||
int scaleHeight = (int)(thumbnail.Height * scale);
|
||||
thumbnail = ResizeImage(thumbnail, scaleWidth, scaleHeight);
|
||||
}
|
||||
// We are not the appropriate size for caller. Resize now while
|
||||
// respecting the aspect ratio.
|
||||
float scale = Math.Min((float)cx / thumbnail.Width, (float)cx / thumbnail.Height);
|
||||
int scaleWidth = (int)(thumbnail.Width * scale);
|
||||
int scaleHeight = (int)(thumbnail.Height * scale);
|
||||
thumbnail = ResizeImage(thumbnail, scaleWidth, scaleHeight);
|
||||
}
|
||||
|
||||
return thumbnail;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all thumbnails in base64 format found on the G-code data.
|
||||
/// </summary>
|
||||
/// <param name="reader">The TextReader instance for the G-code content.</param>
|
||||
/// <returns>An enumeration of thumbnails in base64 format found on the G-code.</returns>
|
||||
private static IEnumerable<string> GetBase64Thumbnails(TextReader reader)
|
||||
{
|
||||
string line;
|
||||
StringBuilder capturedText = null;
|
||||
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
if (line.StartsWith("; thumbnail begin", StringComparison.InvariantCulture))
|
||||
{
|
||||
capturedText = new StringBuilder();
|
||||
}
|
||||
else if (line == "; thumbnail end")
|
||||
{
|
||||
if (capturedText != null)
|
||||
{
|
||||
yield return capturedText.ToString();
|
||||
|
||||
capturedText = null;
|
||||
}
|
||||
}
|
||||
else if (capturedText != null)
|
||||
{
|
||||
capturedText.Append(line[2..]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resize the image with high quality to the specified width and height.
|
||||
/// </summary>
|
||||
|
@ -200,11 +200,23 @@ namespace Microsoft.PowerToys.PreviewHandler.Markdown
|
||||
}
|
||||
});
|
||||
|
||||
PowerToysTelemetry.Log.WriteEvent(new MarkdownFilePreviewed());
|
||||
try
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new MarkdownFilePreviewed());
|
||||
}
|
||||
catch
|
||||
{ // Should not crash if sending telemetry is failing. Ignore the exception.
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new MarkdownFilePreviewError { Message = ex.Message });
|
||||
try
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new MarkdownFilePreviewError { Message = ex.Message });
|
||||
}
|
||||
catch
|
||||
{ // Should not crash if sending telemetry is failing. Ignore the exception.
|
||||
}
|
||||
|
||||
Controls.Clear();
|
||||
_infoBarDisplayed = true;
|
||||
|
@ -161,11 +161,23 @@ namespace Microsoft.PowerToys.PreviewHandler.Pdf
|
||||
}
|
||||
}
|
||||
|
||||
PowerToysTelemetry.Log.WriteEvent(new PdfFilePreviewed());
|
||||
try
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new PdfFilePreviewed());
|
||||
}
|
||||
catch
|
||||
{ // Should not crash if sending telemetry is failing. Ignore the exception.
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new PdfFilePreviewError { Message = ex.Message });
|
||||
try
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new PdfFilePreviewError { Message = ex.Message });
|
||||
}
|
||||
catch
|
||||
{ // Should not crash if sending telemetry is failing. Ignore the exception.
|
||||
}
|
||||
|
||||
Controls.Clear();
|
||||
_infoBar = GetTextBoxControl(Resources.PdfNotPreviewedError);
|
||||
|
@ -143,7 +143,13 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewError { Message = ex.Message });
|
||||
try
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewError { Message = ex.Message });
|
||||
}
|
||||
catch
|
||||
{ // Should not crash if sending telemetry is failing. Ignore the exception.
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
@ -160,7 +166,13 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
|
||||
AddWebViewControl(svgData);
|
||||
Resize += FormResized;
|
||||
base.DoPreview(dataSource);
|
||||
PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewed());
|
||||
try
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewed());
|
||||
}
|
||||
catch
|
||||
{ // Should not crash if sending telemetry is failing. Ignore the exception.
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -288,7 +300,14 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
|
||||
/// <param name="dataSource">Stream reference to access source file.</param>
|
||||
private void PreviewError<T>(Exception exception, T dataSource)
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewError { Message = exception.Message });
|
||||
try
|
||||
{
|
||||
PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewError { Message = exception.Message });
|
||||
}
|
||||
catch
|
||||
{ // Should not crash if sending telemetry is failing. Ignore the exception.
|
||||
}
|
||||
|
||||
Controls.Clear();
|
||||
_infoBarAdded = true;
|
||||
AddTextBoxControl(Properties.Resource.SvgNotPreviewedError);
|
||||
|
@ -19,14 +19,17 @@ namespace GcodePreviewHandlerUnitTests
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "new Exception() is fine in test projects.")]
|
||||
public class GcodePreviewHandlerTest
|
||||
{
|
||||
[TestMethod]
|
||||
public void GcodePreviewHandlerControlAddsControlsToFormWhenDoPreviewIsCalled()
|
||||
[DataTestMethod]
|
||||
[DataRow("HelperFiles/sample.gcode")]
|
||||
[DataRow("HelperFiles/sample_JPG.gcode")]
|
||||
[DataRow("HelperFiles/sample_QOI.gcode")]
|
||||
public void GcodePreviewHandlerControlAddsControlsToFormWhenDoPreviewIsCalled(string filePath)
|
||||
{
|
||||
// Arrange
|
||||
using (var gcodePreviewHandlerControl = new GcodePreviewHandlerControl())
|
||||
{
|
||||
// Act
|
||||
var file = File.ReadAllBytes("HelperFiles/sample.gcode");
|
||||
var file = File.ReadAllBytes(filePath);
|
||||
|
||||
gcodePreviewHandlerControl.DoPreview<IStream>(GetMockStream(file));
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -22,6 +22,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="HelperFiles\sample.gcode" />
|
||||
<None Remove="HelperFiles\sample_JPG.gcode" />
|
||||
<None Remove="HelperFiles\sample_QOI.gcode" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -41,6 +43,12 @@
|
||||
<Content Include="HelperFiles\sample.gcode">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="HelperFiles\sample_JPG.gcode">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="HelperFiles\sample_QOI.gcode">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Compile Include="..\STATestClassAttribute.cs" Link="STATestClassAttribute.cs" />
|
||||
<Compile Include="..\STATestMethodAttribute.cs" Link="STATestMethodAttribute.cs" />
|
||||
</ItemGroup>
|
||||
|
@ -2,28 +2,24 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using Common.ComInterlop;
|
||||
using Microsoft.PowerToys.STATestExtension;
|
||||
using Microsoft.PowerToys.ThumbnailHandler.Gcode;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using Moq;
|
||||
|
||||
namespace GcodeThumbnailProviderUnitTests
|
||||
{
|
||||
[STATestClass]
|
||||
public class GcodeThumbnailProviderTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void GetThumbnailValidStreamGcode()
|
||||
[DataTestMethod]
|
||||
[DataRow("HelperFiles/sample.gcode")]
|
||||
[DataRow("HelperFiles/sample_JPG.gcode")]
|
||||
[DataRow("HelperFiles/sample_QOI.gcode")]
|
||||
public void GetThumbnailValidStreamGcode(string filePath)
|
||||
{
|
||||
// Act
|
||||
var filePath = "HelperFiles/sample.gcode";
|
||||
|
||||
GcodeThumbnailProvider provider = new GcodeThumbnailProvider(filePath);
|
||||
|
||||
Bitmap bitmap = provider.GetThumbnail(256);
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -23,6 +23,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="HelperFiles\sample.gcode" />
|
||||
<None Remove="HelperFiles\sample_JPG.gcode" />
|
||||
<None Remove="HelperFiles\sample_QOI.gcode" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -44,5 +46,11 @@
|
||||
<Content Include="HelperFiles\sample.gcode">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="HelperFiles\sample_JPG.gcode">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Include="HelperFiles\sample_QOI.gcode">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<AssemblyTitle>PowerToys.PreviewHandlerCommon</AssemblyTitle>
|
||||
@ -9,6 +9,8 @@
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
81
src/modules/previewpane/common/Utilities/GcodeHelper.cs
Normal file
81
src/modules/previewpane/common/Utilities/GcodeHelper.cs
Normal file
@ -0,0 +1,81 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Common.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Gcode file helper class.
|
||||
/// </summary>
|
||||
public static class GcodeHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets any thumbnails found in a gcode file.
|
||||
/// </summary>
|
||||
/// <param name="reader">The <see cref="TextReader"/> instance to the gcode file.</param>
|
||||
/// <returns>The thumbnails found in a gcode file.</returns>
|
||||
public static IEnumerable<GcodeThumbnail> GetThumbnails(TextReader reader)
|
||||
{
|
||||
string? line;
|
||||
var format = GcodeThumbnailFormat.Unknown;
|
||||
StringBuilder? capturedText = null;
|
||||
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
if (line.StartsWith("; thumbnail", StringComparison.InvariantCulture))
|
||||
{
|
||||
var parts = line[11..].Split(" ");
|
||||
|
||||
switch (parts[1])
|
||||
{
|
||||
case "begin":
|
||||
format = parts[0].ToUpperInvariant() switch
|
||||
{
|
||||
"" => GcodeThumbnailFormat.PNG,
|
||||
"_JPG" => GcodeThumbnailFormat.JPG,
|
||||
"_QOI" => GcodeThumbnailFormat.QOI,
|
||||
_ => GcodeThumbnailFormat.Unknown,
|
||||
};
|
||||
capturedText = new StringBuilder();
|
||||
|
||||
break;
|
||||
|
||||
case "end":
|
||||
if (capturedText != null)
|
||||
{
|
||||
yield return new GcodeThumbnail(format, capturedText.ToString());
|
||||
|
||||
capturedText = null;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
capturedText?.Append(line[2..]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the best thumbnail available in a gcode file.
|
||||
/// </summary>
|
||||
/// <param name="reader">The <see cref="TextReader"/> instance to the gcode file.</param>
|
||||
/// <returns>The best thumbnail available in the gcode file.</returns>
|
||||
public static GcodeThumbnail? GetBestThumbnail(TextReader reader)
|
||||
{
|
||||
return GetThumbnails(reader)
|
||||
.Where(x => x.Format != GcodeThumbnailFormat.Unknown)
|
||||
.OrderByDescending(x => (int)x.Format)
|
||||
.ThenByDescending(x => x.Data.Length)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
72
src/modules/previewpane/common/Utilities/GcodeThumbnail.cs
Normal file
72
src/modules/previewpane/common/Utilities/GcodeThumbnail.cs
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using PreviewHandlerCommon.Utilities;
|
||||
|
||||
namespace Common.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a gcode thumbnail.
|
||||
/// </summary>
|
||||
public class GcodeThumbnail
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the gcode thumbnail image format.
|
||||
/// </summary>
|
||||
public GcodeThumbnailFormat Format { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the gcode thumbnail image data in base64.
|
||||
/// </summary>
|
||||
public string Data { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GcodeThumbnail"/> class.
|
||||
/// </summary>
|
||||
/// <param name="format">The gcode thumbnail image format.</param>
|
||||
/// <param name="data">The gcode thumbnail image data in base64.</param>
|
||||
public GcodeThumbnail(GcodeThumbnailFormat format, string data)
|
||||
{
|
||||
Format = format;
|
||||
Data = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="Bitmap"/> representing this thumbnail.
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="Bitmap"/> representing this thumbnail.</returns>
|
||||
public Bitmap? GetBitmap()
|
||||
{
|
||||
switch (Format)
|
||||
{
|
||||
case GcodeThumbnailFormat.JPG:
|
||||
case GcodeThumbnailFormat.PNG:
|
||||
return BitmapFromBase64String();
|
||||
|
||||
case GcodeThumbnailFormat.QOI:
|
||||
return BitmapFromQoiBase64String();
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap BitmapFromBase64String()
|
||||
{
|
||||
var bitmapBytes = Convert.FromBase64String(Data);
|
||||
|
||||
return new Bitmap(new MemoryStream(bitmapBytes));
|
||||
}
|
||||
|
||||
private Bitmap BitmapFromQoiBase64String()
|
||||
{
|
||||
var bitmapBytes = Convert.FromBase64String(Data);
|
||||
|
||||
return QoiImage.FromStream(new MemoryStream(bitmapBytes));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Common.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// The gcode thumbnail image format.
|
||||
/// </summary>
|
||||
public enum GcodeThumbnailFormat
|
||||
{
|
||||
/// <summary>
|
||||
/// Unknown image format.
|
||||
/// </summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// JPG image format.
|
||||
/// </summary>
|
||||
JPG,
|
||||
|
||||
/// <summary>
|
||||
/// QOI image format.
|
||||
/// </summary>
|
||||
QOI,
|
||||
|
||||
/// <summary>
|
||||
/// PNG image format.
|
||||
/// </summary>
|
||||
PNG,
|
||||
}
|
||||
}
|
177
src/modules/previewpane/common/Utilities/QoiImage.cs
Normal file
177
src/modules/previewpane/common/Utilities/QoiImage.cs
Normal file
@ -0,0 +1,177 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.IO;
|
||||
|
||||
//// Based on https://github.com/phoboslab/qoi/blob/master/qoi.h
|
||||
|
||||
namespace PreviewHandlerCommon.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// QOI Image helper.
|
||||
/// </summary>
|
||||
public static class QoiImage
|
||||
{
|
||||
#pragma warning disable SA1310 // Field names should not contain underscore
|
||||
private const byte QOI_OP_INDEX = 0x00; // 00xxxxxx
|
||||
private const byte QOI_OP_DIFF = 0x40; // 01xxxxxx
|
||||
private const byte QOI_OP_LUMA = 0x80; // 10xxxxxx
|
||||
private const byte QOI_OP_RUN = 0xc0; // 11xxxxxx
|
||||
private const byte QOI_OP_RGB = 0xfe; // 11111110
|
||||
private const byte QOI_OP_RGBA = 0xff; // 11111111
|
||||
|
||||
private const byte QOI_MASK_2 = 0xc0; // 11000000
|
||||
|
||||
private const int QOI_MAGIC = 'q' << 24 | 'o' << 16 | 'i' << 8 | 'f';
|
||||
private const int QOI_HEADER_SIZE = 14;
|
||||
|
||||
private const uint QOI_PIXELS_MAX = 400000000;
|
||||
|
||||
private const byte QOI_PADDING_LENGTH = 8;
|
||||
#pragma warning restore SA1310 // Field names should not contain underscore
|
||||
|
||||
private record struct QoiPixel(byte R, byte G, byte B, byte A)
|
||||
{
|
||||
public readonly int GetColorHash() => (R * 3) + (G * 5) + (B * 7) + (A * 11);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Bitmap"/> from the specified QOI data stream.
|
||||
/// </summary>
|
||||
/// <param name="stream">A <see cref="Stream"/> that contains the QOI data.</param>
|
||||
/// <returns>The <see cref="Bitmap"/> this method creates.</returns>
|
||||
/// <exception cref="ArgumentException">The stream does not have a valid QOI image format.</exception>
|
||||
public static Bitmap FromStream(Stream stream)
|
||||
{
|
||||
var fileSize = stream.Length;
|
||||
|
||||
if (fileSize < QOI_HEADER_SIZE + QOI_PADDING_LENGTH)
|
||||
{
|
||||
throw new ArgumentException("Not enough data for a QOI file");
|
||||
}
|
||||
|
||||
using var reader = new BinaryReader(stream);
|
||||
|
||||
var headerMagic = ReadUInt32BigEndian(reader);
|
||||
|
||||
if (headerMagic != QOI_MAGIC)
|
||||
{
|
||||
throw new ArgumentException("Invalid QOI file header");
|
||||
}
|
||||
|
||||
var width = ReadUInt32BigEndian(reader);
|
||||
var height = ReadUInt32BigEndian(reader);
|
||||
var channels = reader.ReadByte();
|
||||
var colorSpace = reader.ReadByte();
|
||||
|
||||
if (width == 0 || height == 0 || channels < 3 || channels > 4 || colorSpace > 1 || height >= QOI_PIXELS_MAX / width)
|
||||
{
|
||||
throw new ArgumentException("Invalid QOI file data");
|
||||
}
|
||||
|
||||
var pixelsCount = width * height;
|
||||
var pixels = new QoiPixel[pixelsCount];
|
||||
var index = new QoiPixel[64];
|
||||
|
||||
var pixel = new QoiPixel(0, 0, 0, 255);
|
||||
|
||||
var run = 0;
|
||||
var chunksLen = fileSize - QOI_PADDING_LENGTH;
|
||||
|
||||
for (var pixelIndex = 0; pixelIndex < pixelsCount; pixelIndex++)
|
||||
{
|
||||
if (run > 0)
|
||||
{
|
||||
run--;
|
||||
}
|
||||
else if (stream.Position < chunksLen)
|
||||
{
|
||||
var b1 = reader.ReadByte();
|
||||
|
||||
if (b1 == QOI_OP_RGB)
|
||||
{
|
||||
pixel.R = reader.ReadByte();
|
||||
pixel.G = reader.ReadByte();
|
||||
pixel.B = reader.ReadByte();
|
||||
}
|
||||
else if (b1 == QOI_OP_RGBA)
|
||||
{
|
||||
pixel.R = reader.ReadByte();
|
||||
pixel.G = reader.ReadByte();
|
||||
pixel.B = reader.ReadByte();
|
||||
pixel.A = reader.ReadByte();
|
||||
}
|
||||
else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX)
|
||||
{
|
||||
pixel = index[b1];
|
||||
}
|
||||
else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF)
|
||||
{
|
||||
pixel.R += (byte)(((b1 >> 4) & 0x03) - 2);
|
||||
pixel.G += (byte)(((b1 >> 2) & 0x03) - 2);
|
||||
pixel.B += (byte)((b1 & 0x03) - 2);
|
||||
}
|
||||
else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA)
|
||||
{
|
||||
var b2 = reader.ReadByte();
|
||||
var vg = (b1 & 0x3f) - 32;
|
||||
pixel.R += (byte)(vg - 8 + ((b2 >> 4) & 0x0f));
|
||||
pixel.G += (byte)vg;
|
||||
pixel.B += (byte)(vg - 8 + (b2 & 0x0f));
|
||||
}
|
||||
else if ((b1 & QOI_MASK_2) == QOI_OP_RUN)
|
||||
{
|
||||
run = b1 & 0x3f;
|
||||
}
|
||||
|
||||
index[pixel.GetColorHash() % 64] = pixel;
|
||||
}
|
||||
|
||||
pixels[pixelIndex] = pixel;
|
||||
}
|
||||
|
||||
return ConvertToBitmap(width, height, channels, pixels);
|
||||
}
|
||||
|
||||
private static Bitmap ConvertToBitmap(uint width, uint height, byte channels, QoiPixel[] pixels)
|
||||
{
|
||||
var pixelFormat = channels == 4 ? PixelFormat.Format32bppArgb : PixelFormat.Format24bppRgb;
|
||||
var bitmap = new Bitmap((int)width, (int)height, pixelFormat);
|
||||
|
||||
var bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.WriteOnly, pixelFormat);
|
||||
|
||||
unsafe
|
||||
{
|
||||
for (var pixelIndex = 0; pixelIndex < pixels.Length; pixelIndex++)
|
||||
{
|
||||
var pixel = pixels[pixelIndex];
|
||||
var bitmapPixel = (byte*)bitmapData.Scan0 + (pixelIndex * channels);
|
||||
|
||||
bitmapPixel[0] = pixel.B;
|
||||
bitmapPixel[1] = pixel.G;
|
||||
bitmapPixel[2] = pixel.R;
|
||||
if (channels == 4)
|
||||
{
|
||||
bitmapPixel[3] = pixel.A;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bitmap.UnlockBits(bitmapData);
|
||||
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
private static uint ReadUInt32BigEndian(BinaryReader reader)
|
||||
{
|
||||
var buffer = reader.ReadBytes(4);
|
||||
|
||||
return BinaryPrimitives.ReadUInt32BigEndian(buffer);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
@ -17,7 +18,7 @@ namespace Common.Utilities
|
||||
/// </remarks>
|
||||
public class ReadonlyStream : Stream
|
||||
{
|
||||
private IStream _stream;
|
||||
private IStream? _stream;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReadonlyStream"/> class.
|
||||
@ -238,6 +239,7 @@ namespace Common.Utilities
|
||||
}
|
||||
}
|
||||
|
||||
[MemberNotNull(nameof(_stream))]
|
||||
private void CheckDisposed()
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_stream == null, this);
|
||||
|
@ -20,12 +20,12 @@ namespace Common
|
||||
/// <summary>
|
||||
/// WebView2 Control to display Svg.
|
||||
/// </summary>
|
||||
private WebView2 _browser;
|
||||
private WebView2? _browser;
|
||||
|
||||
/// <summary>
|
||||
/// WebView2 Environment
|
||||
/// </summary>
|
||||
private CoreWebView2Environment _webView2Environment;
|
||||
private CoreWebView2Environment? _webView2Environment;
|
||||
|
||||
/// <summary>
|
||||
/// Name of the virtual host
|
||||
@ -38,7 +38,7 @@ namespace Common
|
||||
/// <remarks>
|
||||
/// Source: https://stackoverflow.com/a/283917/14774889
|
||||
/// </remarks>
|
||||
public static string AssemblyDirectory
|
||||
public static string? AssemblyDirectory
|
||||
{
|
||||
get
|
||||
{
|
||||
@ -60,7 +60,7 @@ namespace Common
|
||||
_browser = new WebView2();
|
||||
_browser.Dock = DockStyle.Fill;
|
||||
_browser.Visible = true;
|
||||
_browser.NavigationCompleted += (object sender, CoreWebView2NavigationCompletedEventArgs args) =>
|
||||
_browser.NavigationCompleted += (object? sender, CoreWebView2NavigationCompletedEventArgs args) =>
|
||||
{
|
||||
// Put here logic needed after WebView2 control is done navigating to url/page
|
||||
};
|
||||
@ -83,7 +83,7 @@ namespace Common
|
||||
_browser.NavigateToString("Test");
|
||||
|
||||
// Or navigate to Uri
|
||||
_browser.Source = new Uri(filePath);
|
||||
_browser.Source = new Uri(filePath!);
|
||||
}
|
||||
catch (NullReferenceException)
|
||||
{
|
||||
|
@ -72,6 +72,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
[JsonPropertyName("search_wait_for_slow_results")]
|
||||
public bool SearchWaitForSlowResults { get; set; }
|
||||
|
||||
[JsonPropertyName("use_pinyin")]
|
||||
public bool UsePinyin { get; set; }
|
||||
|
||||
[JsonPropertyName("generate_thumbnails_from_files")]
|
||||
public bool GenerateThumbnailsFromFiles { get; set; }
|
||||
|
||||
@ -103,6 +106,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
SearchQueryTuningEnabled = false;
|
||||
SearchWaitForSlowResults = false;
|
||||
GenerateThumbnailsFromFiles = true;
|
||||
UsePinyin = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,6 +81,7 @@ namespace ViewModelTests
|
||||
Assert.AreEqual(originalSettings.Properties.SearchResultPreference, viewModel.SearchResultPreference);
|
||||
Assert.AreEqual(originalSettings.Properties.SearchTypePreference, viewModel.SearchTypePreference);
|
||||
Assert.AreEqual(originalSettings.Properties.GenerateThumbnailsFromFiles, viewModel.GenerateThumbnailsFromFiles);
|
||||
Assert.AreEqual(originalSettings.Properties.UsePinyin, viewModel.UsePinyin);
|
||||
|
||||
// Verify that the stub file was used
|
||||
var expectedCallCount = 2; // once via the view model, and once by the test (GetSettings<T>)
|
||||
|
@ -153,6 +153,14 @@
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.TabSelectsContextButtons, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard
|
||||
x:Uid="PowerLauncher_UsePinyin"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource SymbolThemeFontFamily}, Glyph=}">
|
||||
<ToggleSwitch
|
||||
x:Uid="ToggleSwitch"
|
||||
IsOn="{x:Bind ViewModel.UsePinyin, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<controls:SettingsCard x:Uid="PowerLauncher_GenerateThumbnailsFromFiles">
|
||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.GenerateThumbnailsFromFiles, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
@ -736,6 +736,12 @@
|
||||
<data name="PowerLauncher_SearchResultPreference.Header" xml:space="preserve">
|
||||
<value>Search result preference</value>
|
||||
</data>
|
||||
<data name="PowerLauncher_UsePinyin.Header" xml:space="preserve">
|
||||
<value>Use Pinyin</value>
|
||||
</data>
|
||||
<data name="PowerLauncher_UsePinyin.Description" xml:space="preserve">
|
||||
<value>Experimental: Use Pinyin on the search query. May not work for every plugin.</value>
|
||||
</data>
|
||||
<data name="PowerLauncher_SearchResultPreference_MostRecentlyUsed" xml:space="preserve">
|
||||
<value>Most recently used</value>
|
||||
</data>
|
||||
|
@ -577,6 +577,23 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public bool UsePinyin
|
||||
{
|
||||
get
|
||||
{
|
||||
return settings.Properties.UsePinyin;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (settings.Properties.UsePinyin != value)
|
||||
{
|
||||
settings.Properties.UsePinyin = value;
|
||||
UpdateSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ObservableCollection<PowerLauncherPluginViewModel> _plugins;
|
||||
|
||||
public ObservableCollection<PowerLauncherPluginViewModel> Plugins
|
||||
|
Loading…
Reference in New Issue
Block a user