Merge remote-tracking branch 'origin/main' into dev/snickler/net8-upgrade

This commit is contained in:
Jeremy Sinclair 2023-09-19 12:04:24 -04:00
commit 5867b12d67
35 changed files with 103525 additions and 55318 deletions

View File

@ -35,6 +35,7 @@ mshtang
Myrvold Myrvold
naveensrinivasan naveensrinivasan
nVidia nVidia
phoboslab
Ponten Ponten
Pooja Pooja
robmen robmen
@ -44,6 +45,7 @@ skycommand
snickler snickler
sinclairinat sinclairinat
streamjsonrpc streamjsonrpc
Szablewski
tilovell tilovell
TheJoeFin TheJoeFin
Triet Triet

View File

@ -1014,6 +1014,7 @@ lstrlen
LTRB LTRB
LTRREADING LTRREADING
luid luid
LUMA
lusrmgr lusrmgr
LVal LVal
LWA LWA
@ -1496,6 +1497,7 @@ qianlifeng
qit qit
QITAB QITAB
QITABENT QITABENT
qoi
qps qps
Quarternary Quarternary
QUERYENDSESSION QUERYENDSESSION
@ -2250,6 +2252,7 @@ xsi
XStr XStr
XUP XUP
XVIRTUALSCREEN XVIRTUALSCREEN
xxxxxx
YAxis YAxis
ycv ycv
Yeet Yeet

View File

@ -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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. 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 ## Utility: ImageResizer
### Brice Lams's Image Resizer License ### Brice Lams's Image Resizer License

View File

@ -104,7 +104,6 @@ IFACEMETHODIMP ExplorerCommand::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataOb
if (pdtobj) if (pdtobj)
{ {
m_data_obj = pdtobj; m_data_obj = pdtobj;
m_data_obj->AddRef();
} }
return S_OK; return S_OK;
} }
@ -242,10 +241,6 @@ ExplorerCommand::ExplorerCommand()
ExplorerCommand::~ExplorerCommand() ExplorerCommand::~ExplorerCommand()
{ {
if (m_data_obj)
{
m_data_obj->Release();
}
--globals::ref_count; --globals::ref_count;
} }

View File

@ -90,7 +90,19 @@ namespace MouseWithoutBorders
lock (ThreadsLock) lock (ThreadsLock)
{ {
#pragma warning disable 618 // Temporary #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 #pragma warning restore 618
} }
} }

View File

@ -48,6 +48,13 @@ int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
return 0; 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); std::wstring pid = std::wstring(lpCmdLine);
if (!pid.empty()) if (!pid.empty())
{ {

View File

@ -71,6 +71,11 @@ public:
PostMessageW(m_window, WM_PRIV_LOCATIONCHANGE, NULL, NULL); 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); this->disableModuleCallback = std::move(disableModuleCallbackFunction);
FancyZonesSettings::instance().LoadSettings(); FancyZonesSettings::instance().LoadSettings();

View File

@ -171,6 +171,11 @@ namespace PowerLauncher
_settings.GenerateThumbnailsFromFiles = overloadSettings.Properties.GenerateThumbnailsFromFiles; _settings.GenerateThumbnailsFromFiles = overloadSettings.Properties.GenerateThumbnailsFromFiles;
} }
if (_settings.ShouldUsePinyin != overloadSettings.Properties.UsePinyin)
{
_settings.ShouldUsePinyin = overloadSettings.Properties.UsePinyin;
}
retry = false; retry = false;
} }

View File

