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
naveensrinivasan
nVidia
phoboslab
Ponten
Pooja
robmen
@ -44,6 +45,7 @@ skycommand
snickler
sinclairinat
streamjsonrpc
Szablewski
tilovell
TheJoeFin
Triet

View File

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

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
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

View File

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

View File

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

View File

@ -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())
{

View File

@ -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();

View File

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

View File

@ -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);

View File

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

View File

@ -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;

View File

@ -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);

View File

@ -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);

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.")]
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

View File

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

View File

@ -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);

View File

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

View File

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

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.
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);

View File

@ -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)
{

View File

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

View File

@ -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>)

View File

@ -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=&#xE98A;}">
<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>

View File

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

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