Compare commits

..

2 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
412d60fbe4 Fix MouseHighlighter shape object leak (remove TODO in production code)
Agent-Logs-Url: https://github.com/microsoft/PowerToys/sessions/b3bccf3d-6f32-4463-907d-723c08013a6b

Co-authored-by: MuyuanMS <116717757+MuyuanMS@users.noreply.github.com>
2026-04-29 09:32:26 +00:00
copilot-swe-agent[bot]
01d31cb62e Initial plan 2026-04-29 08:49:05 +00:00
3 changed files with 29 additions and 49 deletions

View File

@@ -211,15 +211,23 @@ void Highlighter::AddDrawingPoint(MouseButton button)
else
{
circleShape.FillBrush(m_compositor.CreateColorBrush(m_alwaysColor));
// Remove the previous always-pointer shape from the collection before replacing it.
// It has already been made transparent by ClearDrawingPoint(), so removing it
// causes no visual glitch and prevents an unbounded accumulation of shape objects.
if (m_alwaysPointer)
{
uint32_t index;
if (m_shape.Shapes().IndexOf(m_alwaysPointer, index))
{
m_shape.Shapes().RemoveAt(index);
}
}
m_alwaysPointer = circleShape;
}
}
m_shape.Shapes().Append(circleShape);
// TODO: We're leaking shapes for long drawing sessions.
// Perhaps add a task to the Dispatcher every X circles to clean up.
// Get back on top in case other Window is now the topmost.
// HACK: Draw with 1 pixel off. Otherwise, Windows glitches the task bar transparency when a transparent window fill the whole screen.
SetWindowPos(m_hwnd, HWND_TOPMOST, GetSystemMetrics(SM_XVIRTUALSCREEN) + 1, GetSystemMetrics(SM_YVIRTUALSCREEN) + 1, GetSystemMetrics(SM_CXVIRTUALSCREEN) - 2, GetSystemMetrics(SM_CYVIRTUALSCREEN) - 2, 0);
@@ -289,7 +297,19 @@ void Highlighter::StartDrawingPointFading(MouseButton button)
animation.Duration(timeSpan(duration));
animation.DelayTime(timeSpan(delay));
// Use a scoped batch to detect when the fade animation completes, then remove
// the fully-transparent shape from the collection so it does not leak.
auto batch = m_compositor.CreateScopedBatch(winrt::CompositionBatchTypes::Animation);
circleShape.FillBrush().StartAnimation(L"Color", animation);
batch.End();
auto shapes = m_shape.Shapes();
batch.Completed([shape = circleShape, shapes](winrt::IInspectable const&, winrt::CompositionBatchCompletedEventArgs const&) {
uint32_t index;
if (shapes.IndexOf(shape, index))
{
shapes.RemoveAt(index);
}
});
}
void Highlighter::ClearDrawingPoint()

View File

@@ -1,34 +0,0 @@
// 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.Diagnostics;
using System.IO;
using System.Reflection;
using Microsoft.PowerToys.Settings.UI.ViewModels;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ViewModelTests
{
[TestClass]
public class NewPlus
{
[TestMethod]
public void CreateExplorerProcessStartInfoShouldQuoteTemplatePathAndUseFullExplorerPath()
{
// Arrange
const string templatePath = @"C:\Users\Test User\Documents\My Templates";
var createExplorerProcessStartInfoMethod = typeof(NewPlusViewModel).GetMethod("CreateExplorerProcessStartInfo", BindingFlags.NonPublic | BindingFlags.Static);
// Act
var processStartInfo = (ProcessStartInfo)createExplorerProcessStartInfoMethod.Invoke(null, new object[] { templatePath });
// Assert
Assert.IsNotNull(processStartInfo);
Assert.AreEqual(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "explorer.exe"), processStartInfo.FileName);
Assert.AreEqual($"\"{templatePath}\"", processStartInfo.Arguments);
}
}
}

View File

@@ -330,8 +330,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
private static readonly string ExplorerExePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "explorer.exe");
private bool _isNewPlusEnabled;
private string _templateLocation;
private bool _hideFileExtension;
@@ -358,7 +356,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
{
CopyTemplateExamples(_templateLocation);
Process.Start(CreateExplorerProcessStartInfo(_templateLocation));
var process = new ProcessStartInfo()
{
FileName = _templateLocation,
UseShellExecute = true,
};
Process.Start(process);
}
catch (Exception ex)
{
@@ -366,15 +369,6 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
}
}
private static ProcessStartInfo CreateExplorerProcessStartInfo(string templateLocation)
{
return new ProcessStartInfo
{
FileName = ExplorerExePath,
Arguments = $"\"{templateLocation}\"",
};
}
private async void PickNewTemplateFolder()
{
var newPath = await PickFolderDialog();