@ -2,14 +2,6 @@
// The Microsoft Corporation licenses this file to you under the MIT license. // The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // 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;
using Common.Utilities; using Common.Utilities;
using Microsoft.PowerToys.PreviewHandler.Gcode.Telemetry.Events; using Microsoft.PowerToys.PreviewHandler.Gcode.Telemetry.Events;
@ -67,7 +59,9 @@ namespace Microsoft.PowerToys.PreviewHandler.Gcode
using (var reader = new StreamReader(fs)) using (var reader = new StreamReader(fs))
{ {
thumbnail = GetThumbnail(reader); var gcodeThumbnail = GcodeHelper.GetBestThumbnail(reader);
thumbnail = gcodeThumbnail?.GetBitmap();
} }
_infoBarAdded = false; _infoBarAdded = false;
@ -84,74 +78,20 @@ namespace Microsoft.PowerToys.PreviewHandler.Gcode
Resize += FormResized; Resize += FormResized;
base.DoPreview(fs); base.DoPreview(fs);
try
{
PowerToysTelemetry.Log.WriteEvent(new GcodeFilePreviewed()); PowerToysTelemetry.Log.WriteEvent(new GcodeFilePreviewed());
} }
catch
{ // Should not crash if sending telemetry is failing. Ignore the exception.
}
}
catch (Exception ex) catch (Exception ex)
{ {
PreviewError(ex, dataSource); PreviewError(ex, dataSource);
} }
} }
/// <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> /// <summary>
/// Occurs when RichtextBox is resized. /// Occurs when RichtextBox is resized.
/// </summary> /// </summary>
@ -213,8 +153,15 @@ namespace Microsoft.PowerToys.PreviewHandler.Gcode
/// <param name="exception">The exception which occurred.</param> /// <param name="exception">The exception which occurred.</param>
/// <param name="dataSource">Stream reference to access source file.</param> /// <param name="dataSource">Stream reference to access source file.</param>
private void PreviewError<T>(Exception exception, T dataSource) private void PreviewError<T>(Exception exception, T dataSource)
{
try
{ {
PowerToysTelemetry.Log.WriteEvent(new GcodeFilePreviewError { Message = exception.Message }); PowerToysTelemetry.Log.WriteEvent(new GcodeFilePreviewError { Message = exception.Message });
}
catch
{ // Should not crash if sending telemetry is failing. Ignore the exception.
}
Controls.Clear(); Controls.Clear();
_infoBarAdded = true; _infoBarAdded = true;
AddTextBoxControl(Properties.Resource.GcodeNotPreviewedError); AddTextBoxControl(Properties.Resource.GcodeNotPreviewedError);

View File

@ -2,7 +2,7 @@
// The Microsoft Corporation licenses this file to you under the MIT license. // The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System.Drawing.Drawing2D; using System.Drawing.Drawing2D;
using System.Text; using Common.Utilities;
namespace Microsoft.PowerToys.ThumbnailHandler.Gcode namespace Microsoft.PowerToys.ThumbnailHandler.Gcode
{ {
@ -45,19 +45,11 @@ namespace Microsoft.PowerToys.ThumbnailHandler.Gcode
return null; return null;
} }
Bitmap thumbnail = null; var gcodeThumbnail = GcodeHelper.GetBestThumbnail(reader);
var bitmapBase64 = GetBase64Thumbnails(reader) var thumbnail = gcodeThumbnail?.GetBitmap();
.OrderByDescending(x => x.Length)
.FirstOrDefault();
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 // We are not the appropriate size for caller. Resize now while
// respecting the aspect ratio. // respecting the aspect ratio.
@ -66,43 +58,10 @@ namespace Microsoft.PowerToys.ThumbnailHandler.Gcode
int scaleHeight = (int)(thumbnail.Height * scale); int scaleHeight = (int)(thumbnail.Height * scale);
thumbnail = ResizeImage(thumbnail, scaleWidth, scaleHeight); thumbnail = ResizeImage(thumbnail, scaleWidth, scaleHeight);
} }
}
return thumbnail; 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> /// <summary>
/// Resize the image with high quality to the specified width and height. /// Resize the image with high quality to the specified width and height.
/// </summary> /// </summary>

View File

