Merge branch 'main' of https://github.com/microsoft/PowerToys into leilzh/adclean
1
.github/actions/spell-check/excludes.txt
vendored
@@ -105,6 +105,7 @@
|
|||||||
^src/common/notifications/BackgroundActivatorDLL/cpp\.hint$
|
^src/common/notifications/BackgroundActivatorDLL/cpp\.hint$
|
||||||
^src/common/sysinternals/Eula/
|
^src/common/sysinternals/Eula/
|
||||||
^src/modules/cmdpal/doc/initial-sdk-spec/list-elements-mock-002\.pdn$
|
^src/modules/cmdpal/doc/initial-sdk-spec/list-elements-mock-002\.pdn$
|
||||||
|
^src/modules/cmdpal/ext/SamplePagesExtension/Pages/SampleMarkdownImagesPage\.cs$
|
||||||
^src/modules/colorPicker/ColorPickerUI/Shaders/GridShader\.cso$
|
^src/modules/colorPicker/ColorPickerUI/Shaders/GridShader\.cso$
|
||||||
^src/modules/launcher/Plugins/Microsoft\.PowerToys\.Run\.Plugin\.TimeDate/Properties/
|
^src/modules/launcher/Plugins/Microsoft\.PowerToys\.Run\.Plugin\.TimeDate/Properties/
|
||||||
^src/modules/MouseUtils/MouseJumpUI/MainForm\.resx$
|
^src/modules/MouseUtils/MouseJumpUI/MainForm\.resx$
|
||||||
|
|||||||
15
.github/actions/spell-check/expect.txt
vendored
@@ -65,6 +65,7 @@ APIIs
|
|||||||
Apm
|
Apm
|
||||||
APPBARDATA
|
APPBARDATA
|
||||||
APPEXECLINK
|
APPEXECLINK
|
||||||
|
appext
|
||||||
APPLICATIONFRAMEHOST
|
APPLICATIONFRAMEHOST
|
||||||
appmanifest
|
appmanifest
|
||||||
APPMODEL
|
APPMODEL
|
||||||
@@ -100,7 +101,6 @@ ATX
|
|||||||
ATRIOX
|
ATRIOX
|
||||||
aumid
|
aumid
|
||||||
authenticode
|
authenticode
|
||||||
Authenticode
|
|
||||||
AUTOBUDDY
|
AUTOBUDDY
|
||||||
AUTOCHECKBOX
|
AUTOCHECKBOX
|
||||||
AUTOHIDE
|
AUTOHIDE
|
||||||
@@ -187,6 +187,7 @@ CAPTUREBLT
|
|||||||
CAPTURECHANGED
|
CAPTURECHANGED
|
||||||
CARETBLINKING
|
CARETBLINKING
|
||||||
CAtl
|
CAtl
|
||||||
|
CBN
|
||||||
cch
|
cch
|
||||||
CCHDEVICENAME
|
CCHDEVICENAME
|
||||||
CCHFORMNAME
|
CCHFORMNAME
|
||||||
@@ -315,7 +316,6 @@ CURSORINFO
|
|||||||
cursorpos
|
cursorpos
|
||||||
CURSORSHOWING
|
CURSORSHOWING
|
||||||
CURSORWRAP
|
CURSORWRAP
|
||||||
CursorWrap
|
|
||||||
customaction
|
customaction
|
||||||
CUSTOMACTIONTEST
|
CUSTOMACTIONTEST
|
||||||
CUSTOMFORMATPLACEHOLDER
|
CUSTOMFORMATPLACEHOLDER
|
||||||
@@ -415,6 +415,9 @@ DNLEN
|
|||||||
DONOTROUND
|
DONOTROUND
|
||||||
DONTVALIDATEPATH
|
DONTVALIDATEPATH
|
||||||
dotnet
|
dotnet
|
||||||
|
downsampled
|
||||||
|
downsampling
|
||||||
|
Downsampled
|
||||||
downscale
|
downscale
|
||||||
DPICHANGED
|
DPICHANGED
|
||||||
DPIs
|
DPIs
|
||||||
@@ -431,7 +434,6 @@ DSTINVERT
|
|||||||
DString
|
DString
|
||||||
DSVG
|
DSVG
|
||||||
dto
|
dto
|
||||||
DTo
|
|
||||||
DUMMYUNIONNAME
|
DUMMYUNIONNAME
|
||||||
dutil
|
dutil
|
||||||
DVASPECT
|
DVASPECT
|
||||||
@@ -465,7 +467,6 @@ EDITKEYBOARD
|
|||||||
EDITSHORTCUTS
|
EDITSHORTCUTS
|
||||||
EDITTEXT
|
EDITTEXT
|
||||||
EFile
|
EFile
|
||||||
ekus
|
|
||||||
eku
|
eku
|
||||||
emojis
|
emojis
|
||||||
ENABLEDELAYEDEXPANSION
|
ENABLEDELAYEDEXPANSION
|
||||||
@@ -601,6 +602,7 @@ getfilesiginforedist
|
|||||||
geolocator
|
geolocator
|
||||||
GETHOTKEY
|
GETHOTKEY
|
||||||
GETICON
|
GETICON
|
||||||
|
GETLBTEXT
|
||||||
GETMINMAXINFO
|
GETMINMAXINFO
|
||||||
GETNONCLIENTMETRICS
|
GETNONCLIENTMETRICS
|
||||||
GETPROPERTYSTOREFLAGS
|
GETPROPERTYSTOREFLAGS
|
||||||
@@ -608,6 +610,7 @@ GETSCREENSAVERRUNNING
|
|||||||
GETSECKEY
|
GETSECKEY
|
||||||
GETSTICKYKEYS
|
GETSTICKYKEYS
|
||||||
GETTEXTLENGTH
|
GETTEXTLENGTH
|
||||||
|
GIFs
|
||||||
gitmodules
|
gitmodules
|
||||||
GHND
|
GHND
|
||||||
GMEM
|
GMEM
|
||||||
@@ -618,6 +621,7 @@ GPOCA
|
|||||||
gpp
|
gpp
|
||||||
gpu
|
gpu
|
||||||
gradians
|
gradians
|
||||||
|
grctlext
|
||||||
Gridcustomlayout
|
Gridcustomlayout
|
||||||
GSM
|
GSM
|
||||||
gtm
|
gtm
|
||||||
@@ -1150,7 +1154,6 @@ NONCLIENTMETRICSW
|
|||||||
NONELEVATED
|
NONELEVATED
|
||||||
nonspace
|
nonspace
|
||||||
nonstd
|
nonstd
|
||||||
nullrefs
|
|
||||||
NOOWNERZORDER
|
NOOWNERZORDER
|
||||||
NOPARENTNOTIFY
|
NOPARENTNOTIFY
|
||||||
NOPREFIX
|
NOPREFIX
|
||||||
@@ -1190,8 +1193,8 @@ ntfs
|
|||||||
NTSTATUS
|
NTSTATUS
|
||||||
NTSYSAPI
|
NTSYSAPI
|
||||||
NULLCURSOR
|
NULLCURSOR
|
||||||
nullref
|
|
||||||
nullonfailure
|
nullonfailure
|
||||||
|
nullref
|
||||||
numberbox
|
numberbox
|
||||||
nwc
|
nwc
|
||||||
ocr
|
ocr
|
||||||
|
|||||||
2
.github/actions/spell-check/patterns.txt
vendored
@@ -253,7 +253,7 @@ _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING
|
|||||||
|
|
||||||
# hit-count: 1 file-count: 1
|
# hit-count: 1 file-count: 1
|
||||||
# Amazon
|
# Amazon
|
||||||
\bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|)[^"'\s]+
|
\bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|)
|
||||||
|
|
||||||
# hit-count: 3 file-count: 3
|
# hit-count: 3 file-count: 3
|
||||||
# imgur
|
# imgur
|
||||||
|
|||||||
@@ -52,8 +52,6 @@ extends:
|
|||||||
name: SHINE-INT-S
|
name: SHINE-INT-S
|
||||||
${{ if eq(parameters.useVSPreview, true) }}:
|
${{ if eq(parameters.useVSPreview, true) }}:
|
||||||
demands: ImageOverride -equals SHINE-VS17-Preview
|
demands: ImageOverride -equals SHINE-VS17-Preview
|
||||||
${{ else }}:
|
|
||||||
image: SHINE-VS17-Latest
|
|
||||||
os: windows
|
os: windows
|
||||||
sdl:
|
sdl:
|
||||||
tsa:
|
tsa:
|
||||||
@@ -75,7 +73,6 @@ extends:
|
|||||||
name: SHINE-INT-L
|
name: SHINE-INT-L
|
||||||
demands:
|
demands:
|
||||||
# Our INT agents have a large disk mounted at P:\
|
# Our INT agents have a large disk mounted at P:\
|
||||||
- WorkFolder -equals P:\_work
|
|
||||||
- ${{ if eq(parameters.useVSPreview, true) }}:
|
- ${{ if eq(parameters.useVSPreview, true) }}:
|
||||||
- ImageOverride -equals SHINE-VS17-Preview
|
- ImageOverride -equals SHINE-VS17-Preview
|
||||||
os: windows
|
os: windows
|
||||||
@@ -126,7 +123,6 @@ extends:
|
|||||||
parameters:
|
parameters:
|
||||||
pool:
|
pool:
|
||||||
name: SHINE-INT-L
|
name: SHINE-INT-L
|
||||||
image: SHINE-VS17-Latest
|
|
||||||
os: windows
|
os: windows
|
||||||
official: true
|
official: true
|
||||||
codeSign: true
|
codeSign: true
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ jobs:
|
|||||||
${{ else }}:
|
${{ else }}:
|
||||||
OutputBuildPlatform: ${{ platform }}
|
OutputBuildPlatform: ${{ platform }}
|
||||||
variables:
|
variables:
|
||||||
|
NUGET_PACKAGES: 'C:\NuGetPackages' # Some of our build steps cache these here... and it was apparently part of the global environment
|
||||||
MakeAppxPath: 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x86\MakeAppx.exe'
|
MakeAppxPath: 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x86\MakeAppx.exe'
|
||||||
# Azure DevOps abhors a vacuum
|
# Azure DevOps abhors a vacuum
|
||||||
# If these are blank, expansion will fail later on... which will result in direct substitution of the variable *names*
|
# If these are blank, expansion will fail later on... which will result in direct substitution of the variable *names*
|
||||||
@@ -139,6 +140,10 @@ jobs:
|
|||||||
- output: pipelineArtifact
|
- output: pipelineArtifact
|
||||||
artifactName: $(JobOutputArtifactName)
|
artifactName: $(JobOutputArtifactName)
|
||||||
targetPath: $(Build.ArtifactStagingDirectory)
|
targetPath: $(Build.ArtifactStagingDirectory)
|
||||||
|
- output: pipelineArtifact
|
||||||
|
artifactName: $(JobOutputArtifactName)-failure-$(System.JobAttempt)
|
||||||
|
targetPath: $(LogOutputDirectory)
|
||||||
|
condition: or(failed(), canceled())
|
||||||
steps:
|
steps:
|
||||||
- checkout: self
|
- checkout: self
|
||||||
clean: true
|
clean: true
|
||||||
@@ -395,7 +400,7 @@ jobs:
|
|||||||
### HACK: On ARM64 builds, building an app with Windows App SDK copies the x64 WebView2 dll instead of the ARM64 one. This task makes sure the right dll is used.
|
### HACK: On ARM64 builds, building an app with Windows App SDK copies the x64 WebView2 dll instead of the ARM64 one. This task makes sure the right dll is used.
|
||||||
- task: CopyFiles@2
|
- task: CopyFiles@2
|
||||||
displayName: HACK Copy core WebView2 ARM64 dll to output directory
|
displayName: HACK Copy core WebView2 ARM64 dll to output directory
|
||||||
condition: eq(variables['BuildPlatform'],'arm64')
|
condition: and(succeeded(), eq(variables['BuildPlatform'], 'arm64'))
|
||||||
inputs:
|
inputs:
|
||||||
contents: packages/Microsoft.Web.WebView2.1.0.2903.40/runtimes/win-ARM64/native_uap/Microsoft.Web.WebView2.Core.dll
|
contents: packages/Microsoft.Web.WebView2.1.0.2903.40/runtimes/win-ARM64/native_uap/Microsoft.Web.WebView2.Core.dll
|
||||||
targetFolder: $(Build.SourcesDirectory)/ARM64/Release/WinUI3Apps/
|
targetFolder: $(Build.SourcesDirectory)/ARM64/Release/WinUI3Apps/
|
||||||
@@ -434,11 +439,11 @@ jobs:
|
|||||||
inputs:
|
inputs:
|
||||||
testResultsFormat: VSTest
|
testResultsFormat: VSTest
|
||||||
testResultsFiles: '**/*.trx'
|
testResultsFiles: '**/*.trx'
|
||||||
condition: ne(variables['BuildPlatform'],'arm64')
|
condition: and(succeeded(), ne(variables['BuildPlatform'], 'arm64'))
|
||||||
|
|
||||||
# Native dlls
|
# Native dlls
|
||||||
- task: VSTest@2
|
- task: VSTest@2
|
||||||
condition: ne(variables['BuildPlatform'],'arm64') # No arm64 agents to run the tests.
|
condition: and(succeeded(), ne(variables['BuildPlatform'], 'arm64')) # No arm64 agents to run the tests.
|
||||||
displayName: 'Native Tests'
|
displayName: 'Native Tests'
|
||||||
inputs:
|
inputs:
|
||||||
platform: '$(BuildPlatform)'
|
platform: '$(BuildPlatform)'
|
||||||
|
|||||||
@@ -10,11 +10,11 @@
|
|||||||
|
|
||||||
<h3 align="center">
|
<h3 align="center">
|
||||||
<a href="#-installation">Installation</a>
|
<a href="#-installation">Installation</a>
|
||||||
<span> . </span>
|
<span> · </span>
|
||||||
<a href="https://aka.ms/powertoys-docs">Documentation</a>
|
<a href="https://aka.ms/powertoys-docs">Documentation</a>
|
||||||
<span> . </span>
|
<span> · </span>
|
||||||
<a href="https://aka.ms/powertoys-releaseblog">Blog</a>
|
<a href="https://aka.ms/powertoys-releaseblog">Blog</a>
|
||||||
<span> . </span>
|
<span> · </span>
|
||||||
<a href="#-whats-new">Release notes</a>
|
<a href="#-whats-new">Release notes</a>
|
||||||
</h3>
|
</h3>
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
|
|||||||
BIN
doc/images/icons/CursorWrap.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 21 KiB |
BIN
doc/images/icons/MouseJump.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 22 KiB |
175
src/common/UITestAutomation/SettingsConfigHelper.cs
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
// 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.Diagnostics;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Microsoft.PowerToys.Settings.UI.Library;
|
||||||
|
using Microsoft.PowerToys.Settings.UI.Library.Interfaces;
|
||||||
|
using Microsoft.PowerToys.Settings.UI.Library.Utilities;
|
||||||
|
|
||||||
|
namespace Microsoft.PowerToys.UITest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class for configuring PowerToys settings for UI tests.
|
||||||
|
/// </summary>
|
||||||
|
public class SettingsConfigHelper
|
||||||
|
{
|
||||||
|
private static readonly JsonSerializerOptions IndentedJsonOptions = new() { WriteIndented = true };
|
||||||
|
private static readonly SettingsUtils SettingsUtils = new SettingsUtils();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures global PowerToys settings to enable only specified modules and disable all others.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="modulesToEnable">Array of module names to enable (e.g., "Peek", "FancyZones"). All other modules will be disabled.</param>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when modulesToEnable is null.</exception>
|
||||||
|
/// <exception cref="InvalidOperationException">Thrown when settings file operations fail.</exception>
|
||||||
|
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "This is test code and will not be trimmed")]
|
||||||
|
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "This is test code and will not be AOT compiled")]
|
||||||
|
public static void ConfigureGlobalModuleSettings(params string[] modulesToEnable)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(modulesToEnable);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GeneralSettings settings;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
settings = SettingsUtils.GetSettingsOrDefault<GeneralSettings>();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"Failed to load settings, creating defaults: {ex.Message}");
|
||||||
|
settings = new GeneralSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
string settingsJson = settings.ToJsonString();
|
||||||
|
using (JsonDocument doc = JsonDocument.Parse(settingsJson))
|
||||||
|
{
|
||||||
|
var options = new JsonSerializerOptions { WriteIndented = true };
|
||||||
|
var root = doc.RootElement.Clone();
|
||||||
|
|
||||||
|
if (root.TryGetProperty("enabled", out var enabledElement))
|
||||||
|
{
|
||||||
|
var enabledModules = new Dictionary<string, bool>();
|
||||||
|
|
||||||
|
foreach (var property in enabledElement.EnumerateObject())
|
||||||
|
{
|
||||||
|
string moduleName = property.Name;
|
||||||
|
|
||||||
|
bool shouldEnable = Array.Exists(modulesToEnable, m => string.Equals(m, moduleName, StringComparison.Ordinal));
|
||||||
|
enabledModules[moduleName] = shouldEnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
var settingsDict = JsonSerializer.Deserialize<Dictionary<string, object>>(settingsJson);
|
||||||
|
if (settingsDict != null)
|
||||||
|
{
|
||||||
|
settingsDict["enabled"] = enabledModules;
|
||||||
|
settingsJson = JsonSerializer.Serialize(settingsDict, IndentedJsonOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsUtils.SaveSettings(settingsJson);
|
||||||
|
|
||||||
|
string enabledList = modulesToEnable.Length > 0 ? string.Join(", ", modulesToEnable) : "none";
|
||||||
|
Debug.WriteLine($"Successfully updated global settings");
|
||||||
|
Debug.WriteLine($"Enabled modules: {enabledList}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"ERROR in ConfigureGlobalModuleSettings: {ex.Message}");
|
||||||
|
throw new InvalidOperationException($"Failed to configure global module settings: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates a module's settings file. If the file doesn't exist, creates it with default content.
|
||||||
|
/// If the file exists, reads it and applies the provided update function to modify the settings.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="moduleName">The name of the module (e.g., "Peek", "FancyZones").</param>
|
||||||
|
/// <param name="defaultSettingsContent">The default JSON content to use if the settings file doesn't exist.</param>
|
||||||
|
/// <param name="updateSettingsAction">
|
||||||
|
/// A callback function that modifies the settings dictionary. The function receives the deserialized settings
|
||||||
|
/// and should modify it in-place. The function should accept a Dictionary<string, object> and not return a value.
|
||||||
|
/// Example: (settings) => { ((Dictionary<string, object>)settings["properties"])["SomeSetting"] = newValue; }
|
||||||
|
/// </param>
|
||||||
|
/// <exception cref="ArgumentNullException">Thrown when moduleName or updateSettingsAction is null.</exception>
|
||||||
|
/// <exception cref="InvalidOperationException">Thrown when settings file operations fail.</exception>
|
||||||
|
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "This is test code and will not be trimmed")]
|
||||||
|
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "This is test code and will not be AOT compiled")]
|
||||||
|
public static void UpdateModuleSettings(
|
||||||
|
string moduleName,
|
||||||
|
string defaultSettingsContent,
|
||||||
|
Action<Dictionary<string, object>> updateSettingsAction)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(moduleName);
|
||||||
|
ArgumentNullException.ThrowIfNull(updateSettingsAction);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Build the path to the module settings file
|
||||||
|
string powerToysSettingsDirectory = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||||
|
"Microsoft",
|
||||||
|
"PowerToys");
|
||||||
|
|
||||||
|
string moduleDirectory = Path.Combine(powerToysSettingsDirectory, moduleName);
|
||||||
|
string settingsPath = Path.Combine(moduleDirectory, "settings.json");
|
||||||
|
|
||||||
|
// Ensure directory exists
|
||||||
|
Directory.CreateDirectory(moduleDirectory);
|
||||||
|
|
||||||
|
// Read existing settings or use default
|
||||||
|
string existingJson = string.Empty;
|
||||||
|
if (File.Exists(settingsPath))
|
||||||
|
{
|
||||||
|
existingJson = File.ReadAllText(settingsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<string, object>? settings;
|
||||||
|
|
||||||
|
// If file doesn't exist or is empty, create from defaults
|
||||||
|
if (string.IsNullOrWhiteSpace(existingJson))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(defaultSettingsContent))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Default settings content must be provided when file doesn't exist.", nameof(defaultSettingsContent));
|
||||||
|
}
|
||||||
|
|
||||||
|
settings = JsonSerializer.Deserialize<Dictionary<string, object>>(defaultSettingsContent)
|
||||||
|
?? throw new InvalidOperationException($"Failed to deserialize default settings for {moduleName}");
|
||||||
|
|
||||||
|
Debug.WriteLine($"Created default settings for {moduleName} at {settingsPath}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Parse existing settings
|
||||||
|
settings = JsonSerializer.Deserialize<Dictionary<string, object>>(existingJson)
|
||||||
|
?? throw new InvalidOperationException($"Failed to deserialize existing settings for {moduleName}");
|
||||||
|
|
||||||
|
Debug.WriteLine($"Loaded existing settings for {moduleName} from {settingsPath}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the update action to modify settings
|
||||||
|
updateSettingsAction(settings);
|
||||||
|
|
||||||
|
// Serialize and save the updated settings using SettingsUtils
|
||||||
|
string updatedJson = JsonSerializer.Serialize(settings, IndentedJsonOptions);
|
||||||
|
SettingsUtils.SaveSettings(updatedJson, moduleName);
|
||||||
|
|
||||||
|
Debug.WriteLine($"Successfully updated settings for {moduleName}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"ERROR in UpdateModuleSettings for {moduleName}: {ex.Message}");
|
||||||
|
throw new InvalidOperationException($"Failed to update settings for {moduleName}: {ex.Message}", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<PublishAot>true</PublishAot>
|
<PublishAot>true</PublishAot>
|
||||||
<InvariantGlobalization>true</InvariantGlobalization>
|
<InvariantGlobalization>true</InvariantGlobalization>
|
||||||
<TargetFramework>net9.0-windows10.0.22621.0</TargetFramework>
|
<TargetFramework>net9.0-windows10.0.26100.0</TargetFramework>
|
||||||
<UseWindowsForms>true</UseWindowsForms>
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
<PublishTrimmed>false</PublishTrimmed>
|
<PublishTrimmed>false</PublishTrimmed>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -21,4 +21,8 @@
|
|||||||
<PackageReference Include="CoenM.ImageSharp.ImageHash" />
|
<PackageReference Include="CoenM.ImageSharp.ImageHash" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\settings-ui\Settings.UI.Library\Settings.UI.Library.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -542,7 +542,10 @@
|
|||||||
Source="{x:Bind ViewModel.ActiveAIProvider?.ServiceType, Mode=OneWay, Converter={StaticResource ServiceTypeToIconConverter}}" />
|
Source="{x:Bind ViewModel.ActiveAIProvider?.ServiceType, Mode=OneWay, Converter={StaticResource ServiceTypeToIconConverter}}" />
|
||||||
</DropDownButton.Content>
|
</DropDownButton.Content>
|
||||||
<DropDownButton.Flyout>
|
<DropDownButton.Flyout>
|
||||||
<Flyout Placement="Bottom" ShouldConstrainToRootBounds="False">
|
<Flyout
|
||||||
|
Opened="AIProviderFlyout_Opened"
|
||||||
|
Placement="Bottom"
|
||||||
|
ShouldConstrainToRootBounds="False">
|
||||||
<Grid
|
<Grid
|
||||||
Width="386"
|
Width="386"
|
||||||
Margin="-4"
|
Margin="-4"
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ namespace AdvancedPaste.Controls
|
|||||||
{
|
{
|
||||||
public OptionsViewModel ViewModel { get; private set; }
|
public OptionsViewModel ViewModel { get; private set; }
|
||||||
|
|
||||||
|
private bool _syncingProviderSelection;
|
||||||
|
|
||||||
public static readonly DependencyProperty PlaceholderTextProperty = DependencyProperty.Register(
|
public static readonly DependencyProperty PlaceholderTextProperty = DependencyProperty.Register(
|
||||||
nameof(PlaceholderText),
|
nameof(PlaceholderText),
|
||||||
typeof(string),
|
typeof(string),
|
||||||
@@ -74,6 +76,11 @@ namespace AdvancedPaste.Controls
|
|||||||
var state = ViewModel.IsBusy ? "LoadingState" : ViewModel.PasteActionError.HasText ? "ErrorState" : "DefaultState";
|
var state = ViewModel.IsBusy ? "LoadingState" : ViewModel.PasteActionError.HasText ? "ErrorState" : "DefaultState";
|
||||||
VisualStateManager.GoToState(this, state, true);
|
VisualStateManager.GoToState(this, state, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (e.PropertyName is nameof(ViewModel.ActiveAIProvider) or nameof(ViewModel.AIProviders))
|
||||||
|
{
|
||||||
|
SyncProviderSelection();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ViewModel_PreviewRequested(object sender, EventArgs e)
|
private void ViewModel_PreviewRequested(object sender, EventArgs e)
|
||||||
@@ -87,6 +94,7 @@ namespace AdvancedPaste.Controls
|
|||||||
private void Grid_Loaded(object sender, RoutedEventArgs e)
|
private void Grid_Loaded(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
InputTxtBox.Focus(FocusState.Programmatic);
|
InputTxtBox.Focus(FocusState.Programmatic);
|
||||||
|
SyncProviderSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
@@ -126,18 +134,56 @@ namespace AdvancedPaste.Controls
|
|||||||
Loader.IsLoading = loading;
|
Loader.IsLoading = loading;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SyncProviderSelection()
|
||||||
|
{
|
||||||
|
if (AIProviderListView is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_syncingProviderSelection = true;
|
||||||
|
AIProviderListView.SelectedItem = ViewModel.ActiveAIProvider;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_syncingProviderSelection = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AIProviderFlyout_Opened(object sender, object e)
|
||||||
|
{
|
||||||
|
SyncProviderSelection();
|
||||||
|
}
|
||||||
|
|
||||||
private async void AIProviderListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
private async void AIProviderListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (AIProviderListView.SelectedItem is PasteAIProviderDefinition provider)
|
if (_syncingProviderSelection)
|
||||||
{
|
{
|
||||||
if (ViewModel.SetActiveProviderCommand.CanExecute(provider))
|
return;
|
||||||
{
|
|
||||||
await ViewModel.SetActiveProviderCommand.ExecuteAsync(provider);
|
|
||||||
}
|
|
||||||
|
|
||||||
var flyout = FlyoutBase.GetAttachedFlyout(AIProviderButton);
|
|
||||||
flyout?.Hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var flyout = FlyoutBase.GetAttachedFlyout(AIProviderButton);
|
||||||
|
|
||||||
|
if (AIProviderListView.SelectedItem is not PasteAIProviderDefinition provider)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.Equals(ViewModel.ActiveAIProvider?.Id, provider.Id, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
flyout?.Hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ViewModel.SetActiveProviderCommand.CanExecute(provider))
|
||||||
|
{
|
||||||
|
await ViewModel.SetActiveProviderCommand.ExecuteAsync(provider);
|
||||||
|
SyncProviderSelection();
|
||||||
|
}
|
||||||
|
|
||||||
|
flyout?.Hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -280,13 +280,15 @@
|
|||||||
x:Uid="TermsLink"
|
x:Uid="TermsLink"
|
||||||
Padding="0"
|
Padding="0"
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
NavigateUri="https://openai.com/policies/terms-of-use" />
|
NavigateUri="{x:Bind ViewModel.TermsLinkUri, Mode=OneWay}"
|
||||||
|
Visibility="{x:Bind ViewModel.HasTermsLink, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||||
<HyperlinkButton
|
<HyperlinkButton
|
||||||
x:Name="PrivacyHyperLink"
|
x:Name="PrivacyHyperLink"
|
||||||
x:Uid="PrivacyLink"
|
x:Uid="PrivacyLink"
|
||||||
Padding="0"
|
Padding="0"
|
||||||
FontSize="12"
|
FontSize="12"
|
||||||
NavigateUri="https://openai.com/policies/privacy-policy" />
|
NavigateUri="{x:Bind ViewModel.PrivacyLinkUri, Mode=OneWay}"
|
||||||
|
Visibility="{x:Bind ViewModel.HasPrivacyLink, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Flyout>
|
</Flyout>
|
||||||
|
|||||||
@@ -71,6 +71,11 @@ namespace AdvancedPaste.ViewModels
|
|||||||
[NotifyPropertyChangedFor(nameof(IsCustomAIAvailable))]
|
[NotifyPropertyChangedFor(nameof(IsCustomAIAvailable))]
|
||||||
[NotifyPropertyChangedFor(nameof(AllowedAIProviders))]
|
[NotifyPropertyChangedFor(nameof(AllowedAIProviders))]
|
||||||
[NotifyPropertyChangedFor(nameof(ActiveAIProvider))]
|
[NotifyPropertyChangedFor(nameof(ActiveAIProvider))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(ActiveAIProviderTooltip))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(TermsLinkUri))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(PrivacyLinkUri))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(HasTermsLink))]
|
||||||
|
[NotifyPropertyChangedFor(nameof(HasPrivacyLink))]
|
||||||
private bool _isAllowedByGPO;
|
private bool _isAllowedByGPO;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
@@ -187,6 +192,35 @@ namespace AdvancedPaste.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AIServiceTypeMetadata GetActiveProviderMetadata()
|
||||||
|
{
|
||||||
|
var provider = ActiveAIProvider ?? AllowedAIProviders.FirstOrDefault();
|
||||||
|
var serviceType = provider?.ServiceTypeKind ?? AIServiceType.OpenAI;
|
||||||
|
return AIServiceTypeRegistry.GetMetadata(serviceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri TermsLinkUri
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var metadata = GetActiveProviderMetadata();
|
||||||
|
return metadata.HasTermsLink ? metadata.TermsUri : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri PrivacyLinkUri
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var metadata = GetActiveProviderMetadata();
|
||||||
|
return metadata.HasPrivacyLink ? metadata.PrivacyUri : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasTermsLink => GetActiveProviderMetadata().HasTermsLink;
|
||||||
|
|
||||||
|
public bool HasPrivacyLink => GetActiveProviderMetadata().HasPrivacyLink;
|
||||||
|
|
||||||
public bool ClipboardHasData => AvailableClipboardFormats != ClipboardFormat.None;
|
public bool ClipboardHasData => AvailableClipboardFormats != ClipboardFormat.None;
|
||||||
|
|
||||||
public bool ClipboardHasDataForCustomAI => PasteFormat.SupportsClipboardFormats(CustomAIFormat, AvailableClipboardFormats);
|
public bool ClipboardHasDataForCustomAI => PasteFormat.SupportsClipboardFormats(CustomAIFormat, AvailableClipboardFormats);
|
||||||
@@ -276,8 +310,8 @@ namespace AdvancedPaste.ViewModels
|
|||||||
OnPropertyChanged(nameof(IsAdvancedAIEnabled));
|
OnPropertyChanged(nameof(IsAdvancedAIEnabled));
|
||||||
OnPropertyChanged(nameof(AIProviders));
|
OnPropertyChanged(nameof(AIProviders));
|
||||||
OnPropertyChanged(nameof(AllowedAIProviders));
|
OnPropertyChanged(nameof(AllowedAIProviders));
|
||||||
OnPropertyChanged(nameof(ActiveAIProvider));
|
|
||||||
OnPropertyChanged(nameof(ActiveAIProviderTooltip));
|
NotifyActiveProviderChanged();
|
||||||
|
|
||||||
EnqueueRefreshPasteFormats();
|
EnqueueRefreshPasteFormats();
|
||||||
}
|
}
|
||||||
@@ -316,8 +350,17 @@ namespace AdvancedPaste.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NotifyActiveProviderChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NotifyActiveProviderChanged()
|
||||||
|
{
|
||||||
OnPropertyChanged(nameof(ActiveAIProvider));
|
OnPropertyChanged(nameof(ActiveAIProvider));
|
||||||
OnPropertyChanged(nameof(ActiveAIProviderTooltip));
|
OnPropertyChanged(nameof(ActiveAIProviderTooltip));
|
||||||
|
OnPropertyChanged(nameof(TermsLinkUri));
|
||||||
|
OnPropertyChanged(nameof(PrivacyLinkUri));
|
||||||
|
OnPropertyChanged(nameof(HasTermsLink));
|
||||||
|
OnPropertyChanged(nameof(HasPrivacyLink));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshPasteFormats()
|
private void RefreshPasteFormats()
|
||||||
@@ -836,6 +879,7 @@ namespace AdvancedPaste.ViewModels
|
|||||||
|
|
||||||
UpdateAIProviderActiveFlags();
|
UpdateAIProviderActiveFlags();
|
||||||
OnPropertyChanged(nameof(AIProviders));
|
OnPropertyChanged(nameof(AIProviders));
|
||||||
|
NotifyActiveProviderChanged();
|
||||||
EnqueueRefreshPasteFormats();
|
EnqueueRefreshPasteFormats();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
<PreprocessorDefinitions>_DEBUG;NEWPLUSSHELLEXTENSIONWIN10_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
<PreprocessorDefinitions>_DEBUG;NEWPLUSSHELLEXTENSIONWIN10_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
<ConformanceMode>true</ConformanceMode>
|
<ConformanceMode>true</ConformanceMode>
|
||||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||||
<AdditionalIncludeDirectories>..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu;$(MSBuildThisFileDirectory)Generated Files</AdditionalIncludeDirectories>
|
||||||
<CompileAsWinRT>false</CompileAsWinRT>
|
<CompileAsWinRT>false</CompileAsWinRT>
|
||||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
@@ -67,7 +67,7 @@
|
|||||||
<PreprocessorDefinitions>NDEBUG;NEWPLUSSHELLEXTENSIONWIN10_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
<PreprocessorDefinitions>NDEBUG;NEWPLUSSHELLEXTENSIONWIN10_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||||
<ConformanceMode>true</ConformanceMode>
|
<ConformanceMode>true</ConformanceMode>
|
||||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||||
<AdditionalIncludeDirectories>..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>..\..\..\common\Telemetry;..\..\;..\..\..\;%(AdditionalIncludeDirectories);..\NewShellExtensionContextMenu;$(MSBuildThisFileDirectory)Generated Files</AdditionalIncludeDirectories>
|
||||||
<CompileAsWinRT>false</CompileAsWinRT>
|
<CompileAsWinRT>false</CompileAsWinRT>
|
||||||
<LanguageStandard>stdcpplatest</LanguageStandard>
|
<LanguageStandard>stdcpplatest</LanguageStandard>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|||||||
548
src/modules/ZoomIt/ZoomIt/GifRecordingSession.cpp
Normal file
@@ -0,0 +1,548 @@
|
|||||||
|
//==============================================================================
|
||||||
|
//
|
||||||
|
// Zoomit
|
||||||
|
// Sysinternals - www.sysinternals.com
|
||||||
|
//
|
||||||
|
// GIF recording support using Windows Imaging Component (WIC)
|
||||||
|
//
|
||||||
|
//==============================================================================
|
||||||
|
#include "pch.h"
|
||||||
|
#include "GifRecordingSession.h"
|
||||||
|
#include "CaptureFrameWait.h"
|
||||||
|
#include <shcore.h>
|
||||||
|
|
||||||
|
extern DWORD g_RecordScaling;
|
||||||
|
|
||||||
|
namespace winrt
|
||||||
|
{
|
||||||
|
using namespace Windows::Foundation;
|
||||||
|
using namespace Windows::Graphics;
|
||||||
|
using namespace Windows::Graphics::Capture;
|
||||||
|
using namespace Windows::Graphics::DirectX;
|
||||||
|
using namespace Windows::Graphics::DirectX::Direct3D11;
|
||||||
|
using namespace Windows::Storage;
|
||||||
|
using namespace Windows::UI::Composition;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace util
|
||||||
|
{
|
||||||
|
using namespace robmikh::common::uwp;
|
||||||
|
}
|
||||||
|
|
||||||
|
const float CLEAR_COLOR[] = { 0.0f, 0.0f, 0.0f, 1.0f };
|
||||||
|
|
||||||
|
int32_t EnsureEvenGif(int32_t value)
|
||||||
|
{
|
||||||
|
if (value % 2 == 0)
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return value + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// GifRecordingSession::GifRecordingSession
|
||||||
|
//
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
GifRecordingSession::GifRecordingSession(
|
||||||
|
winrt::IDirect3DDevice const& device,
|
||||||
|
winrt::GraphicsCaptureItem const& item,
|
||||||
|
RECT const cropRect,
|
||||||
|
uint32_t frameRate,
|
||||||
|
winrt::Streams::IRandomAccessStream const& stream)
|
||||||
|
{
|
||||||
|
m_device = device;
|
||||||
|
m_d3dDevice = GetDXGIInterfaceFromObject<ID3D11Device>(m_device);
|
||||||
|
m_d3dDevice->GetImmediateContext(m_d3dContext.put());
|
||||||
|
m_item = item;
|
||||||
|
m_frameRate = frameRate;
|
||||||
|
m_stream = stream;
|
||||||
|
|
||||||
|
auto itemSize = item.Size();
|
||||||
|
auto inputWidth = EnsureEvenGif(itemSize.Width);
|
||||||
|
auto inputHeight = EnsureEvenGif(itemSize.Height);
|
||||||
|
m_frameWait = std::make_shared<CaptureFrameWait>(m_device, m_item, winrt::SizeInt32{ inputWidth, inputHeight });
|
||||||
|
auto weakPointer{ std::weak_ptr{ m_frameWait } };
|
||||||
|
m_itemClosed = item.Closed(winrt::auto_revoke, [weakPointer](auto&, auto&)
|
||||||
|
{
|
||||||
|
auto sharedPointer{ weakPointer.lock() };
|
||||||
|
if (sharedPointer)
|
||||||
|
{
|
||||||
|
sharedPointer->StopCapture();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get crop dimension
|
||||||
|
if ((cropRect.right - cropRect.left) != 0)
|
||||||
|
{
|
||||||
|
m_rcCrop = cropRect;
|
||||||
|
m_frameWait->ShowCaptureBorder(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_rcCrop.left = 0;
|
||||||
|
m_rcCrop.top = 0;
|
||||||
|
m_rcCrop.right = inputWidth;
|
||||||
|
m_rcCrop.bottom = inputHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply scaling
|
||||||
|
constexpr int c_minimumSize = 34;
|
||||||
|
auto scaledWidth = MulDiv(m_rcCrop.right - m_rcCrop.left, g_RecordScaling, 100);
|
||||||
|
auto scaledHeight = MulDiv(m_rcCrop.bottom - m_rcCrop.top, g_RecordScaling, 100);
|
||||||
|
m_width = scaledWidth;
|
||||||
|
m_height = scaledHeight;
|
||||||
|
if (m_width < c_minimumSize)
|
||||||
|
{
|
||||||
|
m_width = c_minimumSize;
|
||||||
|
m_height = MulDiv(m_height, m_width, scaledWidth);
|
||||||
|
}
|
||||||
|
if (m_height < c_minimumSize)
|
||||||
|
{
|
||||||
|
m_height = c_minimumSize;
|
||||||
|
m_width = MulDiv(m_width, m_height, scaledHeight);
|
||||||
|
}
|
||||||
|
if (m_width > inputWidth)
|
||||||
|
{
|
||||||
|
m_width = inputWidth;
|
||||||
|
m_height = c_minimumSize, MulDiv(m_height, scaledWidth, m_width);
|
||||||
|
}
|
||||||
|
if (m_height > inputHeight)
|
||||||
|
{
|
||||||
|
m_height = inputHeight;
|
||||||
|
m_width = c_minimumSize, MulDiv(m_width, scaledHeight, m_height);
|
||||||
|
}
|
||||||
|
m_width = EnsureEvenGif(m_width);
|
||||||
|
m_height = EnsureEvenGif(m_height);
|
||||||
|
|
||||||
|
m_frameDelay = (frameRate > 0) ? (100 / frameRate) : 15;
|
||||||
|
|
||||||
|
// Initialize WIC
|
||||||
|
winrt::check_hresult(CoCreateInstance(
|
||||||
|
CLSID_WICImagingFactory,
|
||||||
|
nullptr,
|
||||||
|
CLSCTX_INPROC_SERVER,
|
||||||
|
IID_PPV_ARGS(m_wicFactory.put())));
|
||||||
|
|
||||||
|
// Create WIC stream from IRandomAccessStream
|
||||||
|
winrt::check_hresult(m_wicFactory->CreateStream(m_wicStream.put()));
|
||||||
|
|
||||||
|
// Get the IStream from the IRandomAccessStream
|
||||||
|
winrt::com_ptr<IStream> streamInterop;
|
||||||
|
winrt::check_hresult(CreateStreamOverRandomAccessStream(
|
||||||
|
winrt::get_unknown(stream),
|
||||||
|
IID_PPV_ARGS(streamInterop.put())));
|
||||||
|
winrt::check_hresult(m_wicStream->InitializeFromIStream(streamInterop.get()));
|
||||||
|
|
||||||
|
// Create GIF encoder
|
||||||
|
winrt::check_hresult(m_wicFactory->CreateEncoder(
|
||||||
|
GUID_ContainerFormatGif,
|
||||||
|
nullptr,
|
||||||
|
m_gifEncoder.put()));
|
||||||
|
|
||||||
|
winrt::check_hresult(m_gifEncoder->Initialize(m_wicStream.get(), WICBitmapEncoderNoCache));
|
||||||
|
|
||||||
|
// Set global GIF metadata for looping (NETSCAPE2.0 application extension)
|
||||||
|
try
|
||||||
|
{
|
||||||
|
winrt::com_ptr<IWICMetadataQueryWriter> encoderMetadataWriter;
|
||||||
|
if (SUCCEEDED(m_gifEncoder->GetMetadataQueryWriter(encoderMetadataWriter.put())) && encoderMetadataWriter)
|
||||||
|
{
|
||||||
|
OutputDebugStringW(L"Setting NETSCAPE2.0 looping extension on encoder...\n");
|
||||||
|
|
||||||
|
// Set application extension
|
||||||
|
PROPVARIANT propValue;
|
||||||
|
PropVariantInit(&propValue);
|
||||||
|
propValue.vt = VT_UI1 | VT_VECTOR;
|
||||||
|
propValue.caub.cElems = 11;
|
||||||
|
propValue.caub.pElems = static_cast<UCHAR*>(CoTaskMemAlloc(11));
|
||||||
|
if (propValue.caub.pElems != nullptr)
|
||||||
|
{
|
||||||
|
memcpy(propValue.caub.pElems, "NETSCAPE2.0", 11);
|
||||||
|
HRESULT hr = encoderMetadataWriter->SetMetadataByName(L"/appext/application", &propValue);
|
||||||
|
if (SUCCEEDED(hr))
|
||||||
|
{
|
||||||
|
OutputDebugStringW(L"Encoder application extension set successfully\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OutputDebugStringW(L"Failed to set encoder application extension\n");
|
||||||
|
}
|
||||||
|
PropVariantClear(&propValue);
|
||||||
|
|
||||||
|
// Set loop count (0 = infinite)
|
||||||
|
PropVariantInit(&propValue);
|
||||||
|
propValue.vt = VT_UI1 | VT_VECTOR;
|
||||||
|
propValue.caub.cElems = 5;
|
||||||
|
propValue.caub.pElems = static_cast<UCHAR*>(CoTaskMemAlloc(5));
|
||||||
|
if (propValue.caub.pElems != nullptr)
|
||||||
|
{
|
||||||
|
propValue.caub.pElems[0] = 3;
|
||||||
|
propValue.caub.pElems[1] = 1;
|
||||||
|
propValue.caub.pElems[2] = 0;
|
||||||
|
propValue.caub.pElems[3] = 0;
|
||||||
|
propValue.caub.pElems[4] = 0;
|
||||||
|
hr = encoderMetadataWriter->SetMetadataByName(L"/appext/data", &propValue);
|
||||||
|
if (SUCCEEDED(hr))
|
||||||
|
{
|
||||||
|
OutputDebugStringW(L"Encoder loop count set successfully\n");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OutputDebugStringW(L"Failed to set encoder loop count\n");
|
||||||
|
}
|
||||||
|
PropVariantClear(&propValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OutputDebugStringW(L"Failed to get encoder metadata writer\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
OutputDebugStringW(L"Warning: Failed to set GIF encoder looping metadata\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// GifRecordingSession::~GifRecordingSession
|
||||||
|
//
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
GifRecordingSession::~GifRecordingSession()
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// GifRecordingSession::Create
|
||||||
|
//
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
std::shared_ptr<GifRecordingSession> GifRecordingSession::Create(
|
||||||
|
winrt::IDirect3DDevice const& device,
|
||||||
|
winrt::GraphicsCaptureItem const& item,
|
||||||
|
RECT const& crop,
|
||||||
|
uint32_t frameRate,
|
||||||
|
winrt::Streams::IRandomAccessStream const& stream)
|
||||||
|
{
|
||||||
|
return std::shared_ptr<GifRecordingSession>(new GifRecordingSession(device, item, crop, frameRate, stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// GifRecordingSession::EncodeFrame
|
||||||
|
//
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
HRESULT GifRecordingSession::EncodeFrame(ID3D11Texture2D* frameTexture)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Create a staging texture for CPU access
|
||||||
|
D3D11_TEXTURE2D_DESC frameDesc;
|
||||||
|
frameTexture->GetDesc(&frameDesc);
|
||||||
|
|
||||||
|
// GIF encoding with palette generation is VERY slow at high resolutions (4K takes 1 second per frame!)
|
||||||
|
|
||||||
|
UINT targetWidth = frameDesc.Width;
|
||||||
|
UINT targetHeight = frameDesc.Height;
|
||||||
|
|
||||||
|
if (frameDesc.Width > static_cast<uint32_t>(m_width) || frameDesc.Height > static_cast<uint32_t>(m_height))
|
||||||
|
{
|
||||||
|
float scaleX = static_cast<float>(m_width) / frameDesc.Width;
|
||||||
|
float scaleY = static_cast<float>(m_height) / frameDesc.Height;
|
||||||
|
float scale = min(scaleX, scaleY);
|
||||||
|
|
||||||
|
targetWidth = static_cast<UINT>(frameDesc.Width * scale);
|
||||||
|
targetHeight = static_cast<UINT>(frameDesc.Height * scale);
|
||||||
|
|
||||||
|
// Ensure even dimensions for GIF
|
||||||
|
targetWidth = (targetWidth / 2) * 2;
|
||||||
|
targetHeight = (targetHeight / 2) * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
D3D11_TEXTURE2D_DESC stagingDesc = frameDesc;
|
||||||
|
stagingDesc.Usage = D3D11_USAGE_STAGING;
|
||||||
|
stagingDesc.BindFlags = 0;
|
||||||
|
stagingDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||||
|
stagingDesc.MiscFlags = 0;
|
||||||
|
|
||||||
|
winrt::com_ptr<ID3D11Texture2D> stagingTexture;
|
||||||
|
winrt::check_hresult(m_d3dDevice->CreateTexture2D(&stagingDesc, nullptr, stagingTexture.put()));
|
||||||
|
|
||||||
|
// Copy the frame to staging texture
|
||||||
|
m_d3dContext->CopyResource(stagingTexture.get(), frameTexture);
|
||||||
|
|
||||||
|
// Map the staging texture
|
||||||
|
D3D11_MAPPED_SUBRESOURCE mappedResource;
|
||||||
|
winrt::check_hresult(m_d3dContext->Map(stagingTexture.get(), 0, D3D11_MAP_READ, 0, &mappedResource));
|
||||||
|
|
||||||
|
// Create a new frame in the GIF
|
||||||
|
winrt::com_ptr<IWICBitmapFrameEncode> frameEncode;
|
||||||
|
winrt::com_ptr<IPropertyBag2> propertyBag;
|
||||||
|
winrt::check_hresult(m_gifEncoder->CreateNewFrame(frameEncode.put(), propertyBag.put()));
|
||||||
|
|
||||||
|
// Initialize the frame encoder with property bag
|
||||||
|
winrt::check_hresult(frameEncode->Initialize(propertyBag.get()));
|
||||||
|
|
||||||
|
// CRITICAL: For GIF, we MUST set size and pixel format BEFORE WriteSource
|
||||||
|
// Use target dimensions (may be downsampled)
|
||||||
|
winrt::check_hresult(frameEncode->SetSize(targetWidth, targetHeight));
|
||||||
|
|
||||||
|
// Set the pixel format to 8-bit indexed (required for GIF)
|
||||||
|
WICPixelFormatGUID pixelFormat = GUID_WICPixelFormat8bppIndexed;
|
||||||
|
winrt::check_hresult(frameEncode->SetPixelFormat(&pixelFormat));
|
||||||
|
|
||||||
|
// Create a WIC bitmap from the BGRA texture data
|
||||||
|
winrt::com_ptr<IWICBitmap> sourceBitmap;
|
||||||
|
winrt::check_hresult(m_wicFactory->CreateBitmapFromMemory(
|
||||||
|
frameDesc.Width,
|
||||||
|
frameDesc.Height,
|
||||||
|
GUID_WICPixelFormat32bppBGRA,
|
||||||
|
mappedResource.RowPitch,
|
||||||
|
frameDesc.Height * mappedResource.RowPitch,
|
||||||
|
static_cast<BYTE*>(mappedResource.pData),
|
||||||
|
sourceBitmap.put()));
|
||||||
|
|
||||||
|
// If we need downsampling, use WIC scaler
|
||||||
|
winrt::com_ptr<IWICBitmapSource> finalSource = sourceBitmap;
|
||||||
|
if (targetWidth != frameDesc.Width || targetHeight != frameDesc.Height)
|
||||||
|
{
|
||||||
|
winrt::com_ptr<IWICBitmapScaler> scaler;
|
||||||
|
winrt::check_hresult(m_wicFactory->CreateBitmapScaler(scaler.put()));
|
||||||
|
winrt::check_hresult(scaler->Initialize(
|
||||||
|
sourceBitmap.get(),
|
||||||
|
targetWidth,
|
||||||
|
targetHeight,
|
||||||
|
WICBitmapInterpolationModeHighQualityCubic));
|
||||||
|
finalSource = scaler;
|
||||||
|
|
||||||
|
OutputDebugStringW((L"Downsampled from " + std::to_wstring(frameDesc.Width) + L"x" + std::to_wstring(frameDesc.Height) +
|
||||||
|
L" to " + std::to_wstring(targetWidth) + L"x" + std::to_wstring(targetHeight) + L"\n").c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use WriteSource - WIC will handle the BGRA to 8bpp indexed conversion
|
||||||
|
winrt::check_hresult(frameEncode->WriteSource(finalSource.get(), nullptr));
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
winrt::com_ptr<IWICMetadataQueryWriter> frameMetadataWriter;
|
||||||
|
if (SUCCEEDED(frameEncode->GetMetadataQueryWriter(frameMetadataWriter.put())) && frameMetadataWriter)
|
||||||
|
{
|
||||||
|
// Set the frame delay in the metadata (in hundredths of a second)
|
||||||
|
PROPVARIANT propValue;
|
||||||
|
PropVariantInit(&propValue);
|
||||||
|
propValue.vt = VT_UI2;
|
||||||
|
propValue.uiVal = static_cast<USHORT>(m_frameDelay);
|
||||||
|
frameMetadataWriter->SetMetadataByName(L"/grctlext/Delay", &propValue);
|
||||||
|
PropVariantClear(&propValue);
|
||||||
|
|
||||||
|
// Set disposal method (2 = restore to background, needed for animation)
|
||||||
|
PropVariantInit(&propValue);
|
||||||
|
propValue.vt = VT_UI1;
|
||||||
|
propValue.bVal = 2; // Disposal method: restore to background color
|
||||||
|
frameMetadataWriter->SetMetadataByName(L"/grctlext/Disposal", &propValue);
|
||||||
|
PropVariantClear(&propValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
// Metadata setting failed, continue anyway
|
||||||
|
OutputDebugStringW(L"Warning: Failed to set GIF frame metadata\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit the frame
|
||||||
|
OutputDebugStringW(L"About to commit frame to encoder...\n");
|
||||||
|
winrt::check_hresult(frameEncode->Commit());
|
||||||
|
OutputDebugStringW(L"Frame committed successfully\n");
|
||||||
|
|
||||||
|
// Unmap the staging texture
|
||||||
|
m_d3dContext->Unmap(stagingTexture.get(), 0);
|
||||||
|
|
||||||
|
// Increment and log frame count
|
||||||
|
m_frameCount++;
|
||||||
|
OutputDebugStringW((L"GIF Frame #" + std::to_wstring(m_frameCount) + L" fully encoded and committed\n").c_str());
|
||||||
|
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
catch (const winrt::hresult_error& error)
|
||||||
|
{
|
||||||
|
OutputDebugStringW(error.message().c_str());
|
||||||
|
return error.code();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// GifRecordingSession::StartAsync
|
||||||
|
//
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
winrt::IAsyncAction GifRecordingSession::StartAsync()
|
||||||
|
{
|
||||||
|
auto expected = false;
|
||||||
|
if (m_isRecording.compare_exchange_strong(expected, true))
|
||||||
|
{
|
||||||
|
auto self = shared_from_this();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Start capturing frames
|
||||||
|
auto frameStartTime = std::chrono::high_resolution_clock::now();
|
||||||
|
int captureAttempts = 0;
|
||||||
|
int successfulCaptures = 0;
|
||||||
|
int duplicatedFrames = 0;
|
||||||
|
|
||||||
|
// Keep track of the last frame to duplicate when needed
|
||||||
|
winrt::com_ptr<ID3D11Texture2D> lastCroppedTexture;
|
||||||
|
|
||||||
|
while (m_isRecording && !m_closed)
|
||||||
|
{
|
||||||
|
captureAttempts++;
|
||||||
|
auto frame = m_frameWait->TryGetNextFrame();
|
||||||
|
|
||||||
|
winrt::com_ptr<ID3D11Texture2D> croppedTexture;
|
||||||
|
|
||||||
|
if (frame)
|
||||||
|
{
|
||||||
|
successfulCaptures++;
|
||||||
|
auto contentSize = frame->ContentSize;
|
||||||
|
auto frameTexture = GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame->FrameTexture);
|
||||||
|
D3D11_TEXTURE2D_DESC desc = {};
|
||||||
|
frameTexture->GetDesc(&desc);
|
||||||
|
|
||||||
|
// Use the smaller of the crop size or content size
|
||||||
|
auto width = min(m_rcCrop.right - m_rcCrop.left, contentSize.Width);
|
||||||
|
auto height = min(m_rcCrop.bottom - m_rcCrop.top, contentSize.Height);
|
||||||
|
|
||||||
|
D3D11_TEXTURE2D_DESC croppedDesc = {};
|
||||||
|
croppedDesc.Width = width;
|
||||||
|
croppedDesc.Height = height;
|
||||||
|
croppedDesc.MipLevels = 1;
|
||||||
|
croppedDesc.ArraySize = 1;
|
||||||
|
croppedDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||||
|
croppedDesc.SampleDesc.Count = 1;
|
||||||
|
croppedDesc.Usage = D3D11_USAGE_DEFAULT;
|
||||||
|
croppedDesc.BindFlags = D3D11_BIND_RENDER_TARGET;
|
||||||
|
|
||||||
|
winrt::check_hresult(m_d3dDevice->CreateTexture2D(&croppedDesc, nullptr, croppedTexture.put()));
|
||||||
|
|
||||||
|
// Set the content region to copy and clamp the coordinates
|
||||||
|
D3D11_BOX region = {};
|
||||||
|
region.left = std::clamp(m_rcCrop.left, static_cast<LONG>(0), static_cast<LONG>(desc.Width));
|
||||||
|
region.right = std::clamp(m_rcCrop.left + width, static_cast<LONG>(0), static_cast<LONG>(desc.Width));
|
||||||
|
region.top = std::clamp(m_rcCrop.top, static_cast<LONG>(0), static_cast<LONG>(desc.Height));
|
||||||
|
region.bottom = std::clamp(m_rcCrop.top + height, static_cast<LONG>(0), static_cast<LONG>(desc.Height));
|
||||||
|
region.back = 1;
|
||||||
|
|
||||||
|
// Copy the cropped region
|
||||||
|
m_d3dContext->CopySubresourceRegion(
|
||||||
|
croppedTexture.get(),
|
||||||
|
0,
|
||||||
|
0, 0, 0,
|
||||||
|
frameTexture.get(),
|
||||||
|
0,
|
||||||
|
®ion);
|
||||||
|
|
||||||
|
// Save this as the last frame for duplication
|
||||||
|
lastCroppedTexture = croppedTexture;
|
||||||
|
}
|
||||||
|
else if (lastCroppedTexture)
|
||||||
|
{
|
||||||
|
// No new frame, duplicate the last one
|
||||||
|
duplicatedFrames++;
|
||||||
|
croppedTexture = lastCroppedTexture;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode the frame (either new or duplicated)
|
||||||
|
if (croppedTexture)
|
||||||
|
{
|
||||||
|
HRESULT hr = EncodeFrame(croppedTexture.get());
|
||||||
|
if (FAILED(hr))
|
||||||
|
{
|
||||||
|
CloseInternal();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the next frame interval
|
||||||
|
co_await winrt::resume_after(std::chrono::milliseconds(1000 / m_frameRate));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit the GIF encoder
|
||||||
|
if (m_gifEncoder)
|
||||||
|
{
|
||||||
|
auto frameEndTime = std::chrono::high_resolution_clock::now();
|
||||||
|
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(frameEndTime - frameStartTime).count();
|
||||||
|
|
||||||
|
OutputDebugStringW(L"Recording stopped. Committing GIF encoder...\n");
|
||||||
|
OutputDebugStringW((L"Total frames captured: " + std::to_wstring(m_frameCount) + L"\n").c_str());
|
||||||
|
OutputDebugStringW((L"Capture attempts: " + std::to_wstring(captureAttempts) + L"\n").c_str());
|
||||||
|
OutputDebugStringW((L"Successful captures: " + std::to_wstring(successfulCaptures) + L"\n").c_str());
|
||||||
|
OutputDebugStringW((L"Duplicated frames: " + std::to_wstring(duplicatedFrames) + L"\n").c_str());
|
||||||
|
OutputDebugStringW((L"Recording duration: " + std::to_wstring(duration) + L"ms\n").c_str());
|
||||||
|
OutputDebugStringW((L"Actual FPS: " + std::to_wstring(m_frameCount * 1000.0 / duration) + L"\n").c_str());
|
||||||
|
|
||||||
|
winrt::check_hresult(m_gifEncoder->Commit());
|
||||||
|
OutputDebugStringW(L"GIF encoder committed successfully\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const winrt::hresult_error& error)
|
||||||
|
{
|
||||||
|
OutputDebugStringW(L"Error in GIF recording: ");
|
||||||
|
OutputDebugStringW(error.message().c_str());
|
||||||
|
OutputDebugStringW(L"\n");
|
||||||
|
|
||||||
|
// Try to commit the encoder even on error
|
||||||
|
if (m_gifEncoder)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
m_gifEncoder->Commit();
|
||||||
|
}
|
||||||
|
catch (...) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseInternal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// GifRecordingSession::Close
|
||||||
|
//
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
void GifRecordingSession::Close()
|
||||||
|
{
|
||||||
|
auto expected = false;
|
||||||
|
if (m_closed.compare_exchange_strong(expected, true))
|
||||||
|
{
|
||||||
|
expected = true;
|
||||||
|
if (!m_isRecording.compare_exchange_strong(expected, false))
|
||||||
|
{
|
||||||
|
CloseInternal();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_frameWait->StopCapture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// GifRecordingSession::CloseInternal
|
||||||
|
//
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
void GifRecordingSession::CloseInternal()
|
||||||
|
{
|
||||||
|
m_frameWait->StopCapture();
|
||||||
|
m_itemClosed.revoke();
|
||||||
|
}
|
||||||
69
src/modules/ZoomIt/ZoomIt/GifRecordingSession.h
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
//==============================================================================
|
||||||
|
//
|
||||||
|
// Zoomit
|
||||||
|
// Sysinternals - www.sysinternals.com
|
||||||
|
//
|
||||||
|
// GIF recording support using Windows Imaging Component (WIC)
|
||||||
|
//
|
||||||
|
//==============================================================================
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CaptureFrameWait.h"
|
||||||
|
#include <d3d11_4.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class GifRecordingSession : public std::enable_shared_from_this<GifRecordingSession>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
[[nodiscard]] static std::shared_ptr<GifRecordingSession> Create(
|
||||||
|
winrt::Direct3D11::IDirect3DDevice const& device,
|
||||||
|
winrt::GraphicsCaptureItem const& item,
|
||||||
|
RECT const& cropRect,
|
||||||
|
uint32_t frameRate,
|
||||||
|
winrt::Streams::IRandomAccessStream const& stream);
|
||||||
|
~GifRecordingSession();
|
||||||
|
|
||||||
|
winrt::IAsyncAction StartAsync();
|
||||||
|
void EnableCursorCapture(bool enable = true) { m_frameWait->EnableCursorCapture(enable); }
|
||||||
|
void Close();
|
||||||
|
|
||||||
|
private:
|
||||||
|
GifRecordingSession(
|
||||||
|
winrt::Direct3D11::IDirect3DDevice const& device,
|
||||||
|
winrt::Capture::GraphicsCaptureItem const& item,
|
||||||
|
RECT const cropRect,
|
||||||
|
uint32_t frameRate,
|
||||||
|
winrt::Streams::IRandomAccessStream const& stream);
|
||||||
|
void CloseInternal();
|
||||||
|
HRESULT EncodeFrame(ID3D11Texture2D* texture);
|
||||||
|
|
||||||
|
private:
|
||||||
|
winrt::Direct3D11::IDirect3DDevice m_device{ nullptr };
|
||||||
|
winrt::com_ptr<ID3D11Device> m_d3dDevice;
|
||||||
|
winrt::com_ptr<ID3D11DeviceContext> m_d3dContext;
|
||||||
|
RECT m_rcCrop;
|
||||||
|
uint32_t m_frameRate;
|
||||||
|
|
||||||
|
winrt::GraphicsCaptureItem m_item{ nullptr };
|
||||||
|
winrt::GraphicsCaptureItem::Closed_revoker m_itemClosed;
|
||||||
|
std::shared_ptr<CaptureFrameWait> m_frameWait;
|
||||||
|
|
||||||
|
winrt::Streams::IRandomAccessStream m_stream{ nullptr };
|
||||||
|
|
||||||
|
// WIC components for GIF encoding
|
||||||
|
winrt::com_ptr<IWICImagingFactory> m_wicFactory;
|
||||||
|
winrt::com_ptr<IWICStream> m_wicStream;
|
||||||
|
winrt::com_ptr<IWICBitmapEncoder> m_gifEncoder;
|
||||||
|
winrt::com_ptr<IWICMetadataQueryWriter> m_encoderMetadataWriter;
|
||||||
|
|
||||||
|
std::atomic<bool> m_isRecording = false;
|
||||||
|
std::atomic<bool> m_closed = false;
|
||||||
|
|
||||||
|
uint32_t m_frameWidth=0;
|
||||||
|
uint32_t m_frameHeight=0;
|
||||||
|
uint32_t m_frameDelay=0;
|
||||||
|
uint32_t m_frameCount = 0;
|
||||||
|
|
||||||
|
int32_t m_width=0;
|
||||||
|
int32_t m_height=0;
|
||||||
|
};
|
||||||
@@ -32,18 +32,18 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
|
|||||||
// TEXTINCLUDE
|
// TEXTINCLUDE
|
||||||
//
|
//
|
||||||
|
|
||||||
1 TEXTINCLUDE
|
1 TEXTINCLUDE
|
||||||
BEGIN
|
BEGIN
|
||||||
"resource.h\0"
|
"resource.h\0"
|
||||||
END
|
END
|
||||||
|
|
||||||
2 TEXTINCLUDE
|
2 TEXTINCLUDE
|
||||||
BEGIN
|
BEGIN
|
||||||
"#include ""winres.h""\r\n"
|
"#include ""winres.h""\r\n"
|
||||||
"\0"
|
"\0"
|
||||||
END
|
END
|
||||||
|
|
||||||
3 TEXTINCLUDE
|
3 TEXTINCLUDE
|
||||||
BEGIN
|
BEGIN
|
||||||
"#include ""binres.rc""\0"
|
"#include ""binres.rc""\0"
|
||||||
END
|
END
|
||||||
@@ -121,8 +121,8 @@ FONT 8, "MS Shell Dlg", 0, 0, 0x0
|
|||||||
BEGIN
|
BEGIN
|
||||||
DEFPUSHBUTTON "OK",IDOK,166,306,50,14
|
DEFPUSHBUTTON "OK",IDOK,166,306,50,14
|
||||||
PUSHBUTTON "Cancel",IDCANCEL,223,306,50,14
|
PUSHBUTTON "Cancel",IDCANCEL,223,306,50,14
|
||||||
LTEXT "ZoomIt v9.10",IDC_VERSION,42,7,73,10
|
LTEXT "ZoomIt v9.20",IDC_VERSION,42,7,73,10
|
||||||
LTEXT "Copyright © 2006-2025 Mark Russinovich",IDC_COPYRIGHT,42,17,231,8
|
LTEXT "Copyright <EFBFBD> 2006-2025 Mark Russinovich",IDC_COPYRIGHT,42,17,231,8
|
||||||
CONTROL "<a HREF=""https://www.sysinternals.com"">Sysinternals - www.sysinternals.com</a>",IDC_LINK,
|
CONTROL "<a HREF=""https://www.sysinternals.com"">Sysinternals - www.sysinternals.com</a>",IDC_LINK,
|
||||||
"SysLink",WS_TABSTOP,42,26,150,9
|
"SysLink",WS_TABSTOP,42,26,150,9
|
||||||
ICON "APPICON",IDC_STATIC,12,9,20,20
|
ICON "APPICON",IDC_STATIC,12,9,20,20
|
||||||
@@ -272,13 +272,15 @@ BEGIN
|
|||||||
LTEXT "Note: Recording is only available on Windows 10 (version 1903) and higher.",IDC_STATIC,7,77,246,19
|
LTEXT "Note: Recording is only available on Windows 10 (version 1903) and higher.",IDC_STATIC,7,77,246,19
|
||||||
LTEXT "Scaling:",IDC_STATIC,30,115,26,8
|
LTEXT "Scaling:",IDC_STATIC,30,115,26,8
|
||||||
COMBOBOX IDC_RECORD_SCALING,61,114,26,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | CBS_SORT | WS_VSCROLL | WS_TABSTOP
|
COMBOBOX IDC_RECORD_SCALING,61,114,26,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | CBS_SORT | WS_VSCROLL | WS_TABSTOP
|
||||||
|
LTEXT "Format:",IDC_STATIC,30,132,26,8
|
||||||
|
COMBOBOX IDC_RECORD_FORMAT,61,131,60,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | WS_VSCROLL | WS_TABSTOP
|
||||||
LTEXT "Frame Rate:",IDC_STATIC,119,115,44,8,NOT WS_VISIBLE
|
LTEXT "Frame Rate:",IDC_STATIC,119,115,44,8,NOT WS_VISIBLE
|
||||||
COMBOBOX IDC_RECORD_FRAME_RATE,166,114,42,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | CBS_SORT | NOT WS_VISIBLE | WS_VSCROLL | WS_TABSTOP
|
COMBOBOX IDC_RECORD_FRAME_RATE,166,114,42,30,CBS_DROPDOWNLIST | CBS_AUTOHSCROLL | CBS_OEMCONVERT | CBS_SORT | NOT WS_VISIBLE | WS_VSCROLL | WS_TABSTOP
|
||||||
LTEXT "To crop the portion of the screen that will be recorded, enter the hotkey with the Shift key in the opposite mode. ",IDC_STATIC,7,32,246,19
|
LTEXT "To crop the portion of the screen that will be recorded, enter the hotkey with the Shift key in the opposite mode. ",IDC_STATIC,7,32,246,19
|
||||||
LTEXT "To record a specific window, enter the hotkey with the Alt key in the opposite mode.",IDC_STATIC,7,55,246,19
|
LTEXT "To record a specific window, enter the hotkey with the Alt key in the opposite mode.",IDC_STATIC,7,55,246,19
|
||||||
CONTROL "&Capture audio input:",IDC_CAPTURE_AUDIO,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,7,137,83,10
|
CONTROL "&Capture audio input:",IDC_CAPTURE_AUDIO,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,7,149,83,10
|
||||||
COMBOBOX IDC_MICROPHONE,81,152,172,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
|
COMBOBOX IDC_MICROPHONE,81,164,172,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP
|
||||||
LTEXT "Microphone:",IDC_STATIC,32,154,47,8
|
LTEXT "Microphone:",IDC_STATIC,32,166,47,8
|
||||||
END
|
END
|
||||||
|
|
||||||
SNIP DIALOGEX 0, 0, 260, 68
|
SNIP DIALOGEX 0, 0, 260, 68
|
||||||
|
|||||||
@@ -234,6 +234,7 @@
|
|||||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="GifRecordingSession.cpp" />
|
||||||
<ClCompile Include="pch.cpp" />
|
<ClCompile Include="pch.cpp" />
|
||||||
<ClCompile Include="SelectRectangle.cpp">
|
<ClCompile Include="SelectRectangle.cpp">
|
||||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Use</PrecompiledHeader>
|
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Use</PrecompiledHeader>
|
||||||
@@ -288,6 +289,7 @@
|
|||||||
<ClInclude Include="AudioSampleGenerator.h" />
|
<ClInclude Include="AudioSampleGenerator.h" />
|
||||||
<ClInclude Include="$(MSBuildThisFileDirectory)..\..\..\common\sysinternals\Eula\Eula.h" />
|
<ClInclude Include="$(MSBuildThisFileDirectory)..\..\..\common\sysinternals\Eula\Eula.h" />
|
||||||
<ClInclude Include="$(MSBuildThisFileDirectory)..\ZoomItModuleInterface\Trace.h" />
|
<ClInclude Include="$(MSBuildThisFileDirectory)..\ZoomItModuleInterface\Trace.h" />
|
||||||
|
<ClInclude Include="GifRecordingSession.h" />
|
||||||
<ClInclude Include="pch.h" />
|
<ClInclude Include="pch.h" />
|
||||||
<ClInclude Include="Registry.h" />
|
<ClInclude Include="Registry.h" />
|
||||||
<ClInclude Include="resource.h" />
|
<ClInclude Include="resource.h" />
|
||||||
|
|||||||
@@ -54,6 +54,9 @@
|
|||||||
<ClCompile Include="$(MSBuildThisFileDirectory)..\..\..\common\sysinternals\WindowsVersions.cpp">
|
<ClCompile Include="$(MSBuildThisFileDirectory)..\..\..\common\sysinternals\WindowsVersions.cpp">
|
||||||
<Filter>Source Files</Filter>
|
<Filter>Source Files</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="GifRecordingSession.cpp">
|
||||||
|
<Filter>Source Files</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="Registry.h">
|
<ClInclude Include="Registry.h">
|
||||||
@@ -95,6 +98,9 @@
|
|||||||
<ClInclude Include="ZoomItSettings.h">
|
<ClInclude Include="ZoomItSettings.h">
|
||||||
<Filter>Header Files</Filter>
|
<Filter>Header Files</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="GifRecordingSession.h">
|
||||||
|
<Filter>Header Files</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Image Include="appicon.ico">
|
<Image Include="appicon.ico">
|
||||||
|
|||||||
@@ -3,6 +3,13 @@
|
|||||||
#include "Registry.h"
|
#include "Registry.h"
|
||||||
#include "DemoType.h"
|
#include "DemoType.h"
|
||||||
|
|
||||||
|
// Recording format enum
|
||||||
|
enum class RecordingFormat
|
||||||
|
{
|
||||||
|
GIF = 0,
|
||||||
|
MP4 = 1
|
||||||
|
};
|
||||||
|
|
||||||
DWORD g_ToggleKey = (HOTKEYF_CONTROL << 8)| '1';
|
DWORD g_ToggleKey = (HOTKEYF_CONTROL << 8)| '1';
|
||||||
DWORD g_LiveZoomToggleKey = ((HOTKEYF_CONTROL) << 8)| '4';
|
DWORD g_LiveZoomToggleKey = ((HOTKEYF_CONTROL) << 8)| '4';
|
||||||
DWORD g_DrawToggleKey = ((HOTKEYF_CONTROL) << 8)| '2';
|
DWORD g_DrawToggleKey = ((HOTKEYF_CONTROL) << 8)| '2';
|
||||||
@@ -38,8 +45,10 @@ BOOLEAN g_DemoTypeUserDriven = false;
|
|||||||
TCHAR g_DemoTypeFile[MAX_PATH] = {0};
|
TCHAR g_DemoTypeFile[MAX_PATH] = {0};
|
||||||
DWORD g_DemoTypeSpeedSlider = static_cast<int>(((MIN_TYPING_SPEED - MAX_TYPING_SPEED) / 2) + MAX_TYPING_SPEED);
|
DWORD g_DemoTypeSpeedSlider = static_cast<int>(((MIN_TYPING_SPEED - MAX_TYPING_SPEED) / 2) + MAX_TYPING_SPEED);
|
||||||
DWORD g_RecordFrameRate = 30;
|
DWORD g_RecordFrameRate = 30;
|
||||||
// Divide by 100 to get actual scaling
|
DWORD g_RecordScaling = 100;
|
||||||
DWORD g_RecordScaling = 100;
|
DWORD g_RecordScalingGIF = 50;
|
||||||
|
DWORD g_RecordScalingMP4 = 100;
|
||||||
|
RecordingFormat g_RecordingFormat = RecordingFormat::GIF;
|
||||||
BOOLEAN g_CaptureAudio = FALSE;
|
BOOLEAN g_CaptureAudio = FALSE;
|
||||||
TCHAR g_MicrophoneDeviceId[MAX_PATH] = {0};
|
TCHAR g_MicrophoneDeviceId[MAX_PATH] = {0};
|
||||||
|
|
||||||
@@ -79,7 +88,9 @@ REG_SETTING RegSettings[] = {
|
|||||||
{ L"ZoominSliderLevel", SETTING_TYPE_DWORD, 0, &g_SliderZoomLevel, static_cast<DOUBLE>(g_SliderZoomLevel) },
|
{ L"ZoominSliderLevel", SETTING_TYPE_DWORD, 0, &g_SliderZoomLevel, static_cast<DOUBLE>(g_SliderZoomLevel) },
|
||||||
{ L"Font", SETTING_TYPE_BINARY, sizeof g_LogFont, &g_LogFont, static_cast<DOUBLE>(0) },
|
{ L"Font", SETTING_TYPE_BINARY, sizeof g_LogFont, &g_LogFont, static_cast<DOUBLE>(0) },
|
||||||
{ L"RecordFrameRate", SETTING_TYPE_DWORD, 0, &g_RecordFrameRate, static_cast<DOUBLE>(g_RecordFrameRate) },
|
{ L"RecordFrameRate", SETTING_TYPE_DWORD, 0, &g_RecordFrameRate, static_cast<DOUBLE>(g_RecordFrameRate) },
|
||||||
{ L"RecordScaling", SETTING_TYPE_DWORD, 0, &g_RecordScaling, static_cast<DOUBLE>(g_RecordScaling) },
|
{ L"RecordingFormat", SETTING_TYPE_DWORD, 0, &g_RecordingFormat, static_cast<DOUBLE>(0) },
|
||||||
|
{ L"RecordScalingGIF", SETTING_TYPE_DWORD, 0, &g_RecordScalingGIF, static_cast<DOUBLE>(g_RecordScalingGIF) },
|
||||||
|
{ L"RecordScalingMP4", SETTING_TYPE_DWORD, 0, &g_RecordScalingMP4, static_cast<DOUBLE>(g_RecordScalingMP4) },
|
||||||
{ L"CaptureAudio", SETTING_TYPE_BOOLEAN, 0, &g_CaptureAudio, static_cast<DOUBLE>(g_CaptureAudio) },
|
{ L"CaptureAudio", SETTING_TYPE_BOOLEAN, 0, &g_CaptureAudio, static_cast<DOUBLE>(g_CaptureAudio) },
|
||||||
{ L"MicrophoneDeviceId", SETTING_TYPE_STRING, sizeof(g_MicrophoneDeviceId), g_MicrophoneDeviceId, static_cast<DOUBLE>(0) },
|
{ L"MicrophoneDeviceId", SETTING_TYPE_STRING, sizeof(g_MicrophoneDeviceId), g_MicrophoneDeviceId, static_cast<DOUBLE>(0) },
|
||||||
{ NULL, SETTING_TYPE_DWORD, 0, NULL, static_cast<DOUBLE>(0) }
|
{ NULL, SETTING_TYPE_DWORD, 0, NULL, static_cast<DOUBLE>(0) }
|
||||||
|
|||||||
@@ -93,6 +93,7 @@
|
|||||||
#define IDC_DEMOTYPE_SLIDER2 1074
|
#define IDC_DEMOTYPE_SLIDER2 1074
|
||||||
#define IDC_DEMOTYPE_STATIC2 1074
|
#define IDC_DEMOTYPE_STATIC2 1074
|
||||||
#define IDC_COPYRIGHT 1075
|
#define IDC_COPYRIGHT 1075
|
||||||
|
#define IDC_RECORD_FORMAT 1076
|
||||||
#define IDC_PEN_WIDTH 1105
|
#define IDC_PEN_WIDTH 1105
|
||||||
#define IDC_TIMER 1106
|
#define IDC_TIMER 1106
|
||||||
#define IDC_SMOOTH_IMAGE 1107
|
#define IDC_SMOOTH_IMAGE 1107
|
||||||
|
|||||||
@@ -14,6 +14,9 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
|
|||||||
const unsigned int SPECIAL_SEMANTICS_SHORTCUT = 1;
|
const unsigned int SPECIAL_SEMANTICS_SHORTCUT = 1;
|
||||||
const unsigned int SPECIAL_SEMANTICS_COLOR = 2;
|
const unsigned int SPECIAL_SEMANTICS_COLOR = 2;
|
||||||
const unsigned int SPECIAL_SEMANTICS_LOG_FONT = 3;
|
const unsigned int SPECIAL_SEMANTICS_LOG_FONT = 3;
|
||||||
|
const unsigned int SPECIAL_SEMANTICS_RECORDING_FORMAT = 4;
|
||||||
|
const unsigned int SPECIAL_SEMANTICS_RECORD_SCALING_GIF = 5;
|
||||||
|
const unsigned int SPECIAL_SEMANTICS_RECORD_SCALING_MP4 = 6;
|
||||||
|
|
||||||
std::vector<unsigned char> base64_decode(const std::wstring& base64_string)
|
std::vector<unsigned char> base64_decode(const std::wstring& base64_string)
|
||||||
{
|
{
|
||||||
@@ -72,6 +75,9 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
|
|||||||
{ L"PenColor", SPECIAL_SEMANTICS_COLOR },
|
{ L"PenColor", SPECIAL_SEMANTICS_COLOR },
|
||||||
{ L"BreakPenColor", SPECIAL_SEMANTICS_COLOR },
|
{ L"BreakPenColor", SPECIAL_SEMANTICS_COLOR },
|
||||||
{ L"Font", SPECIAL_SEMANTICS_LOG_FONT },
|
{ L"Font", SPECIAL_SEMANTICS_LOG_FONT },
|
||||||
|
{ L"RecordingFormat", SPECIAL_SEMANTICS_RECORDING_FORMAT },
|
||||||
|
{ L"RecordScalingGIF", SPECIAL_SEMANTICS_RECORD_SCALING_GIF },
|
||||||
|
{ L"RecordScalingMP4", SPECIAL_SEMANTICS_RECORD_SCALING_MP4 },
|
||||||
};
|
};
|
||||||
|
|
||||||
hstring ZoomItSettings::LoadSettingsJson()
|
hstring ZoomItSettings::LoadSettingsJson()
|
||||||
@@ -103,6 +109,11 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
|
|||||||
value & 0xFF);
|
value & 0xFF);
|
||||||
_settings.add_property(curSetting->ValueName, hotkey.get_json());
|
_settings.add_property(curSetting->ValueName, hotkey.get_json());
|
||||||
}
|
}
|
||||||
|
else if (special_semantics->second == SPECIAL_SEMANTICS_RECORDING_FORMAT)
|
||||||
|
{
|
||||||
|
std::wstring formatString = (value == 0) ? L"GIF" : L"MP4";
|
||||||
|
_settings.add_property(L"RecordFormat", formatString);
|
||||||
|
}
|
||||||
else if (special_semantics->second == SPECIAL_SEMANTICS_COLOR)
|
else if (special_semantics->second == SPECIAL_SEMANTICS_COLOR)
|
||||||
{
|
{
|
||||||
/* PowerToys settings likes colors as #FFFFFF strings.
|
/* PowerToys settings likes colors as #FFFFFF strings.
|
||||||
@@ -156,6 +167,9 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
|
|||||||
curSetting++;
|
curSetting++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DWORD recordScaling = (g_RecordingFormat == static_cast<RecordingFormat>(0)) ? g_RecordScalingGIF : g_RecordScalingMP4;
|
||||||
|
_settings.add_property<DWORD>(L"RecordScaling", recordScaling);
|
||||||
|
|
||||||
return _settings.get_raw_json().Stringify();
|
return _settings.get_raw_json().Stringify();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,6 +181,8 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
|
|||||||
PowerToysSettings::PowerToyValues valuesFromSettings =
|
PowerToysSettings::PowerToyValues valuesFromSettings =
|
||||||
PowerToysSettings::PowerToyValues::from_json_string(json, L"ZoomIt");
|
PowerToysSettings::PowerToyValues::from_json_string(json, L"ZoomIt");
|
||||||
|
|
||||||
|
bool formatChanged = false;
|
||||||
|
|
||||||
PREG_SETTING curSetting = RegSettings;
|
PREG_SETTING curSetting = RegSettings;
|
||||||
while (curSetting->ValueName)
|
while (curSetting->ValueName)
|
||||||
{
|
{
|
||||||
@@ -212,6 +228,42 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
|
|||||||
*static_cast<PDWORD>(curSetting->Setting) = value;
|
*static_cast<PDWORD>(curSetting->Setting) = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (special_semantics->second == SPECIAL_SEMANTICS_RECORDING_FORMAT)
|
||||||
|
{
|
||||||
|
// Convert string ("GIF" or "MP4") to DWORD enum value (0=GIF, 1=MP4)
|
||||||
|
auto possibleValue = valuesFromSettings.get_string_value(L"RecordFormat");
|
||||||
|
if (possibleValue.has_value())
|
||||||
|
{
|
||||||
|
RecordingFormat oldFormat = g_RecordingFormat;
|
||||||
|
DWORD formatValue = (possibleValue.value() == L"GIF") ? 0 : 1;
|
||||||
|
RecordingFormat newFormat = static_cast<RecordingFormat>(formatValue);
|
||||||
|
|
||||||
|
*static_cast<PDWORD>(curSetting->Setting) = formatValue;
|
||||||
|
|
||||||
|
if (oldFormat != newFormat)
|
||||||
|
{
|
||||||
|
formatChanged = true;
|
||||||
|
|
||||||
|
if (oldFormat == static_cast<RecordingFormat>(0))
|
||||||
|
{
|
||||||
|
g_RecordScalingGIF = g_RecordScaling;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_RecordScalingMP4 = g_RecordScaling;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newFormat == static_cast<RecordingFormat>(0))
|
||||||
|
{
|
||||||
|
g_RecordScaling = g_RecordScalingGIF;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_RecordScaling = g_RecordScalingMP4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
else if (special_semantics->second == SPECIAL_SEMANTICS_COLOR)
|
else if (special_semantics->second == SPECIAL_SEMANTICS_COLOR)
|
||||||
{
|
{
|
||||||
/* PowerToys settings likes colors as #FFFFFF strings.
|
/* PowerToys settings likes colors as #FFFFFF strings.
|
||||||
@@ -275,6 +327,22 @@ namespace winrt::PowerToys::ZoomItSettingsInterop::implementation
|
|||||||
}
|
}
|
||||||
curSetting++;
|
curSetting++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto recordScalingValue = valuesFromSettings.get_uint_value(L"RecordScaling");
|
||||||
|
if (recordScalingValue.has_value() && !formatChanged)
|
||||||
|
{
|
||||||
|
g_RecordScaling = recordScalingValue.value();
|
||||||
|
|
||||||
|
if (g_RecordingFormat == static_cast<RecordingFormat>(0))
|
||||||
|
{
|
||||||
|
g_RecordScalingGIF = recordScalingValue.value();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_RecordScalingMP4 = recordScalingValue.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
reg.WriteRegSettings(RegSettings);
|
reg.WriteRegSettings(RegSettings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,9 +88,6 @@ namespace Awake.Core.Native
|
|||||||
[return: MarshalAs(UnmanagedType.Bool)]
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
internal static extern bool GetCursorPos(out Point lpPoint);
|
internal static extern bool GetCursorPos(out Point lpPoint);
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
|
||||||
internal static extern bool ScreenToClient(IntPtr hWnd, ref Point lpPoint);
|
|
||||||
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
internal static extern bool GetMessage(out Msg lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
|
internal static extern bool GetMessage(out Msg lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax);
|
||||||
|
|
||||||
|
|||||||
@@ -61,9 +61,8 @@ namespace Awake.Core
|
|||||||
|
|
||||||
Bridge.SetForegroundWindow(hWnd);
|
Bridge.SetForegroundWindow(hWnd);
|
||||||
|
|
||||||
// Get cursor position and convert it to client coordinates
|
// Get cursor position in screen coordinates
|
||||||
Bridge.GetCursorPos(out Models.Point cursorPos);
|
Bridge.GetCursorPos(out Models.Point cursorPos);
|
||||||
Bridge.ScreenToClient(hWnd, ref cursorPos);
|
|
||||||
|
|
||||||
// Set menu information
|
// Set menu information
|
||||||
MenuInfo menuInfo = new()
|
MenuInfo menuInfo = new()
|
||||||
|
|||||||
@@ -51,10 +51,10 @@ internal sealed partial class GlobalErrorHandler
|
|||||||
// without its exception being observed. It is NOT raised immediately
|
// without its exception being observed. It is NOT raised immediately
|
||||||
// when the Task faults; timing depends on GC finalization.
|
// when the Task faults; timing depends on GC finalization.
|
||||||
e.SetObserved();
|
e.SetObserved();
|
||||||
HandleException(e.Exception, Context.UnobservedTaskException, isRecoverable: true);
|
HandleException(e.Exception, Context.UnobservedTaskException);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleException(Exception ex, Context context, bool isRecoverable = false)
|
private static void HandleException(Exception ex, Context context)
|
||||||
{
|
{
|
||||||
Logger.LogError($"Unhandled exception detected ({context})", ex);
|
Logger.LogError($"Unhandled exception detected ({context})", ex);
|
||||||
|
|
||||||
@@ -70,10 +70,25 @@ internal sealed partial class GlobalErrorHandler
|
|||||||
|
|
||||||
StoreReport(report, storeOnDesktop: false);
|
StoreReport(report, storeOnDesktop: false);
|
||||||
|
|
||||||
|
string message;
|
||||||
|
string caption;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
message = ResourceLoaderInstance.GetString("GlobalErrorHandler_CrashMessageBox_Message");
|
||||||
|
caption = ResourceLoaderInstance.GetString("GlobalErrorHandler_CrashMessageBox_Caption");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// The resource loader may not be available if the exception occurred during startup.
|
||||||
|
// Fall back to hardcoded strings in that case.
|
||||||
|
message = "Command Palette has encountered a fatal error and must close.";
|
||||||
|
caption = "Command Palette - Fatal error";
|
||||||
|
}
|
||||||
|
|
||||||
PInvoke.MessageBox(
|
PInvoke.MessageBox(
|
||||||
HWND.Null,
|
HWND.Null,
|
||||||
"Command Palette has encountered a fatal error and must close.\n\nAn error report has been saved to your desktop.",
|
message,
|
||||||
"Unhandled Error",
|
caption,
|
||||||
MESSAGEBOX_STYLE.MB_ICONERROR);
|
MESSAGEBOX_STYLE.MB_ICONERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ public sealed partial class MainWindow : WindowEx,
|
|||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
HideWindow();
|
|
||||||
|
|
||||||
_hwnd = new HWND(WinRT.Interop.WindowNative.GetWindowHandle(this).ToInt32());
|
_hwnd = new HWND(WinRT.Interop.WindowNative.GetWindowHandle(this).ToInt32());
|
||||||
|
|
||||||
|
|||||||
@@ -431,8 +431,8 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
|||||||
<data name="Run_Radio_Position_LastPosition.Content" xml:space="preserve">
|
<data name="Run_Radio_Position_LastPosition.Content" xml:space="preserve">
|
||||||
<value>Last Position</value>
|
<value>Last Position</value>
|
||||||
<comment>Reopen the window where it was last closed</comment>
|
<comment>Reopen the window where it was last closed</comment>
|
||||||
</data>
|
</data>
|
||||||
<data name="TrayMenu_Settings" xml:space="preserve">
|
<data name="TrayMenu_Settings" xml:space="preserve">
|
||||||
<value>Settings</value>
|
<value>Settings</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="TrayMenu_Close" xml:space="preserve">
|
<data name="TrayMenu_Close" xml:space="preserve">
|
||||||
@@ -493,28 +493,34 @@ Right-click to remove the key combination, thereby deactivating the shortcut.</v
|
|||||||
<data name="Settings_ExtensionsPage_Reloading_Text.Text" xml:space="preserve">
|
<data name="Settings_ExtensionsPage_Reloading_Text.Text" xml:space="preserve">
|
||||||
<value>Reloading extensions..</value>
|
<value>Reloading extensions..</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Settings_ExtensionsPage_Banner_Header.Text" xml:space="preserve">
|
<data name="Settings_ExtensionsPage_Banner_Header.Text" xml:space="preserve">
|
||||||
<value>Discover more extensions</value>
|
<value>Discover more extensions</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Settings_ExtensionsPage_Banner_Description.Text" xml:space="preserve">
|
<data name="Settings_ExtensionsPage_Banner_Description.Text" xml:space="preserve">
|
||||||
<value>Find more extensions on the Microsoft Store or WinGet.</value>
|
<value>Find more extensions on the Microsoft Store or WinGet.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Settings_ExtensionsPage_Banner_Hyperlink.Content" xml:space="preserve">
|
<data name="Settings_ExtensionsPage_Banner_Hyperlink.Content" xml:space="preserve">
|
||||||
<value>Learn how to create your own extensions</value>
|
<value>Learn how to create your own extensions</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Settings_ExtensionsPage_FindExtensions_MicrosoftStore.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
<data name="Settings_ExtensionsPage_FindExtensions_MicrosoftStore.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||||
<value>Find extensions on the Microsoft Store</value>
|
<value>Find extensions on the Microsoft Store</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Settings_ExtensionsPage_FindExtensions_MicrosoftStore.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
<data name="Settings_ExtensionsPage_FindExtensions_MicrosoftStore.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||||
<value>Microsoft Store</value>
|
<value>Microsoft Store</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Settings_ExtensionsPage_FindExtensions_WinGet.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
<data name="Settings_ExtensionsPage_FindExtensions_WinGet.[using:Microsoft.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||||
<value>Find extensions on WinGet</value>
|
<value>Find extensions on WinGet</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Settings_ExtensionsPage_FindExtensions_WinGet.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
<data name="Settings_ExtensionsPage_FindExtensions_WinGet.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||||
<value>Microsoft Store</value>
|
<value>Microsoft Store</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Settings_ExtensionsPage_SearchBox_Placeholder.PlaceholderText" xml:space="preserve">
|
<data name="Settings_ExtensionsPage_SearchBox_Placeholder.PlaceholderText" xml:space="preserve">
|
||||||
<value>Search extensions</value>
|
<value>Search extensions</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="GlobalErrorHandler_CrashMessageBox_Message" xml:space="preserve">
|
||||||
|
<value>Command Palette has encountered a fatal error and must close.</value>
|
||||||
|
</data>
|
||||||
|
<data name="GlobalErrorHandler_CrashMessageBox_Caption" xml:space="preserve">
|
||||||
|
<value>Command Palette - Fatal error</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
|
<XesUseOneStoreVersioning>true</XesUseOneStoreVersioning>
|
||||||
<XesBaseYearForStoreVersion>2025</XesBaseYearForStoreVersion>
|
<XesBaseYearForStoreVersion>2025</XesBaseYearForStoreVersion>
|
||||||
<VersionMajor>0</VersionMajor>
|
<VersionMajor>0</VersionMajor>
|
||||||
<VersionMinor>6</VersionMinor>
|
<VersionMinor>7</VersionMinor>
|
||||||
<VersionInfoProductName>Microsoft Command Palette</VersionInfoProductName>
|
<VersionInfoProductName>Microsoft Command Palette</VersionInfoProductName>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
|||||||
|
|
||||||
public sealed partial class AppListItem : ListItem
|
public sealed partial class AppListItem : ListItem
|
||||||
{
|
{
|
||||||
private static readonly Tag _appTag = new("App");
|
|
||||||
|
|
||||||
private readonly AppCommand _appCommand;
|
private readonly AppCommand _appCommand;
|
||||||
private readonly AppItem _app;
|
private readonly AppItem _app;
|
||||||
private readonly Lazy<Details> _details;
|
private readonly Lazy<Details> _details;
|
||||||
@@ -48,7 +46,6 @@ public sealed partial class AppListItem : ListItem
|
|||||||
_app = app;
|
_app = app;
|
||||||
Title = app.Name;
|
Title = app.Name;
|
||||||
Subtitle = app.Subtitle;
|
Subtitle = app.Subtitle;
|
||||||
Tags = [_appTag];
|
|
||||||
Icon = Icons.GenericAppIcon;
|
Icon = Icons.GenericAppIcon;
|
||||||
|
|
||||||
MoreCommands = AddPinCommands(_app.Commands!, isPinned);
|
MoreCommands = AddPinCommands(_app.Commands!, isPinned);
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ internal sealed partial class SampleListPageWithDetails : ListPage
|
|||||||
Details = new Details()
|
Details = new Details()
|
||||||
{
|
{
|
||||||
Title = "Hero Image Example",
|
Title = "Hero Image Example",
|
||||||
HeroImage = new IconInfo("https://m.media-amazon.com/images/M/MV5BNDBkMzVmNGQtYTM2OC00OWRjLTk5OWMtNzNkMDI4NjFjNTZmXkEyXkFqcGdeQXZ3ZXNsZXk@._V1_QL75_UX500_CR0,0,500,281_.jpg"),
|
HeroImage = new IconInfo("https://m.media-amazon.com/images/M/MV5BNDBkMzVmNGQtYTM2OC00OWRjLTk5OWMtNzNkMDI4NjFjNTZmXkEyXkFqcGdeQXZ3ZXNsZXk@._V1_QL75_UX500_CR0,0,500,281_.jpg"), /* #no-spell-check-line */
|
||||||
Body = "It is literally an image of a hero",
|
Body = "It is literally an image of a hero",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using ImageResizer.Properties;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Moq.Protected;
|
using Moq.Protected;
|
||||||
@@ -101,7 +101,9 @@ namespace ImageResizer.Models
|
|||||||
private static ResizeBatch CreateBatch(Action<string> executeAction)
|
private static ResizeBatch CreateBatch(Action<string> executeAction)
|
||||||
{
|
{
|
||||||
var mock = new Mock<ResizeBatch> { CallBase = true };
|
var mock = new Mock<ResizeBatch> { CallBase = true };
|
||||||
mock.Protected().Setup("Execute", ItExpr.IsAny<string>()).Callback(executeAction);
|
mock.Protected()
|
||||||
|
.Setup("Execute", ItExpr.IsAny<string>(), ItExpr.IsAny<Settings>())
|
||||||
|
.Callback((string file, Settings settings) => executeAction(file));
|
||||||
|
|
||||||
return mock.Object;
|
return mock.Object;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,9 +87,14 @@ namespace ImageResizer.Models
|
|||||||
public IEnumerable<ResizeError> Process(Action<int, double> reportProgress, CancellationToken cancellationToken)
|
public IEnumerable<ResizeError> Process(Action<int, double> reportProgress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
double total = Files.Count;
|
double total = Files.Count;
|
||||||
var completed = 0;
|
int completed = 0;
|
||||||
var errors = new ConcurrentBag<ResizeError>();
|
var errors = new ConcurrentBag<ResizeError>();
|
||||||
|
|
||||||
|
// NOTE: Settings.Default is captured once before parallel processing.
|
||||||
|
// Any changes to settings on disk during this batch will NOT be reflected until the next batch.
|
||||||
|
// This improves performance and predictability by avoiding repeated mutex acquisition and behaviour change results in a batch.
|
||||||
|
var settings = Settings.Default;
|
||||||
|
|
||||||
// TODO: If we ever switch to Windows.Graphics.Imaging, we can get a lot more throughput by using the async
|
// TODO: If we ever switch to Windows.Graphics.Imaging, we can get a lot more throughput by using the async
|
||||||
// APIs and a custom SynchronizationContext
|
// APIs and a custom SynchronizationContext
|
||||||
Parallel.ForEach(
|
Parallel.ForEach(
|
||||||
@@ -97,13 +102,12 @@ namespace ImageResizer.Models
|
|||||||
new ParallelOptions
|
new ParallelOptions
|
||||||
{
|
{
|
||||||
CancellationToken = cancellationToken,
|
CancellationToken = cancellationToken,
|
||||||
MaxDegreeOfParallelism = Environment.ProcessorCount,
|
|
||||||
},
|
},
|
||||||
(file, state, i) =>
|
(file, state, i) =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Execute(file);
|
Execute(file, settings);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -111,14 +115,13 @@ namespace ImageResizer.Models
|
|||||||
}
|
}
|
||||||
|
|
||||||
Interlocked.Increment(ref completed);
|
Interlocked.Increment(ref completed);
|
||||||
|
|
||||||
reportProgress(completed, total);
|
reportProgress(completed, total);
|
||||||
});
|
});
|
||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void Execute(string file)
|
protected virtual void Execute(string file, Settings settings)
|
||||||
=> new ResizeOperation(file, DestinationDirectory, Settings.Default).Execute();
|
=> new ResizeOperation(file, DestinationDirectory, settings).Execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -461,33 +461,42 @@ namespace ImageResizer.Properties
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
// Needs to be called on the App UI thread as the properties are bound to the UI.
|
if (App.Current?.Dispatcher != null)
|
||||||
App.Current.Dispatcher.Invoke(() =>
|
|
||||||
{
|
{
|
||||||
ShrinkOnly = jsonSettings.ShrinkOnly;
|
// Needs to be called on the App UI thread as the properties are bound to the UI.
|
||||||
Replace = jsonSettings.Replace;
|
App.Current.Dispatcher.Invoke(() => ReloadCore(jsonSettings));
|
||||||
IgnoreOrientation = jsonSettings.IgnoreOrientation;
|
}
|
||||||
RemoveMetadata = jsonSettings.RemoveMetadata;
|
else
|
||||||
JpegQualityLevel = jsonSettings.JpegQualityLevel;
|
{
|
||||||
PngInterlaceOption = jsonSettings.PngInterlaceOption;
|
ReloadCore(jsonSettings);
|
||||||
TiffCompressOption = jsonSettings.TiffCompressOption;
|
}
|
||||||
FileName = jsonSettings.FileName;
|
|
||||||
KeepDateModified = jsonSettings.KeepDateModified;
|
|
||||||
FallbackEncoder = jsonSettings.FallbackEncoder;
|
|
||||||
CustomSize = jsonSettings.CustomSize;
|
|
||||||
SelectedSizeIndex = jsonSettings.SelectedSizeIndex;
|
|
||||||
|
|
||||||
if (jsonSettings.Sizes.Count > 0)
|
|
||||||
{
|
|
||||||
Sizes.Clear();
|
|
||||||
Sizes.AddRange(jsonSettings.Sizes);
|
|
||||||
|
|
||||||
// Ensure Ids are unique and handle missing Ids
|
|
||||||
IdRecoveryHelper.RecoverInvalidIds(Sizes);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_jsonMutex.ReleaseMutex();
|
_jsonMutex.ReleaseMutex();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ReloadCore(Settings jsonSettings)
|
||||||
|
{
|
||||||
|
ShrinkOnly = jsonSettings.ShrinkOnly;
|
||||||
|
Replace = jsonSettings.Replace;
|
||||||
|
IgnoreOrientation = jsonSettings.IgnoreOrientation;
|
||||||
|
RemoveMetadata = jsonSettings.RemoveMetadata;
|
||||||
|
JpegQualityLevel = jsonSettings.JpegQualityLevel;
|
||||||
|
PngInterlaceOption = jsonSettings.PngInterlaceOption;
|
||||||
|
TiffCompressOption = jsonSettings.TiffCompressOption;
|
||||||
|
FileName = jsonSettings.FileName;
|
||||||
|
KeepDateModified = jsonSettings.KeepDateModified;
|
||||||
|
FallbackEncoder = jsonSettings.FallbackEncoder;
|
||||||
|
CustomSize = jsonSettings.CustomSize;
|
||||||
|
SelectedSizeIndex = jsonSettings.SelectedSizeIndex;
|
||||||
|
|
||||||
|
if (jsonSettings.Sizes.Count > 0)
|
||||||
|
{
|
||||||
|
Sizes.Clear();
|
||||||
|
Sizes.AddRange(jsonSettings.Sizes);
|
||||||
|
|
||||||
|
// Ensure Ids are unique and handle missing Ids
|
||||||
|
IdRecoveryHelper.RecoverInvalidIds(Sizes);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,13 +24,15 @@ using Windows.Storage;
|
|||||||
|
|
||||||
namespace Peek.FilePreviewer.Previewers.MediaPreviewer
|
namespace Peek.FilePreviewer.Previewers.MediaPreviewer
|
||||||
{
|
{
|
||||||
public partial class AudioPreviewer : ObservableObject, IAudioPreviewer
|
public partial class AudioPreviewer : ObservableObject, IDisposable, IAudioPreviewer
|
||||||
{
|
{
|
||||||
|
private MediaSource? _mediaSource;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private PreviewState _state;
|
private PreviewState _state;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private AudioPreviewData _preview;
|
private AudioPreviewData? _preview;
|
||||||
|
|
||||||
private IFileSystemItem Item { get; }
|
private IFileSystemItem Item { get; }
|
||||||
|
|
||||||
@@ -40,7 +42,6 @@ namespace Peek.FilePreviewer.Previewers.MediaPreviewer
|
|||||||
{
|
{
|
||||||
Item = file;
|
Item = file;
|
||||||
Dispatcher = DispatcherQueue.GetForCurrentThread();
|
Dispatcher = DispatcherQueue.GetForCurrentThread();
|
||||||
Preview = new AudioPreviewData();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CopyAsync()
|
public async Task CopyAsync()
|
||||||
@@ -63,19 +64,23 @@ namespace Peek.FilePreviewer.Previewers.MediaPreviewer
|
|||||||
{
|
{
|
||||||
State = PreviewState.Loading;
|
State = PreviewState.Loading;
|
||||||
|
|
||||||
|
Preview = new AudioPreviewData();
|
||||||
|
|
||||||
var thumbnailTask = LoadThumbnailAsync(cancellationToken);
|
var thumbnailTask = LoadThumbnailAsync(cancellationToken);
|
||||||
var sourceTask = LoadSourceAsync(cancellationToken);
|
var sourceTask = LoadSourceAsync(cancellationToken);
|
||||||
var metadataTask = LoadMetadataAsync(cancellationToken);
|
var metadataTask = LoadMetadataAsync(cancellationToken);
|
||||||
|
|
||||||
await Task.WhenAll(thumbnailTask, sourceTask, metadataTask);
|
await Task.WhenAll(thumbnailTask, sourceTask, metadataTask);
|
||||||
|
|
||||||
if (!thumbnailTask.Result || !sourceTask.Result || !metadataTask.Result)
|
if (sourceTask.Result && metadataTask.Result)
|
||||||
{
|
{
|
||||||
State = PreviewState.Error;
|
State = PreviewState.Loaded;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
State = PreviewState.Loaded;
|
// Release all resources on error.
|
||||||
|
Unload();
|
||||||
|
State = PreviewState.Error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,12 +93,15 @@ namespace Peek.FilePreviewer.Previewers.MediaPreviewer
|
|||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
var thumbnail = await ThumbnailHelper.GetThumbnailAsync(Item.Path, cancellationToken)
|
if (Preview != null)
|
||||||
?? await ThumbnailHelper.GetIconAsync(Item.Path, cancellationToken);
|
{
|
||||||
|
var thumbnail = await ThumbnailHelper.GetThumbnailAsync(Item.Path, cancellationToken)
|
||||||
|
?? await ThumbnailHelper.GetIconAsync(Item.Path, cancellationToken);
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
Preview.Thumbnail = thumbnail ?? new SvgImageSource(new Uri("ms-appx:///Assets/Peek/DefaultFileIcon.svg"));
|
Preview.Thumbnail = thumbnail ?? new SvgImageSource(new Uri("ms-appx:///Assets/Peek/DefaultFileIcon.svg"));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -110,7 +118,11 @@ namespace Peek.FilePreviewer.Previewers.MediaPreviewer
|
|||||||
{
|
{
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
Preview.MediaSource = MediaSource.CreateFromStorageFile(storageFile);
|
if (Preview != null)
|
||||||
|
{
|
||||||
|
_mediaSource = MediaSource.CreateFromStorageFile(storageFile);
|
||||||
|
Preview.MediaSource = _mediaSource;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -123,6 +135,11 @@ namespace Peek.FilePreviewer.Previewers.MediaPreviewer
|
|||||||
|
|
||||||
await Dispatcher.RunOnUiThread(() =>
|
await Dispatcher.RunOnUiThread(() =>
|
||||||
{
|
{
|
||||||
|
if (Preview == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
Preview.Title = PropertyStoreHelper.TryGetStringProperty(Item.Path, PropertyKey.MusicTitle)
|
Preview.Title = PropertyStoreHelper.TryGetStringProperty(Item.Path, PropertyKey.MusicTitle)
|
||||||
?? Item.Name[..^Item.Extension.Length];
|
?? Item.Name[..^Item.Extension.Length];
|
||||||
@@ -160,6 +177,22 @@ namespace Peek.FilePreviewer.Previewers.MediaPreviewer
|
|||||||
return _supportedFileTypes.Contains(item.Extension);
|
return _supportedFileTypes.Contains(item.Extension);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Unload();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Explicitly unloads the preview and releases file resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Unload()
|
||||||
|
{
|
||||||
|
_mediaSource?.Dispose();
|
||||||
|
_mediaSource = null;
|
||||||
|
Preview = null;
|
||||||
|
}
|
||||||
|
|
||||||
private static readonly HashSet<string> _supportedFileTypes = new()
|
private static readonly HashSet<string> _supportedFileTypes = new()
|
||||||
{
|
{
|
||||||
".aac",
|
".aac",
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ namespace Peek.FilePreviewer.Previewers
|
|||||||
{
|
{
|
||||||
public partial class VideoPreviewer : ObservableObject, IVideoPreviewer, IDisposable
|
public partial class VideoPreviewer : ObservableObject, IVideoPreviewer, IDisposable
|
||||||
{
|
{
|
||||||
|
private MediaSource? _mediaSource;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private MediaSource? preview;
|
private MediaSource? preview;
|
||||||
|
|
||||||
@@ -56,6 +58,7 @@ namespace Peek.FilePreviewer.Previewers
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
Unload();
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +148,8 @@ namespace Peek.FilePreviewer.Previewers
|
|||||||
MissingCodecName = missingCodecName;
|
MissingCodecName = missingCodecName;
|
||||||
}
|
}
|
||||||
|
|
||||||
Preview = MediaSource.CreateFromStorageFile(storageFile);
|
_mediaSource = MediaSource.CreateFromStorageFile(storageFile);
|
||||||
|
Preview = _mediaSource;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -155,6 +159,16 @@ namespace Peek.FilePreviewer.Previewers
|
|||||||
return !(VideoTask?.Result ?? true);
|
return !(VideoTask?.Result ?? true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Explicitly unloads the preview and releases file resources.
|
||||||
|
/// </summary>
|
||||||
|
public void Unload()
|
||||||
|
{
|
||||||
|
_mediaSource?.Dispose();
|
||||||
|
_mediaSource = null;
|
||||||
|
Preview = null;
|
||||||
|
}
|
||||||
|
|
||||||
private static readonly HashSet<string> _supportedFileTypes = new()
|
private static readonly HashSet<string> _supportedFileTypes = new()
|
||||||
{
|
{
|
||||||
".mp4", ".3g2", ".3gp", ".3gp2", ".3gpp", ".asf", ".avi", ".m2t", ".m2ts",
|
".mp4", ".3g2", ".3gp", ".3gp2", ".3gpp", ".asf", ".avi", ".m2t", ".m2ts",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using System.Drawing;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.PowerToys.UITest;
|
using Microsoft.PowerToys.UITest;
|
||||||
@@ -35,6 +36,105 @@ public class PeekFilePreviewTests : UITestBase
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PeekFilePreviewTests()
|
||||||
|
{
|
||||||
|
FixSettingsFileBeforeTests();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly JsonSerializerOptions IndentedJsonOptions = new() { WriteIndented = true };
|
||||||
|
|
||||||
|
private static void FixSettingsFileBeforeTests()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Default Peek settings
|
||||||
|
string peekSettingsContent = @"{
|
||||||
|
""name"": ""Peek"",
|
||||||
|
""version"": ""1.0"",
|
||||||
|
""properties"": {
|
||||||
|
""ActivationShortcut"": {
|
||||||
|
""win"": false,
|
||||||
|
""ctrl"": true,
|
||||||
|
""alt"": false,
|
||||||
|
""shift"": false,
|
||||||
|
""code"": 32,
|
||||||
|
""key"": ""Space""
|
||||||
|
},
|
||||||
|
""AlwaysRunNotElevated"": {
|
||||||
|
""value"": true
|
||||||
|
},
|
||||||
|
""CloseAfterLosingFocus"": {
|
||||||
|
""value"": false
|
||||||
|
},
|
||||||
|
""ConfirmFileDelete"": {
|
||||||
|
""value"": true
|
||||||
|
},
|
||||||
|
""EnableSpaceToActivate"": {
|
||||||
|
""value"": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
// Update Peek module settings
|
||||||
|
SettingsConfigHelper.UpdateModuleSettings(
|
||||||
|
"Peek",
|
||||||
|
peekSettingsContent,
|
||||||
|
(settings) =>
|
||||||
|
{
|
||||||
|
// Get or ensure properties section exists
|
||||||
|
Dictionary<string, object> properties;
|
||||||
|
|
||||||
|
if (settings.TryGetValue("properties", out var propertiesObj))
|
||||||
|
{
|
||||||
|
if (propertiesObj is Dictionary<string, object> dict)
|
||||||
|
{
|
||||||
|
properties = dict;
|
||||||
|
}
|
||||||
|
else if (propertiesObj is JsonElement jsonElem)
|
||||||
|
{
|
||||||
|
properties = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonElem.GetRawText())
|
||||||
|
?? throw new InvalidOperationException("Failed to deserialize properties");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
properties = new Dictionary<string, object>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
properties = new Dictionary<string, object>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the required properties
|
||||||
|
properties["ActivationShortcut"] = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "win", false },
|
||||||
|
{ "ctrl", true },
|
||||||
|
{ "alt", false },
|
||||||
|
{ "shift", false },
|
||||||
|
{ "code", 32 },
|
||||||
|
{ "key", "Space" },
|
||||||
|
};
|
||||||
|
|
||||||
|
properties["EnableSpaceToActivate"] = new Dictionary<string, object>
|
||||||
|
{
|
||||||
|
{ "value", false },
|
||||||
|
};
|
||||||
|
|
||||||
|
settings["properties"] = properties;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Disable all modules except Peek in global settings
|
||||||
|
SettingsConfigHelper.ConfigureGlobalModuleSettings("Peek");
|
||||||
|
|
||||||
|
Debug.WriteLine("Successfully updated all settings - Peek shortcut configured and all modules except Peek disabled");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Assert.Fail($"ERROR in FixSettingsFileBeforeTests: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestInitialize]
|
[TestInitialize]
|
||||||
public void TestInitialize()
|
public void TestInitialize()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ namespace PowerAccent.Core
|
|||||||
LetterKey.VK_L => new[] { "ļ", "₺" }, // ₺ is in VK_T for other languages, but not VK_L, so we add it here.
|
LetterKey.VK_L => new[] { "ļ", "₺" }, // ₺ is in VK_T for other languages, but not VK_L, so we add it here.
|
||||||
LetterKey.VK_M => new[] { "ṁ" },
|
LetterKey.VK_M => new[] { "ṁ" },
|
||||||
LetterKey.VK_N => new[] { "ņ", "ṅ", "ⁿ", "ℕ", "№" },
|
LetterKey.VK_N => new[] { "ņ", "ṅ", "ⁿ", "ℕ", "№" },
|
||||||
LetterKey.VK_O => new[] { "ȯ", "∅" },
|
LetterKey.VK_O => new[] { "ȯ", "∅", "⌀" },
|
||||||
LetterKey.VK_P => new[] { "ṗ", "℗", "∏", "¶" },
|
LetterKey.VK_P => new[] { "ṗ", "℗", "∏", "¶" },
|
||||||
LetterKey.VK_Q => new[] { "ℚ" },
|
LetterKey.VK_Q => new[] { "ℚ" },
|
||||||
LetterKey.VK_R => new[] { "ṙ", "®", "ℝ" },
|
LetterKey.VK_R => new[] { "ṙ", "®", "ℝ" },
|
||||||
|
|||||||
@@ -71,6 +71,7 @@
|
|||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<SubSystem>Console</SubSystem>
|
<SubSystem>Console</SubSystem>
|
||||||
|
<AdditionalDependencies>windowscodecs.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||||
</Link>
|
</Link>
|
||||||
</ItemDefinitionGroup>
|
</ItemDefinitionGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
<PlatformToolset>v143</PlatformToolset>
|
<PlatformToolset>v143</PlatformToolset>
|
||||||
<WindowsPackageType>None</WindowsPackageType>
|
<WindowsPackageType>None</WindowsPackageType>
|
||||||
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>
|
||||||
|
<WindowsAppSdkUndockedRegFreeWinRTInitialize>true</WindowsAppSdkUndockedRegFreeWinRTInitialize>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||||
<ImportGroup Label="ExtensionSettings">
|
<ImportGroup Label="ExtensionSettings">
|
||||||
|
|||||||
@@ -56,9 +56,9 @@ public static class AIServiceTypeRegistry
|
|||||||
IsOnlineService = true,
|
IsOnlineService = true,
|
||||||
LegalDescription = "AdvancedPaste_Google_LegalDescription",
|
LegalDescription = "AdvancedPaste_Google_LegalDescription",
|
||||||
TermsLabel = "AdvancedPaste_Google_TermsLabel",
|
TermsLabel = "AdvancedPaste_Google_TermsLabel",
|
||||||
TermsUri = new Uri("https://policies.google.com/terms"),
|
TermsUri = new Uri("https://ai.google.dev/gemini-api/terms"),
|
||||||
PrivacyLabel = "AdvancedPaste_Google_PrivacyLabel",
|
PrivacyLabel = "AdvancedPaste_Google_PrivacyLabel",
|
||||||
PrivacyUri = new Uri("https://policies.google.com/privacy"),
|
PrivacyUri = new Uri("https://support.google.com/gemini/answer/13594961"),
|
||||||
},
|
},
|
||||||
[AIServiceType.Mistral] = new AIServiceTypeMetadata
|
[AIServiceType.Mistral] = new AIServiceTypeMetadata
|
||||||
{
|
{
|
||||||
@@ -93,9 +93,9 @@ public static class AIServiceTypeRegistry
|
|||||||
IsLocalModel = true,
|
IsLocalModel = true,
|
||||||
LegalDescription = "AdvancedPaste_LocalModel_LegalDescription",
|
LegalDescription = "AdvancedPaste_LocalModel_LegalDescription",
|
||||||
TermsLabel = "AdvancedPaste_Ollama_TermsLabel",
|
TermsLabel = "AdvancedPaste_Ollama_TermsLabel",
|
||||||
TermsUri = new Uri("https://ollama.com/terms"),
|
TermsUri = new Uri("https://ollama.org/terms"),
|
||||||
PrivacyLabel = "AdvancedPaste_Ollama_PrivacyLabel",
|
PrivacyLabel = "AdvancedPaste_Ollama_PrivacyLabel",
|
||||||
PrivacyUri = new Uri("https://ollama.com/privacy"),
|
PrivacyUri = new Uri("https://ollama.org/privacy"),
|
||||||
},
|
},
|
||||||
[AIServiceType.Onnx] = new AIServiceTypeMetadata
|
[AIServiceType.Onnx] = new AIServiceTypeMetadata
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
|||||||
|
|
||||||
public IntProperty RecordScaling { get; set; }
|
public IntProperty RecordScaling { get; set; }
|
||||||
|
|
||||||
|
public StringProperty RecordFormat { get; set; }
|
||||||
|
|
||||||
public BoolProperty CaptureAudio { get; set; }
|
public BoolProperty CaptureAudio { get; set; }
|
||||||
|
|
||||||
public StringProperty MicrophoneDeviceId { get; set; }
|
public StringProperty MicrophoneDeviceId { get; set; }
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -9,7 +9,10 @@
|
|||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Button Click="ShortcutConflictBtn_Click" Style="{StaticResource SubtleButtonStyle}">
|
<Button
|
||||||
|
x:Uid="ShortcutConflictControl_Automation"
|
||||||
|
Click="ShortcutConflictBtn_Click"
|
||||||
|
Style="{StaticResource SubtleButtonStyle}">
|
||||||
<Grid ColumnSpacing="16">
|
<Grid ColumnSpacing="16">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
|
|||||||
@@ -111,7 +111,8 @@
|
|||||||
x:Uid="UpdateAvailableInfoBar"
|
x:Uid="UpdateAvailableInfoBar"
|
||||||
IsClosable="False"
|
IsClosable="False"
|
||||||
IsOpen="{x:Bind ViewModel.IsUpdateAvailable, Mode=OneWay}"
|
IsOpen="{x:Bind ViewModel.IsUpdateAvailable, Mode=OneWay}"
|
||||||
Severity="Success" />
|
Severity="Success"
|
||||||
|
Tapped="UpdateInfoBar_Tapped" />
|
||||||
|
|
||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
@@ -144,7 +145,6 @@
|
|||||||
<Button
|
<Button
|
||||||
x:Name="SettingsBtn"
|
x:Name="SettingsBtn"
|
||||||
x:Uid="SettingsBtn"
|
x:Uid="SettingsBtn"
|
||||||
Padding="8"
|
|
||||||
Click="SettingsBtn_Click"
|
Click="SettingsBtn_Click"
|
||||||
Style="{StaticResource FlyoutButtonStyle}">
|
Style="{StaticResource FlyoutButtonStyle}">
|
||||||
<ToolTipService.ToolTip>
|
<ToolTipService.ToolTip>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ using Microsoft.PowerToys.Settings.UI.Controls;
|
|||||||
using Microsoft.PowerToys.Settings.UI.Library;
|
using Microsoft.PowerToys.Settings.UI.Library;
|
||||||
using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events;
|
using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events;
|
||||||
using Microsoft.PowerToys.Settings.UI.ViewModels;
|
using Microsoft.PowerToys.Settings.UI.ViewModels;
|
||||||
|
using Microsoft.PowerToys.Settings.UI.Views;
|
||||||
using Microsoft.PowerToys.Telemetry;
|
using Microsoft.PowerToys.Telemetry;
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
@@ -183,5 +184,14 @@ namespace Microsoft.PowerToys.Settings.UI.Flyout
|
|||||||
// Closing manually the flyout since no window will steal the focus
|
// Closing manually the flyout since no window will steal the focus
|
||||||
App.GetFlyoutWindow()?.Hide();
|
App.GetFlyoutWindow()?.Hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateInfoBar_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// Hide the flyout before opening settings window
|
||||||
|
App.GetFlyoutWindow()?.Hide();
|
||||||
|
|
||||||
|
// Open Settings window directly to General page where update controls are located
|
||||||
|
App.OpenSettingsWindow(typeof(GeneralPage));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,6 +240,7 @@
|
|||||||
<ComboBox
|
<ComboBox
|
||||||
x:Name="Languages_ComboBox"
|
x:Name="Languages_ComboBox"
|
||||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||||
|
AutomationProperties.Name="{Binding ElementName=LanguageHeader, Path=Header}"
|
||||||
DisplayMemberPath="Language"
|
DisplayMemberPath="Language"
|
||||||
ItemsSource="{Binding Languages, Mode=TwoWay}"
|
ItemsSource="{Binding Languages, Mode=TwoWay}"
|
||||||
SelectedIndex="{Binding LanguagesIndex, Mode=TwoWay}" />
|
SelectedIndex="{Binding LanguagesIndex, Mode=TwoWay}" />
|
||||||
@@ -262,7 +263,10 @@
|
|||||||
<tkcontrols:SettingsCard.Description>
|
<tkcontrols:SettingsCard.Description>
|
||||||
<HyperlinkButton x:Uid="Windows_Color_Settings" Click="OpenColorsSettings_Click" />
|
<HyperlinkButton x:Uid="Windows_Color_Settings" Click="OpenColorsSettings_Click" />
|
||||||
</tkcontrols:SettingsCard.Description>
|
</tkcontrols:SettingsCard.Description>
|
||||||
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind ViewModel.ThemeIndex, Mode=TwoWay}">
|
<ComboBox
|
||||||
|
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||||
|
AutomationProperties.Name="{Binding ElementName=ColorModeHeader, Path=Header}"
|
||||||
|
SelectedIndex="{x:Bind ViewModel.ThemeIndex, Mode=TwoWay}">
|
||||||
<ComboBoxItem x:Uid="Radio_Theme_Dark" />
|
<ComboBoxItem x:Uid="Radio_Theme_Dark" />
|
||||||
<ComboBoxItem x:Uid="Radio_Theme_Light" />
|
<ComboBoxItem x:Uid="Radio_Theme_Light" />
|
||||||
<ComboBoxItem x:Uid="Radio_Theme_Default" />
|
<ComboBoxItem x:Uid="Radio_Theme_Default" />
|
||||||
@@ -273,12 +277,16 @@
|
|||||||
Name="GeneralPageRunAtStartUp"
|
Name="GeneralPageRunAtStartUp"
|
||||||
x:Uid="GeneralPage_RunAtStartUp"
|
x:Uid="GeneralPage_RunAtStartUp"
|
||||||
IsEnabled="{x:Bind ViewModel.IsRunAtStartupGPOManaged, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
IsEnabled="{x:Bind ViewModel.IsRunAtStartupGPOManaged, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.Startup, Mode=TwoWay}" />
|
<ToggleSwitch
|
||||||
|
x:Uid="ToggleSwitch"
|
||||||
|
AutomationProperties.Name="{Binding ElementName=GeneralPageRunAtStartUp, Path=Header}"
|
||||||
|
IsOn="{x:Bind ViewModel.Startup, Mode=TwoWay}" />
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsRunAtStartupGPOManaged, Mode=OneWay}">
|
<controls:GPOInfoControl ShowWarning="{x:Bind ViewModel.IsRunAtStartupGPOManaged, Mode=OneWay}">
|
||||||
<tkcontrols:SettingsCard x:Uid="ShowSystemTrayIcon">
|
<tkcontrols:SettingsCard x:Name="ShowSystemTrayIconCard" x:Uid="ShowSystemTrayIcon">
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
x:Uid="ShowSystemTrayIcon_ToggleSwitch"
|
x:Uid="ShowSystemTrayIcon_ToggleSwitch"
|
||||||
|
AutomationProperties.Name="{Binding ElementName=ShowSystemTrayIconCard, Path=Header}"
|
||||||
IsOn="{x:Bind ViewModel.ShowSysTrayIcon, Mode=TwoWay}"
|
IsOn="{x:Bind ViewModel.ShowSysTrayIcon, Mode=TwoWay}"
|
||||||
Toggled="ShowSystemTrayIcon_Toggled" />
|
Toggled="ShowSystemTrayIcon_Toggled" />
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
@@ -398,12 +406,16 @@
|
|||||||
<tkcontrols:SettingsCard.HeaderIcon>
|
<tkcontrols:SettingsCard.HeaderIcon>
|
||||||
<PathIcon Data="M1859 1758q14 23 21 47t7 51q0 40-15 75t-41 61-61 41-75 15H354q-40 0-75-15t-61-41-41-61-15-75q0-27 6-51t21-47l569-992q10-14 10-34V128H640V0h768v128h-128v604q0 19 10 35l569 991zM896 732q0 53-27 99l-331 577h972l-331-577q-27-46-27-99V128H896v604zm799 1188q26 0 44-19t19-45q0-10-2-17t-8-16l-164-287H464l-165 287q-9 15-9 33 0 26 18 45t46 19h1341z" />
|
<PathIcon Data="M1859 1758q14 23 21 47t7 51q0 40-15 75t-41 61-61 41-75 15H354q-40 0-75-15t-61-41-41-61-15-75q0-27 6-51t21-47l569-992q10-14 10-34V128H640V0h768v128h-128v604q0 19 10 35l569 991zM896 732q0 53-27 99l-331 577h972l-331-577q-27-46-27-99V128H896v604zm799 1188q26 0 44-19t19-45q0-10-2-17t-8-16l-164-287H464l-165 287q-9 15-9 33 0 26 18 45t46 19h1341z" />
|
||||||
</tkcontrols:SettingsCard.HeaderIcon>
|
</tkcontrols:SettingsCard.HeaderIcon>
|
||||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.EnableExperimentation, Mode=TwoWay}" />
|
<ToggleSwitch
|
||||||
|
x:Uid="ToggleSwitch"
|
||||||
|
AutomationProperties.Name="{Binding ElementName=GeneralPageEnableExperimentation, Path=Header}"
|
||||||
|
IsOn="{x:Bind ViewModel.EnableExperimentation, Mode=TwoWay}" />
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
</controls:GPOInfoControl>
|
</controls:GPOInfoControl>
|
||||||
</controls:SettingsGroup>
|
</controls:SettingsGroup>
|
||||||
<controls:SettingsGroup x:Uid="General_DiagnosticsAndFeedback">
|
<controls:SettingsGroup x:Uid="General_DiagnosticsAndFeedback">
|
||||||
<tkcontrols:SettingsExpander
|
<tkcontrols:SettingsExpander
|
||||||
|
x:Name="GeneralPageEnableDataDiagnostics"
|
||||||
x:Uid="GeneralPage_EnableDataDiagnostics"
|
x:Uid="GeneralPage_EnableDataDiagnostics"
|
||||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||||
IsEnabled="{x:Bind ViewModel.IsDataDiagnosticsGPOManaged, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"
|
IsEnabled="{x:Bind ViewModel.IsDataDiagnosticsGPOManaged, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"
|
||||||
@@ -421,10 +433,19 @@
|
|||||||
NavigateUri="https://aka.ms/powertoys-data-and-privacy-documentation" />
|
NavigateUri="https://aka.ms/powertoys-data-and-privacy-documentation" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</tkcontrols:SettingsExpander.Description>
|
</tkcontrols:SettingsExpander.Description>
|
||||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.EnableDataDiagnostics, Mode=TwoWay}" />
|
<ToggleSwitch
|
||||||
|
x:Uid="ToggleSwitch"
|
||||||
|
AutomationProperties.Name="{Binding ElementName=GeneralPageEnableDataDiagnostics, Path=Header}"
|
||||||
|
IsOn="{x:Bind ViewModel.EnableDataDiagnostics, Mode=TwoWay}" />
|
||||||
<tkcontrols:SettingsExpander.Items>
|
<tkcontrols:SettingsExpander.Items>
|
||||||
<tkcontrols:SettingsCard x:Uid="GeneralPage_EnableViewDiagnosticData" IsEnabled="{x:Bind ViewModel.EnableDataDiagnostics, Mode=TwoWay}">
|
<tkcontrols:SettingsCard
|
||||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.EnableViewDataDiagnostics, Mode=TwoWay}" />
|
x:Name="GeneralPageEnableViewDiagnosticData"
|
||||||
|
x:Uid="GeneralPage_EnableViewDiagnosticData"
|
||||||
|
IsEnabled="{x:Bind ViewModel.EnableDataDiagnostics, Mode=TwoWay}">
|
||||||
|
<ToggleSwitch
|
||||||
|
x:Uid="ToggleSwitch"
|
||||||
|
AutomationProperties.Name="{Binding ElementName=GeneralPageEnableViewDiagnosticData, Path=Header}"
|
||||||
|
IsOn="{x:Bind ViewModel.EnableViewDataDiagnostics, Mode=TwoWay}" />
|
||||||
<tkcontrols:SettingsCard.Description>
|
<tkcontrols:SettingsCard.Description>
|
||||||
<StackPanel Orientation="Vertical">
|
<StackPanel Orientation="Vertical">
|
||||||
<TextBlock
|
<TextBlock
|
||||||
|
|||||||
@@ -23,7 +23,10 @@
|
|||||||
Name="NewPlusEnableToggle"
|
Name="NewPlusEnableToggle"
|
||||||
x:Uid="NewPlus_Enable_Toggle"
|
x:Uid="NewPlus_Enable_Toggle"
|
||||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/NewPlus.png}">
|
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/NewPlus.png}">
|
||||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
<ToggleSwitch
|
||||||
|
x:Uid="ToggleSwitch"
|
||||||
|
AutomationProperties.Name="{Binding ElementName=NewPlusEnableToggle, Path=Header}"
|
||||||
|
IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
</controls:GPOInfoControl>
|
</controls:GPOInfoControl>
|
||||||
<controls:SettingsGroup x:Uid="NewPlus_Templates" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
<controls:SettingsGroup x:Uid="NewPlus_Templates" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||||
@@ -60,12 +63,18 @@
|
|||||||
Name="NewPlusHideFileExtensionToggle"
|
Name="NewPlusHideFileExtensionToggle"
|
||||||
x:Uid="NewPlus_Hide_File_Extension_Toggle"
|
x:Uid="NewPlus_Hide_File_Extension_Toggle"
|
||||||
IsEnabled="{x:Bind ViewModel.IsHideFileExtSettingsCardEnabled, Mode=OneWay}">
|
IsEnabled="{x:Bind ViewModel.IsHideFileExtSettingsCardEnabled, Mode=OneWay}">
|
||||||
<ToggleSwitch x:Uid="HideFileExtensionToggle" IsOn="{x:Bind ViewModel.HideFileExtension, Mode=TwoWay}" />
|
<ToggleSwitch
|
||||||
|
x:Uid="HideFileExtensionToggle"
|
||||||
|
AutomationProperties.Name="{Binding ElementName=NewPlusHideFileExtensionToggle, Path=Header}"
|
||||||
|
IsOn="{x:Bind ViewModel.HideFileExtension, Mode=TwoWay}" />
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
</controls:GPOInfoControl>
|
</controls:GPOInfoControl>
|
||||||
<tkcontrols:SettingsCard Name="NewPlusHideStartingDigitsToggle" x:Uid="NewPlus_Hide_Starting_Digits_Toggle">
|
<tkcontrols:SettingsCard Name="NewPlusHideStartingDigitsToggle" x:Uid="NewPlus_Hide_Starting_Digits_Toggle">
|
||||||
|
|
||||||
<ToggleSwitch x:Uid="HideStartingDigitsToggle" IsOn="{x:Bind ViewModel.HideStartingDigits, Mode=TwoWay}" />
|
<ToggleSwitch
|
||||||
|
x:Uid="HideStartingDigitsToggle"
|
||||||
|
AutomationProperties.Name="{Binding ElementName=NewPlusHideStartingDigitsToggle, Path=Header}"
|
||||||
|
IsOn="{x:Bind ViewModel.HideStartingDigits, Mode=TwoWay}" />
|
||||||
<tkcontrols:SettingsCard.Description>
|
<tkcontrols:SettingsCard.Description>
|
||||||
<TextBlock x:Uid="NewPlus_Hide_Starting_Digits_Description" />
|
<TextBlock x:Uid="NewPlus_Hide_Starting_Digits_Description" />
|
||||||
</tkcontrols:SettingsCard.Description>
|
</tkcontrols:SettingsCard.Description>
|
||||||
@@ -79,7 +88,10 @@
|
|||||||
x:Uid="NewPlus_Behaviour_Replace_Variables_Toggle"
|
x:Uid="NewPlus_Behaviour_Replace_Variables_Toggle"
|
||||||
IsEnabled="{x:Bind ViewModel.IsReplaceVariablesSettingsCardEnabled, Mode=OneWay}">
|
IsEnabled="{x:Bind ViewModel.IsReplaceVariablesSettingsCardEnabled, Mode=OneWay}">
|
||||||
<StackPanel Orientation="Horizontal" Spacing="4">
|
<StackPanel Orientation="Horizontal" Spacing="4">
|
||||||
<ToggleSwitch x:Uid="ReplaceVariablesToggle" IsOn="{x:Bind ViewModel.ReplaceVariables, Mode=TwoWay}" />
|
<ToggleSwitch
|
||||||
|
x:Uid="ReplaceVariablesToggle"
|
||||||
|
AutomationProperties.Name="{Binding ElementName=NewPlusBehaviourReplaceVariablesToggle, Path=Header}"
|
||||||
|
IsOn="{x:Bind ViewModel.ReplaceVariables, Mode=TwoWay}" />
|
||||||
<Button
|
<Button
|
||||||
x:Uid="FileCreationButton"
|
x:Uid="FileCreationButton"
|
||||||
Width="28"
|
Width="28"
|
||||||
|
|||||||
@@ -67,6 +67,7 @@
|
|||||||
Header="{x:Bind Path=DisplayLabel}">
|
Header="{x:Bind Path=DisplayLabel}">
|
||||||
<ComboBox
|
<ComboBox
|
||||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||||
|
AutomationProperties.Name="{x:Bind Path=DisplayLabel}"
|
||||||
DisplayMemberPath="Key"
|
DisplayMemberPath="Key"
|
||||||
ItemsSource="{x:Bind Path=ComboBoxItems}"
|
ItemsSource="{x:Bind Path=ComboBoxItems}"
|
||||||
SelectedValue="{x:Bind ComboBoxValue, Mode=TwoWay}"
|
SelectedValue="{x:Bind ComboBoxValue, Mode=TwoWay}"
|
||||||
@@ -110,6 +111,7 @@
|
|||||||
Header="{x:Bind Path=DisplayLabel}">
|
Header="{x:Bind Path=DisplayLabel}">
|
||||||
<NumberBox
|
<NumberBox
|
||||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||||
|
AutomationProperties.Name="{x:Bind Path=DisplayLabel}"
|
||||||
LargeChange="{x:Bind NumberBoxLargeChange, Mode=OneWay}"
|
LargeChange="{x:Bind NumberBoxLargeChange, Mode=OneWay}"
|
||||||
Maximum="{x:Bind NumberBoxMax, Mode=OneWay}"
|
Maximum="{x:Bind NumberBoxMax, Mode=OneWay}"
|
||||||
Minimum="{x:Bind NumberBoxMin, Mode=OneWay}"
|
Minimum="{x:Bind NumberBoxMin, Mode=OneWay}"
|
||||||
@@ -175,6 +177,7 @@
|
|||||||
IsEnabled="{x:Bind SecondSettingIsEnabled, Mode=OneWay}">
|
IsEnabled="{x:Bind SecondSettingIsEnabled, Mode=OneWay}">
|
||||||
<ComboBox
|
<ComboBox
|
||||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||||
|
AutomationProperties.Name="{x:Bind Path=SecondDisplayLabel}"
|
||||||
DisplayMemberPath="Key"
|
DisplayMemberPath="Key"
|
||||||
ItemsSource="{x:Bind Path=ComboBoxItems}"
|
ItemsSource="{x:Bind Path=ComboBoxItems}"
|
||||||
SelectedValue="{x:Bind ComboBoxValue, Mode=TwoWay}"
|
SelectedValue="{x:Bind ComboBoxValue, Mode=TwoWay}"
|
||||||
@@ -290,6 +293,7 @@
|
|||||||
IsEnabled="{x:Bind SecondSettingIsEnabled, Mode=OneWay}">
|
IsEnabled="{x:Bind SecondSettingIsEnabled, Mode=OneWay}">
|
||||||
<NumberBox
|
<NumberBox
|
||||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||||
|
AutomationProperties.Name="{x:Bind Path=SecondDisplayLabel}"
|
||||||
LargeChange="{x:Bind NumberBoxLargeChange, Mode=OneWay}"
|
LargeChange="{x:Bind NumberBoxLargeChange, Mode=OneWay}"
|
||||||
Maximum="{x:Bind NumberBoxMax, Mode=OneWay}"
|
Maximum="{x:Bind NumberBoxMax, Mode=OneWay}"
|
||||||
Minimum="{x:Bind NumberBoxMin, Mode=OneWay}"
|
Minimum="{x:Bind NumberBoxMin, Mode=OneWay}"
|
||||||
|
|||||||
@@ -22,7 +22,10 @@
|
|||||||
Name="PowerRenameToggleEnable"
|
Name="PowerRenameToggleEnable"
|
||||||
x:Uid="PowerRename_Toggle_Enable"
|
x:Uid="PowerRename_Toggle_Enable"
|
||||||
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/PowerRename.png}">
|
HeaderIcon="{ui:BitmapIcon Source=/Assets/Settings/Icons/PowerRename.png}">
|
||||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
<ToggleSwitch
|
||||||
|
x:Uid="ToggleSwitch"
|
||||||
|
AutomationProperties.Name="{Binding ElementName=PowerRenameToggleEnable, Path=Header}"
|
||||||
|
IsOn="{x:Bind ViewModel.IsEnabled, Mode=TwoWay}" />
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
</controls:GPOInfoControl>
|
</controls:GPOInfoControl>
|
||||||
<controls:SettingsGroup x:Uid="PowerRename_ShellIntegration" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
<controls:SettingsGroup x:Uid="PowerRename_ShellIntegration" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||||
@@ -30,7 +33,10 @@
|
|||||||
Name="PowerRenameToggleContextMenu"
|
Name="PowerRenameToggleContextMenu"
|
||||||
x:Uid="PowerRename_Toggle_ContextMenu"
|
x:Uid="PowerRename_Toggle_ContextMenu"
|
||||||
IsExpanded="False">
|
IsExpanded="False">
|
||||||
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind ViewModel.EnabledOnContextExtendedMenu, Mode=TwoWay, Converter={StaticResource BoolToComboBoxIndexConverter}}">
|
<ComboBox
|
||||||
|
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||||
|
AutomationProperties.Name="{Binding ElementName=PowerRenameToggleContextMenu, Path=Header}"
|
||||||
|
SelectedIndex="{x:Bind ViewModel.EnabledOnContextExtendedMenu, Mode=TwoWay, Converter={StaticResource BoolToComboBoxIndexConverter}}">
|
||||||
<ComboBoxItem x:Uid="PowerRename_Toggle_StandardContextMenu" />
|
<ComboBoxItem x:Uid="PowerRename_Toggle_StandardContextMenu" />
|
||||||
<ComboBoxItem x:Uid="PowerRename_Toggle_ExtendedContextMenu" />
|
<ComboBoxItem x:Uid="PowerRename_Toggle_ExtendedContextMenu" />
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
@@ -53,7 +59,10 @@
|
|||||||
Name="PowerRenameToggleAutoComplete"
|
Name="PowerRenameToggleAutoComplete"
|
||||||
x:Uid="PowerRename_Toggle_AutoComplete"
|
x:Uid="PowerRename_Toggle_AutoComplete"
|
||||||
IsExpanded="True">
|
IsExpanded="True">
|
||||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.MRUEnabled, Mode=TwoWay}" />
|
<ToggleSwitch
|
||||||
|
x:Uid="ToggleSwitch"
|
||||||
|
AutomationProperties.Name="{Binding ElementName=PowerRenameToggleAutoComplete, Path=Header}"
|
||||||
|
IsOn="{x:Bind ViewModel.MRUEnabled, Mode=TwoWay}" />
|
||||||
<tkcontrols:SettingsExpander.Items>
|
<tkcontrols:SettingsExpander.Items>
|
||||||
<tkcontrols:SettingsCard
|
<tkcontrols:SettingsCard
|
||||||
Name="PowerRenameToggleMaxDispListNum"
|
Name="PowerRenameToggleMaxDispListNum"
|
||||||
@@ -61,6 +70,7 @@
|
|||||||
IsEnabled="{x:Bind ViewModel.GlobalAndMruEnabled, Mode=OneWay}">
|
IsEnabled="{x:Bind ViewModel.GlobalAndMruEnabled, Mode=OneWay}">
|
||||||
<NumberBox
|
<NumberBox
|
||||||
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
MinWidth="{StaticResource SettingActionControlMinWidth}"
|
||||||
|
AutomationProperties.Name="{Binding ElementName=PowerRenameToggleMaxDispListNum, Path=Header}"
|
||||||
Maximum="20"
|
Maximum="20"
|
||||||
Minimum="0"
|
Minimum="0"
|
||||||
SpinButtonPlacementMode="Compact"
|
SpinButtonPlacementMode="Compact"
|
||||||
@@ -73,12 +83,18 @@
|
|||||||
Name="PowerRenameToggleRestoreFlagsOnLaunch"
|
Name="PowerRenameToggleRestoreFlagsOnLaunch"
|
||||||
x:Uid="PowerRename_Toggle_RestoreFlagsOnLaunch"
|
x:Uid="PowerRename_Toggle_RestoreFlagsOnLaunch"
|
||||||
HeaderIcon="{ui:FontIcon Glyph=}">
|
HeaderIcon="{ui:FontIcon Glyph=}">
|
||||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.RestoreFlagsOnLaunch, Mode=TwoWay}" />
|
<ToggleSwitch
|
||||||
|
x:Uid="ToggleSwitch"
|
||||||
|
AutomationProperties.Name="{Binding ElementName=PowerRenameToggleRestoreFlagsOnLaunch, Path=Header}"
|
||||||
|
IsOn="{x:Bind ViewModel.RestoreFlagsOnLaunch, Mode=TwoWay}" />
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
</controls:SettingsGroup>
|
</controls:SettingsGroup>
|
||||||
<controls:SettingsGroup x:Uid="PowerRename_BehaviorHeader" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
<controls:SettingsGroup x:Uid="PowerRename_BehaviorHeader" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||||
<tkcontrols:SettingsCard Name="PowerRenameToggleUseBoostLib" x:Uid="PowerRename_Toggle_UseBoostLib">
|
<tkcontrols:SettingsCard Name="PowerRenameToggleUseBoostLib" x:Uid="PowerRename_Toggle_UseBoostLib">
|
||||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.UseBoostLib, Mode=TwoWay}" />
|
<ToggleSwitch
|
||||||
|
x:Uid="ToggleSwitch"
|
||||||
|
AutomationProperties.Name="{Binding ElementName=PowerRenameToggleUseBoostLib, Path=Header}"
|
||||||
|
IsOn="{x:Bind ViewModel.UseBoostLib, Mode=TwoWay}" />
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
</controls:SettingsGroup>
|
</controls:SettingsGroup>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -241,6 +241,12 @@
|
|||||||
<ComboBoxItem>1.0</ComboBoxItem>
|
<ComboBoxItem>1.0</ComboBoxItem>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
|
<tkcontrols:SettingsCard Name="ZoomItRecordFormat" x:Uid="ZoomIt_Record_Format">
|
||||||
|
<ComboBox MinWidth="{StaticResource SettingActionControlMinWidth}" SelectedIndex="{x:Bind Path=ViewModel.RecordFormatIndex, Mode=TwoWay}">
|
||||||
|
<ComboBoxItem>GIF</ComboBoxItem>
|
||||||
|
<ComboBoxItem>MP4</ComboBoxItem>
|
||||||
|
</ComboBox>
|
||||||
|
</tkcontrols:SettingsCard>
|
||||||
<tkcontrols:SettingsCard Name="ZoomItRecordCaptureAudio" x:Uid="ZoomIt_Record_CaptureAudio">
|
<tkcontrols:SettingsCard Name="ZoomItRecordCaptureAudio" x:Uid="ZoomIt_Record_CaptureAudio">
|
||||||
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.RecordCaptureAudio, Mode=TwoWay}" />
|
<ToggleSwitch x:Uid="ToggleSwitch" IsOn="{x:Bind ViewModel.RecordCaptureAudio, Mode=TwoWay}" />
|
||||||
</tkcontrols:SettingsCard>
|
</tkcontrols:SettingsCard>
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</resheader>
|
<resheader name="version">2.0</resheader>
|
||||||
@@ -26,36 +26,36 @@
|
|||||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
value : The object must be serialized with
|
value : The object must be serialized with
|
||||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
value : The object must be serialized into a byte array
|
value : The object must be serialized into a byte array
|
||||||
: using a System.ComponentModel.TypeConverter
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
@@ -2251,7 +2251,7 @@ Take a moment to preview the various utilities listed or view our comprehensive
|
|||||||
<value>Press the **Restart as administrator** button from the File Locksmith UI to also get information on elevated processes that might be using the files.</value>
|
<value>Press the **Restart as administrator** button from the File Locksmith UI to also get information on elevated processes that might be using the files.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Oobe_FileExplorer_HowToEnable.Text" xml:space="preserve">
|
<data name="Oobe_FileExplorer_HowToEnable.Text" xml:space="preserve">
|
||||||
<value>Select **View** which is located at the top of File Explorer, followed by **Show**, and then **Preview pane**.
|
<value>Select **View** which is located at the top of File Explorer, followed by **Show**, and then **Preview pane**.
|
||||||
From there, simply click on one of the supported files in the File Explorer and observe the content on the preview pane!</value>
|
From there, simply click on one of the supported files in the File Explorer and observe the content on the preview pane!</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Oobe_HowToCreateMappings.Text" xml:space="preserve">
|
<data name="Oobe_HowToCreateMappings.Text" xml:space="preserve">
|
||||||
@@ -5028,6 +5028,9 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
|
|||||||
<data name="ZoomIt_Record_Scaling.Header" xml:space="preserve">
|
<data name="ZoomIt_Record_Scaling.Header" xml:space="preserve">
|
||||||
<value>Scaling</value>
|
<value>Scaling</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ZoomIt_Record_Format.Header" xml:space="preserve">
|
||||||
|
<value>Format</value>
|
||||||
|
</data>
|
||||||
<data name="ZoomIt_Record_CaptureAudio.Header" xml:space="preserve">
|
<data name="ZoomIt_Record_CaptureAudio.Header" xml:space="preserve">
|
||||||
<value>Capture audio input</value>
|
<value>Capture audio input</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -5562,6 +5565,9 @@ To record a specific window, enter the hotkey with the Alt key in the opposite m
|
|||||||
<data name="ShortcutConflictControl_Title.Text" xml:space="preserve">
|
<data name="ShortcutConflictControl_Title.Text" xml:space="preserve">
|
||||||
<value>Shortcut conflicts</value>
|
<value>Shortcut conflicts</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ShortcutConflictControl_Automation.[using:Microsoft.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||||
|
<value>Shortcut conflicts</value>
|
||||||
|
</data>
|
||||||
<data name="ShortcutConflictControl_NoConflictsFound" xml:space="preserve">
|
<data name="ShortcutConflictControl_NoConflictsFound" xml:space="preserve">
|
||||||
<value>No conflicts found</value>
|
<value>No conflicts found</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -652,6 +652,54 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int RecordFormatIndex
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_zoomItSettings.Properties.RecordFormat.Value == "GIF")
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_zoomItSettings.Properties.RecordFormat.Value == "MP4")
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
int format = 0;
|
||||||
|
if (_zoomItSettings.Properties.RecordFormat.Value == "GIF")
|
||||||
|
{
|
||||||
|
format = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_zoomItSettings.Properties.RecordFormat.Value == "MP4")
|
||||||
|
{
|
||||||
|
format = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format != value)
|
||||||
|
{
|
||||||
|
_zoomItSettings.Properties.RecordFormat.Value = value == 0 ? "GIF" : "MP4";
|
||||||
|
OnPropertyChanged(nameof(RecordFormatIndex));
|
||||||
|
NotifySettingsChanged();
|
||||||
|
|
||||||
|
// Reload settings to get the new format's scaling value
|
||||||
|
var reloadedSettings = global::PowerToys.ZoomItSettingsInterop.ZoomItSettings.LoadSettingsJson();
|
||||||
|
var reloaded = JsonSerializer.Deserialize<ZoomItSettings>(reloadedSettings, _serializerOptions);
|
||||||
|
if (reloaded != null && reloaded.Properties != null)
|
||||||
|
{
|
||||||
|
_zoomItSettings.Properties.RecordScaling.Value = reloaded.Properties.RecordScaling.Value;
|
||||||
|
OnPropertyChanged(nameof(RecordScalingIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool RecordCaptureAudio
|
public bool RecordCaptureAudio
|
||||||
{
|
{
|
||||||
get => _zoomItSettings.Properties.CaptureAudio.Value;
|
get => _zoomItSettings.Properties.CaptureAudio.Value;
|
||||||
|
|||||||