@ -200,11 +200,23 @@ namespace Microsoft.PowerToys.PreviewHandler.Markdown
} }
}); });
try
{
PowerToysTelemetry.Log.WriteEvent(new MarkdownFilePreviewed()); PowerToysTelemetry.Log.WriteEvent(new MarkdownFilePreviewed());
} }
catch
{ // Should not crash if sending telemetry is failing. Ignore the exception.
}
}
catch (Exception ex) catch (Exception ex)
{
try
{ {
PowerToysTelemetry.Log.WriteEvent(new MarkdownFilePreviewError { Message = ex.Message }); PowerToysTelemetry.Log.WriteEvent(new MarkdownFilePreviewError { Message = ex.Message });
}
catch
{ // Should not crash if sending telemetry is failing. Ignore the exception.
}
Controls.Clear(); Controls.Clear();
_infoBarDisplayed = true; _infoBarDisplayed = true;

View File

@ -161,11 +161,23 @@ namespace Microsoft.PowerToys.PreviewHandler.Pdf
} }
} }
try
{
PowerToysTelemetry.Log.WriteEvent(new PdfFilePreviewed()); PowerToysTelemetry.Log.WriteEvent(new PdfFilePreviewed());
} }
catch
{ // Should not crash if sending telemetry is failing. Ignore the exception.
}
}
catch (Exception ex) catch (Exception ex)
{
try
{ {
PowerToysTelemetry.Log.WriteEvent(new PdfFilePreviewError { Message = ex.Message }); PowerToysTelemetry.Log.WriteEvent(new PdfFilePreviewError { Message = ex.Message });
}
catch
{ // Should not crash if sending telemetry is failing. Ignore the exception.
}
Controls.Clear(); Controls.Clear();
_infoBar = GetTextBoxControl(Resources.PdfNotPreviewedError); _infoBar = GetTextBoxControl(Resources.PdfNotPreviewedError);

View File

@ -142,9 +142,15 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
svgData = SvgPreviewHandlerHelper.AddStyleSVG(svgData); svgData = SvgPreviewHandlerHelper.AddStyleSVG(svgData);
} }
catch (Exception ex) catch (Exception ex)
{
try
{ {
PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewError { Message = ex.Message }); PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewError { Message = ex.Message });
} }
catch
{ // Should not crash if sending telemetry is failing. Ignore the exception.
}
}
try try
{ {
@ -160,8 +166,14 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
AddWebViewControl(svgData); AddWebViewControl(svgData);
Resize += FormResized; Resize += FormResized;
base.DoPreview(dataSource); base.DoPreview(dataSource);
try
{
PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewed()); PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewed());
} }
catch
{ // Should not crash if sending telemetry is failing. Ignore the exception.
}
}
catch (Exception ex) catch (Exception ex)
{ {
PreviewError(ex, dataSource); PreviewError(ex, dataSource);
@ -287,8 +299,15 @@ namespace Microsoft.PowerToys.PreviewHandler.Svg
/// <param name="exception">The exception which occurred.</param> /// <param name="exception">The exception which occurred.</param>
/// <param name="dataSource">Stream reference to access source file.</param> /// <param name="dataSource">Stream reference to access source file.</param>
private void PreviewError<T>(Exception exception, T dataSource) private void PreviewError<T>(Exception exception, T dataSource)
{
try
{ {
PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewError { Message = exception.Message }); PowerToysTelemetry.Log.WriteEvent(new SvgFilePreviewError { Message = exception.Message });
}
catch
{ // Should not crash if sending telemetry is failing. Ignore the exception.
}
Controls.Clear(); Controls.Clear();
_infoBarAdded = true; _infoBarAdded = true;
AddTextBoxControl(Properties.Resource.SvgNotPreviewedError); AddTextBoxControl(Properties.Resource.SvgNotPreviewedError);

View File

@ -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.")] [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "new Exception() is fine in test projects.")]
public class GcodePreviewHandlerTest public class GcodePreviewHandlerTest
{ {
[TestMethod] [DataTestMethod]
public void GcodePreviewHandlerControlAddsControlsToFormWhenDoPreviewIsCalled() [DataRow("HelperFiles/sample.gcode")]
[DataRow("HelperFiles/sample_JPG.gcode")]
[DataRow("HelperFiles/sample_QOI.gcode")]
public void GcodePreviewHandlerControlAddsControlsToFormWhenDoPreviewIsCalled(string filePath)
{ {
// Arrange // Arrange
using (var gcodePreviewHandlerControl = new GcodePreviewHandlerControl()) using (var gcodePreviewHandlerControl = new GcodePreviewHandlerControl())
{ {
// Act // Act
var file = File.ReadAllBytes("HelperFiles/sample.gcode"); var file = File.ReadAllBytes(filePath);
gcodePreviewHandlerControl.DoPreview<IStream>(GetMockStream(file)); 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

View File

@ -22,6 +22,8 @@
<ItemGroup> <ItemGroup>
<None Remove="HelperFiles\sample.gcode" /> <None Remove="HelperFiles\sample.gcode" />
<None Remove="HelperFiles\sample_JPG.gcode" />
<None Remove="HelperFiles\sample_QOI.gcode" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -41,6 +43,12 @@
<Content Include="HelperFiles\sample.gcode"> <Content Include="HelperFiles\sample.gcode">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </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="..\STATestClassAttribute.cs" Link="STATestClassAttribute.cs" />
<Compile Include="..\STATestMethodAttribute.cs" Link="STATestMethodAttribute.cs" /> <Compile Include="..\STATestMethodAttribute.cs" Link="STATestMethodAttribute.cs" />
</ItemGroup> </ItemGroup>

View File

@ -2,28 +2,24 @@
// The Microsoft Corporation licenses this file to you under the MIT license. // The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System;
using System.Drawing; using System.Drawing;
using System.IO; using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using Common.ComInterlop;
using Microsoft.PowerToys.STATestExtension; using Microsoft.PowerToys.STATestExtension;
using Microsoft.PowerToys.ThumbnailHandler.Gcode; using Microsoft.PowerToys.ThumbnailHandler.Gcode;
using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
namespace GcodeThumbnailProviderUnitTests namespace GcodeThumbnailProviderUnitTests
{ {
[STATestClass] [STATestClass]
public class GcodeThumbnailProviderTests public class GcodeThumbnailProviderTests
{ {
[TestMethod] [DataTestMethod]
public void GetThumbnailValidStreamGcode() [DataRow("HelperFiles/sample.gcode")]
[DataRow("HelperFiles/sample_JPG.gcode")]
[DataRow("HelperFiles/sample_QOI.gcode")]
public void GetThumbnailValidStreamGcode(string filePath)
{ {
// Act // Act
var filePath = "HelperFiles/sample.gcode";
GcodeThumbnailProvider provider = new GcodeThumbnailProvider(filePath); GcodeThumbnailProvider provider = new GcodeThumbnailProvider(filePath);
Bitmap bitmap = provider.GetThumbnail(256); Bitmap bitmap = provider.GetThumbnail(256);

View File

@ -23,6 +23,8 @@
<ItemGroup> <ItemGroup>
<None Remove="HelperFiles\sample.gcode" /> <None Remove="HelperFiles\sample.gcode" />
<None Remove="HelperFiles\sample_JPG.gcode" />
<None Remove="HelperFiles\sample_QOI.gcode" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -44,5 +46,11 @@
<Content Include="HelperFiles\sample.gcode"> <Content Include="HelperFiles\sample.gcode">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Include="HelperFiles\sample_JPG.gcode">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="HelperFiles\sample_QOI.gcode">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<UseWindowsForms>true</UseWindowsForms> <UseWindowsForms>true</UseWindowsForms>
<AssemblyTitle>PowerToys.PreviewHandlerCommon</AssemblyTitle> <AssemblyTitle>PowerToys.PreviewHandlerCommon</AssemblyTitle>
@ -9,6 +9,8 @@
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath> <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View 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();
}
}
}

View 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));
}
}
}

View File

@ -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,
}
}

View 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);
}
}
}

View File

@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information. // See the LICENSE file in the project root for more information.
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes; using System.Runtime.InteropServices.ComTypes;
@ -17,7 +18,7 @@ namespace Common.Utilities
/// </remarks> /// </remarks>
public class ReadonlyStream : Stream public class ReadonlyStream : Stream
{ {
private IStream _stream; private IStream? _stream;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="ReadonlyStream"/> class. /// Initializes a new instance of the <see cref="ReadonlyStream"/> class.
@ -238,6 +239,7 @@ namespace Common.Utilities
} }
} }
[MemberNotNull(nameof(_stream))]
private void CheckDisposed() private void CheckDisposed()
{ {
ObjectDisposedException.ThrowIf(_stream == null, this); ObjectDisposedException.ThrowIf(_stream == null, this);

View File

@ -20,12 +20,12 @@ namespace Common
/// <summary> /// <summary>
/// WebView2 Control to display Svg. /// WebView2 Control to display Svg.
/// </summary> /// </summary>
private WebView2 _browser; private WebView2? _browser;
/// <summary> /// <summary>
/// WebView2 Environment /// WebView2 Environment
/// </summary> /// </summary>
private CoreWebView2Environment _webView2Environment; private CoreWebView2Environment? _webView2Environment;
/// <summary> /// <summary>
/// Name of the virtual host /// Name of the virtual host
@ -38,7 +38,7 @@ namespace Common
/// <remarks> /// <remarks>
/// Source: https://stackoverflow.com/a/283917/14774889 /// Source: https://stackoverflow.com/a/283917/14774889
/// </remarks> /// </remarks>
public static string AssemblyDirectory public static string? AssemblyDirectory
{ {
get get
{ {
@ -60,7 +60,7 @@ namespace Common
_browser = new WebView2(); _browser = new WebView2();
_browser.Dock = DockStyle.Fill; _browser.Dock = DockStyle.Fill;
_browser.Visible = true; _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 // Put here logic needed after WebView2 control is done navigating to url/page
}; };
@ -83,7 +83,7 @@ namespace Common
_browser.NavigateToString("Test"); _browser.NavigateToString("Test");
// Or navigate to Uri // Or navigate to Uri
_browser.Source = new Uri(filePath); _browser.Source = new Uri(filePath!);
} }
catch (NullReferenceException) catch (NullReferenceException)
{ {

View File

@ -72,6 +72,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
[JsonPropertyName("search_wait_for_slow_results")] [JsonPropertyName("search_wait_for_slow_results")]
public bool SearchWaitForSlowResults { get; set; } public bool SearchWaitForSlowResults { get; set; }
[JsonPropertyName("use_pinyin")]
public bool UsePinyin { get; set; }
[JsonPropertyName("generate_thumbnails_from_files")] [JsonPropertyName("generate_thumbnails_from_files")]
public bool GenerateThumbnailsFromFiles { get; set; } public bool GenerateThumbnailsFromFiles { get; set; }
@ -103,6 +106,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
SearchQueryTuningEnabled = false; SearchQueryTuningEnabled = false;
SearchWaitForSlowResults = false; SearchWaitForSlowResults = false;
GenerateThumbnailsFromFiles = true; GenerateThumbnailsFromFiles = true;
UsePinyin = false;
} }
} }
} }

View File

@ -81,6 +81,7 @@ namespace ViewModelTests
Assert.AreEqual(originalSettings.Properties.SearchResultPreference, viewModel.SearchResultPreference); Assert.AreEqual(originalSettings.Properties.SearchResultPreference, viewModel.SearchResultPreference);
Assert.AreEqual(originalSettings.Properties.SearchTypePreference, viewModel.SearchTypePreference); Assert.AreEqual(originalSettings.Properties.SearchTypePreference, viewModel.SearchTypePreference);
Assert.AreEqual(originalSettings.Properties.GenerateThumbnailsFromFiles, viewModel.GenerateThumbnailsFromFiles); Assert.AreEqual(originalSettings.Properties.GenerateThumbnailsFromFiles, viewModel.GenerateThumbnailsFromFiles);
Assert.AreEqual(originalSettings.Properties.UsePinyin, viewModel.UsePinyin);
// Verify that the stub file was used // Verify that the stub file was used
var expectedCallCount = 2; // once via the view model, and once by the test (GetSettings<T>) var expectedCallCount = 2; // once via the view model, and once by the test (GetSettings<T>)

View File

@ -153,6 +153,14 @@
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.TabSelectsContextButtons, Mode=TwoWay}" /> <ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.TabSelectsContextButtons, Mode=TwoWay}" />
</controls:SettingsCard> </controls:SettingsCard>
<controls:SettingsCard
x:Uid="PowerLauncher_UsePinyin"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource SymbolThemeFontFamily}, Glyph=&#xE98A;}">
<ToggleSwitch
x:Uid="ToggleSwitch"
IsOn="{x:Bind ViewModel.UsePinyin, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="PowerLauncher_GenerateThumbnailsFromFiles"> <controls:SettingsCard x:Uid="PowerLauncher_GenerateThumbnailsFromFiles">
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.GenerateThumbnailsFromFiles, Mode=TwoWay}" /> <ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.GenerateThumbnailsFromFiles, Mode=TwoWay}" />
</controls:SettingsCard> </controls:SettingsCard>

View File

@ -736,6 +736,12 @@
<data name="PowerLauncher_SearchResultPreference.Header" xml:space="preserve"> <data name="PowerLauncher_SearchResultPreference.Header" xml:space="preserve">
<value>Search result preference</value> <value>Search result preference</value>
</data> </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"> <data name="PowerLauncher_SearchResultPreference_MostRecentlyUsed" xml:space="preserve">
<value>Most recently used</value> <value>Most recently used</value>
</data> </data>

View File

@ -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; private ObservableCollection<PowerLauncherPluginViewModel> _plugins;
public ObservableCollection<PowerLauncherPluginViewModel> Plugins public ObservableCollection<PowerLauncherPluginViewModel> Plugins