Merge branch 'main' into main
6
.gitignore
vendored
@@ -224,7 +224,7 @@ ClientBin/
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
@@ -322,7 +322,7 @@ ImageResizer/tools/**
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
@@ -331,7 +331,7 @@ ASALocalRun/
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Temp build files
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="AdaptiveCards.ObjectModel.WinUI3" Version="2.0.0-beta" />
|
||||
<PackageVersion Include="AdaptiveCards.Rendering.WinUI3" Version="2.1.0-beta" />
|
||||
<PackageVersion Include="AdaptiveCards.Templating" Version="2.0.2" />
|
||||
<PackageVersion Include="Appium.WebDriver" Version="4.4.5" />
|
||||
<PackageVersion Include="Azure.AI.OpenAI" Version="1.0.0-beta.12" />
|
||||
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.2.2" />
|
||||
@@ -83,6 +86,11 @@
|
||||
<PackageVersion Include="UTF.Unknown" Version="2.5.1" />
|
||||
<PackageVersion Include="WinUIEx" Version="2.2.0" />
|
||||
<PackageVersion Include="WPF-UI" Version="3.0.5" />
|
||||
|
||||
<!-- TODO! Mike see which of these we actually need, and -->
|
||||
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
|
||||
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
|
||||
<PackageVersion Include="System.Private.Uri" Version="4.3.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(IsExperimentationLive)'!=''">
|
||||
<!-- Additional dependencies used by experimentation -->
|
||||
|
||||
104
Microsoft.CmdPal.sln
Normal file
@@ -0,0 +1,104 @@
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.11.34929.205
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.UI.Poc", "src\modules\cmdpal\src\WindowsCommandPalette\Microsoft.CmdPal.UI.Poc.csproj", "{F71CF22B-A5C7-4328-A5B3-F4191AE57314}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Calculator", "src\modules\cmdpal\src\Plugins\Calculator\Calculator.csproj", "{C668A4BF-8BC1-48D1-B00D-FF99D05E6739}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AllApps", "src\modules\cmdpal\src\Plugins\AllApps\AllApps.csproj", "{F5E5B8B9-7F51-43B8-ACED-1BD380BED98D}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{B7FF739F-7716-4FC3-B622-705486187B87}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Terminal.UI", "src\modules\cmdpal\src\Microsoft.Terminal.UI\Microsoft.Terminal.UI.vcxproj", "{6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extension SDK", "Extension SDK", "{FAA24D36-5515-467C-91E7-101A189AAF48}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Windows.CommandPalette.Extensions", "src\modules\cmdpal\extensionsdk\Microsoft.Windows.CommandPalette.Extensions\Microsoft.Windows.CommandPalette.Extensions.vcxproj", "{305DD37E-C85D-4B08-AAFE-7381FA890463}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.Extensions.Helpers", "src\modules\cmdpal\extensionsdk\Microsoft.Windows.CommandPalette.Extensions.Helpers\Microsoft.CmdPal.Extensions.Helpers.csproj", "{79060D06-7174-4D66-8D0B-4FF021154049}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.CmdPal.Common", "src\modules\cmdpal\src\common\Microsoft.CmdPal.Common.csproj", "{05CDE6EE-23AE-42AF-A9F5-E398C382675F}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|ARM64 = Debug|ARM64
|
||||
Debug|x64 = Debug|x64
|
||||
Release|ARM64 = Release|ARM64
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{F71CF22B-A5C7-4328-A5B3-F4191AE57314}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{F71CF22B-A5C7-4328-A5B3-F4191AE57314}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{F71CF22B-A5C7-4328-A5B3-F4191AE57314}.Debug|ARM64.Deploy.0 = Debug|ARM64
|
||||
{F71CF22B-A5C7-4328-A5B3-F4191AE57314}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{F71CF22B-A5C7-4328-A5B3-F4191AE57314}.Debug|x64.Build.0 = Debug|x64
|
||||
{F71CF22B-A5C7-4328-A5B3-F4191AE57314}.Debug|x64.Deploy.0 = Debug|x64
|
||||
{F71CF22B-A5C7-4328-A5B3-F4191AE57314}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{F71CF22B-A5C7-4328-A5B3-F4191AE57314}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{F71CF22B-A5C7-4328-A5B3-F4191AE57314}.Release|ARM64.Deploy.0 = Release|ARM64
|
||||
{F71CF22B-A5C7-4328-A5B3-F4191AE57314}.Release|x64.ActiveCfg = Release|x64
|
||||
{F71CF22B-A5C7-4328-A5B3-F4191AE57314}.Release|x64.Build.0 = Release|x64
|
||||
{F71CF22B-A5C7-4328-A5B3-F4191AE57314}.Release|x64.Deploy.0 = Release|x64
|
||||
{C668A4BF-8BC1-48D1-B00D-FF99D05E6739}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{C668A4BF-8BC1-48D1-B00D-FF99D05E6739}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{C668A4BF-8BC1-48D1-B00D-FF99D05E6739}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{C668A4BF-8BC1-48D1-B00D-FF99D05E6739}.Debug|x64.Build.0 = Debug|x64
|
||||
{C668A4BF-8BC1-48D1-B00D-FF99D05E6739}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{C668A4BF-8BC1-48D1-B00D-FF99D05E6739}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{C668A4BF-8BC1-48D1-B00D-FF99D05E6739}.Release|x64.ActiveCfg = Release|x64
|
||||
{C668A4BF-8BC1-48D1-B00D-FF99D05E6739}.Release|x64.Build.0 = Release|x64
|
||||
{F5E5B8B9-7F51-43B8-ACED-1BD380BED98D}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{F5E5B8B9-7F51-43B8-ACED-1BD380BED98D}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{F5E5B8B9-7F51-43B8-ACED-1BD380BED98D}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{F5E5B8B9-7F51-43B8-ACED-1BD380BED98D}.Debug|x64.Build.0 = Debug|x64
|
||||
{F5E5B8B9-7F51-43B8-ACED-1BD380BED98D}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{F5E5B8B9-7F51-43B8-ACED-1BD380BED98D}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{F5E5B8B9-7F51-43B8-ACED-1BD380BED98D}.Release|x64.ActiveCfg = Release|x64
|
||||
{F5E5B8B9-7F51-43B8-ACED-1BD380BED98D}.Release|x64.Build.0 = Release|x64
|
||||
{6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Debug|x64.Build.0 = Debug|x64
|
||||
{6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Release|x64.ActiveCfg = Release|x64
|
||||
{6515F03F-E56D-4DB4-B23D-AC4FB80DB36F}.Release|x64.Build.0 = Release|x64
|
||||
{305DD37E-C85D-4B08-AAFE-7381FA890463}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{305DD37E-C85D-4B08-AAFE-7381FA890463}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{305DD37E-C85D-4B08-AAFE-7381FA890463}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{305DD37E-C85D-4B08-AAFE-7381FA890463}.Debug|x64.Build.0 = Debug|x64
|
||||
{305DD37E-C85D-4B08-AAFE-7381FA890463}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{305DD37E-C85D-4B08-AAFE-7381FA890463}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{305DD37E-C85D-4B08-AAFE-7381FA890463}.Release|x64.ActiveCfg = Release|x64
|
||||
{305DD37E-C85D-4B08-AAFE-7381FA890463}.Release|x64.Build.0 = Release|x64
|
||||
{79060D06-7174-4D66-8D0B-4FF021154049}.Debug|ARM64.ActiveCfg = Debug|arm64
|
||||
{79060D06-7174-4D66-8D0B-4FF021154049}.Debug|ARM64.Build.0 = Debug|arm64
|
||||
{79060D06-7174-4D66-8D0B-4FF021154049}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{79060D06-7174-4D66-8D0B-4FF021154049}.Debug|x64.Build.0 = Debug|x64
|
||||
{79060D06-7174-4D66-8D0B-4FF021154049}.Release|ARM64.ActiveCfg = Release|arm64
|
||||
{79060D06-7174-4D66-8D0B-4FF021154049}.Release|ARM64.Build.0 = Release|arm64
|
||||
{79060D06-7174-4D66-8D0B-4FF021154049}.Release|x64.ActiveCfg = Release|x64
|
||||
{79060D06-7174-4D66-8D0B-4FF021154049}.Release|x64.Build.0 = Release|x64
|
||||
{05CDE6EE-23AE-42AF-A9F5-E398C382675F}.Debug|ARM64.ActiveCfg = Debug|arm64
|
||||
{05CDE6EE-23AE-42AF-A9F5-E398C382675F}.Debug|ARM64.Build.0 = Debug|arm64
|
||||
{05CDE6EE-23AE-42AF-A9F5-E398C382675F}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{05CDE6EE-23AE-42AF-A9F5-E398C382675F}.Debug|x64.Build.0 = Debug|x64
|
||||
{05CDE6EE-23AE-42AF-A9F5-E398C382675F}.Release|ARM64.ActiveCfg = Release|arm64
|
||||
{05CDE6EE-23AE-42AF-A9F5-E398C382675F}.Release|ARM64.Build.0 = Release|arm64
|
||||
{05CDE6EE-23AE-42AF-A9F5-E398C382675F}.Release|x64.ActiveCfg = Release|x64
|
||||
{05CDE6EE-23AE-42AF-A9F5-E398C382675F}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{C668A4BF-8BC1-48D1-B00D-FF99D05E6739} = {B7FF739F-7716-4FC3-B622-705486187B87}
|
||||
{F5E5B8B9-7F51-43B8-ACED-1BD380BED98D} = {B7FF739F-7716-4FC3-B622-705486187B87}
|
||||
{305DD37E-C85D-4B08-AAFE-7381FA890463} = {FAA24D36-5515-467C-91E7-101A189AAF48}
|
||||
{79060D06-7174-4D66-8D0B-4FF021154049} = {FAA24D36-5515-467C-91E7-101A189AAF48}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {BC94BFC2-A741-4978-B6A4-9E01B7660E6B}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -12,10 +12,10 @@ using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1101:PrefixLocalCallsWithThis", Justification = "We follow the C# Core Coding Style which avoids using `this` unless absolutely necessary.")]
|
||||
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:ElementsMustAppearInTheCorrectOrder", Justification = "It is not a priority and have hight impact in code changes.")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:ElementsMustBeOrderedByAccess", Justification = "It is not a priority and have hight impact in code changes.")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1203:ConstantsMustAppearBeforeFields", Justification = "It is not a priority and have hight impact in code changes.")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204:StaticElementsMustAppearBeforeInstanceElements", Justification = "It is not a priority and have hight impact in code changes.")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:ElementsMustAppearInTheCorrectOrder", Justification = "It is not a priority and has high impact in code changes.")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1202:ElementsMustBeOrderedByAccess", Justification = "It is not a priority and has high impact in code changes.")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1203:ConstantsMustAppearBeforeFields", Justification = "It is not a priority and has high impact in code changes.")]
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1204:StaticElementsMustAppearBeforeInstanceElements", Justification = "It is not a priority and has high impact in code changes.")]
|
||||
|
||||
[assembly: SuppressMessage("StyleCop.CSharp.NamingRules", "SA1309:FieldNamesMustNotBeginWithUnderscore", Justification = "We follow the C# Core Coding Style which uses underscores as prefixes rather than using `this.`.")]
|
||||
|
||||
|
||||
95
src/modules/cmdpal/.clang-format
Normal file
@@ -0,0 +1,95 @@
|
||||
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
#AllowAllArgumentsOnNextLine: false
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
#AllowAllConstructorInitializersOnNextLine: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: false
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
#AllowShortLambdasOnASingleLine: Inline
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: true
|
||||
AfterClass: true
|
||||
AfterControlStatement: true
|
||||
AfterEnum: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterObjCDeclaration: true
|
||||
AfterStruct: true
|
||||
AfterUnion: true
|
||||
AfterExternBlock: true
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Custom
|
||||
BreakBeforeTernaryOperators: false
|
||||
BreakConstructorInitializers: AfterColon
|
||||
BreakInheritanceList: AfterColon
|
||||
ColumnLimit: 0
|
||||
CommentPragmas: "suppress"
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: false
|
||||
DerivePointerAlignment: false
|
||||
FixNamespaceComments: false
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^.*(precomp|pch|stdafx)'
|
||||
Priority: -1
|
||||
- Regex: '^".*"'
|
||||
Priority: 1
|
||||
- Regex: '^<.*>'
|
||||
Priority: 2
|
||||
- Regex: '.*'
|
||||
Priority: 3
|
||||
IndentCaseLabels: false
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
ForEachMacros: ['TEST_CLASS', 'TEST_METHOD']
|
||||
MacroBlockBegin: "TEST_METHOD|TEST_CLASS|BEGIN_TEST_METHOD_PROPERTIES|BEGIN_MODULE|BEGIN_TEST_CLASS|BEGIN_TEST_METHOD"
|
||||
MacroBlockEnd: "END_TEST_METHOD_PROPERTIES|END_MODULE|END_TEST_CLASS|END_TEST_METHOD"
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: All
|
||||
PointerAlignment: Left
|
||||
ReflowComments: false
|
||||
SortIncludes: false
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
#SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Cpp11
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
214
src/modules/cmdpal/.editorconfig
Normal file
@@ -0,0 +1,214 @@
|
||||
# Rules in this file were initially inferred by Visual Studio IntelliCode from the Template Studio codebase.
|
||||
# You can modify the rules from these initially generated values to suit your own policies.
|
||||
# You can learn more about editorconfig here: https://docs.microsoft.com/en-us/visualstudio/ide/editorconfig-code-style-settings-reference.
|
||||
|
||||
[*.cs]
|
||||
|
||||
file_header_template = Copyright (c) Microsoft Corporation\r\nThe Microsoft Corporation licenses this file to you under the MIT license.\r\nSee the LICENSE file in the project root for more information.
|
||||
|
||||
#Core editorconfig formatting - indentation
|
||||
|
||||
#use soft tabs (spaces) for indentation
|
||||
indent_style = space
|
||||
|
||||
#Formatting - new line options
|
||||
|
||||
#place else statements on a new line
|
||||
csharp_new_line_before_else = true
|
||||
#require braces to be on a new line for lambdas, methods, control_blocks, types, properties, and accessors (also known as "Allman" style)
|
||||
csharp_new_line_before_open_brace = all
|
||||
|
||||
#Formatting - organize using options
|
||||
|
||||
#sort System.* using directives alphabetically, and place them before other usings
|
||||
dotnet_sort_system_directives_first = true
|
||||
|
||||
#Formatting - spacing options
|
||||
|
||||
#require NO space between a cast and the value
|
||||
csharp_space_after_cast = false
|
||||
#require a space before the colon for bases or interfaces in a type declaration
|
||||
csharp_space_after_colon_in_inheritance_clause = true
|
||||
#require a space after a keyword in a control flow statement such as a for loop
|
||||
csharp_space_after_keywords_in_control_flow_statements = true
|
||||
#require a space before the colon for bases or interfaces in a type declaration
|
||||
csharp_space_before_colon_in_inheritance_clause = true
|
||||
#remove space within empty argument list parentheses
|
||||
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||
#remove space between method call name and opening parenthesis
|
||||
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||
#do not place space characters after the opening parenthesis and before the closing parenthesis of a method call
|
||||
csharp_space_between_method_call_parameter_list_parentheses = false
|
||||
#remove space within empty parameter list parentheses for a method declaration
|
||||
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
|
||||
#place a space character after the opening parenthesis and before the closing parenthesis of a method declaration parameter list.
|
||||
csharp_space_between_method_declaration_parameter_list_parentheses = false
|
||||
|
||||
#Formatting - wrapping options
|
||||
|
||||
#leave code block on separate lines
|
||||
csharp_preserve_single_line_blocks = true
|
||||
|
||||
#Style - Code block preferences
|
||||
|
||||
#prefer curly braces even for one line of code
|
||||
csharp_prefer_braces = true:suggestion
|
||||
|
||||
#Style - expression bodied member options
|
||||
|
||||
#prefer expression bodies for accessors
|
||||
csharp_style_expression_bodied_accessors = true:warning
|
||||
#prefer block bodies for constructors
|
||||
csharp_style_expression_bodied_constructors = false:suggestion
|
||||
#prefer expression bodies for methods
|
||||
csharp_style_expression_bodied_methods = when_on_single_line:silent
|
||||
#prefer expression-bodied members for properties
|
||||
csharp_style_expression_bodied_properties = true:warning
|
||||
|
||||
#Style - expression level options
|
||||
|
||||
#prefer out variables to be declared before the method call
|
||||
csharp_style_inlined_variable_declaration = false:suggestion
|
||||
#prefer the language keyword for member access expressions, instead of the type name, for types that have a keyword to represent them
|
||||
dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||
|
||||
#Style - Expression-level preferences
|
||||
|
||||
#prefer default over default(T)
|
||||
csharp_prefer_simple_default_expression = true:suggestion
|
||||
#prefer objects to be initialized using object initializers when possible
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
|
||||
#Style - implicit and explicit types
|
||||
|
||||
#prefer var over explicit type in all cases, unless overridden by another code style rule
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
#prefer var is used to declare variables with built-in system types such as int
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
#prefer var when the type is already mentioned on the right-hand side of a declaration expression
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
|
||||
#Style - language keyword and framework type options
|
||||
|
||||
#prefer the language keyword for local variables, method parameters, and class members, instead of the type name, for types that have a keyword to represent them
|
||||
dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
|
||||
|
||||
#Style - Language rules
|
||||
csharp_style_implicit_object_creation_when_type_is_apparent = true:warning
|
||||
csharp_style_var_for_built_in_types = true:warning
|
||||
|
||||
#Style - modifier options
|
||||
|
||||
#prefer accessibility modifiers to be declared except for public interface members. This will currently not differ from always and will act as future proofing for if C# adds default interface methods.
|
||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
||||
|
||||
#Style - Modifier preferences
|
||||
|
||||
#when this rule is set to a list of modifiers, prefer the specified ordering.
|
||||
csharp_preferred_modifier_order = public,private,protected,internal,static,async,readonly,override,sealed,abstract,virtual:warning
|
||||
dotnet_style_readonly_field = true:warning
|
||||
|
||||
#Style - Pattern matching
|
||||
|
||||
#prefer pattern matching instead of is expression with type casts
|
||||
csharp_style_pattern_matching_over_as_with_null_check = true:warning
|
||||
|
||||
#Style - qualification options
|
||||
|
||||
#prefer events not to be prefaced with this. or Me. in Visual Basic
|
||||
dotnet_style_qualification_for_event = false:suggestion
|
||||
#prefer fields not to be prefaced with this. or Me. in Visual Basic
|
||||
dotnet_style_qualification_for_field = false:suggestion
|
||||
#prefer methods not to be prefaced with this. or Me. in Visual Basic
|
||||
dotnet_style_qualification_for_method = false:suggestion
|
||||
#prefer properties not to be prefaced with this. or Me. in Visual Basic
|
||||
dotnet_style_qualification_for_property = false:suggestion
|
||||
csharp_indent_labels = one_less_than_current
|
||||
csharp_using_directive_placement = outside_namespace:silent
|
||||
csharp_prefer_simple_using_statement = true:warning
|
||||
csharp_style_namespace_declarations = file_scoped:warning
|
||||
csharp_style_expression_bodied_operators = false:silent
|
||||
csharp_style_expression_bodied_indexers = true:silent
|
||||
csharp_style_expression_bodied_lambdas = true:silent
|
||||
csharp_style_expression_bodied_local_functions = false:silent
|
||||
|
||||
[*.{cs,vb}]
|
||||
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||
tab_width = 4
|
||||
indent_size = 4
|
||||
end_of_line = crlf
|
||||
dotnet_style_coalesce_expression = true:suggestion
|
||||
dotnet_style_null_propagation = true:suggestion
|
||||
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
|
||||
dotnet_style_prefer_auto_properties = true:silent
|
||||
dotnet_style_object_initializer = true:suggestion
|
||||
dotnet_style_collection_initializer = true:suggestion
|
||||
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
|
||||
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
|
||||
dotnet_style_prefer_conditional_expression_over_return = true:silent
|
||||
[*.{cs,vb}]
|
||||
|
||||
#Style - Unnecessary code rules
|
||||
csharp_style_unused_value_assignment_preference = discard_variable:warning
|
||||
|
||||
#### Naming styles ####
|
||||
|
||||
# Naming rules
|
||||
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||
|
||||
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
|
||||
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
|
||||
|
||||
# Symbol specifications
|
||||
|
||||
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.interface.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.types.required_modifiers =
|
||||
|
||||
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
|
||||
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||
dotnet_naming_symbols.non_field_members.required_modifiers =
|
||||
|
||||
# Naming styles
|
||||
|
||||
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||
dotnet_naming_style.begins_with_i.required_suffix =
|
||||
dotnet_naming_style.begins_with_i.word_separator =
|
||||
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
|
||||
dotnet_naming_style.pascal_case.required_prefix =
|
||||
dotnet_naming_style.pascal_case.required_suffix =
|
||||
dotnet_naming_style.pascal_case.word_separator =
|
||||
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||
dotnet_style_explicit_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||
dotnet_style_prefer_compound_assignment = true:warning
|
||||
dotnet_style_prefer_simplified_interpolation = true:suggestion
|
||||
|
||||
# Spelling
|
||||
|
||||
spelling_exclusion_path = .\exclusion.dic
|
||||
|
||||
# Diagnostic configuration
|
||||
|
||||
# CS8305: Type is for evaluation purposes only and is subject to change or removal in future updates.
|
||||
dotnet_diagnostic.CS8305.severity = suggestion
|
||||
|
||||
5
src/modules/cmdpal/.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
# Allow launchSettings.json in the Properties folder to be committed to source
|
||||
# control. This overrides the one in the root .gitignore, because all the cmdpal
|
||||
# projects will explode without this file
|
||||
!**/Properties/launchSettings.json
|
||||
43
src/modules/cmdpal/.vsconfig
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"version": "1.0",
|
||||
"components": [
|
||||
"Microsoft.VisualStudio.Component.Roslyn.Compiler",
|
||||
"Microsoft.Component.MSBuild",
|
||||
"Microsoft.VisualStudio.Component.Roslyn.LanguageServices",
|
||||
"Microsoft.VisualStudio.Component.SQL.CLR",
|
||||
"Microsoft.VisualStudio.Component.CoreEditor",
|
||||
"Microsoft.VisualStudio.Workload.CoreEditor",
|
||||
"Microsoft.Net.Component.4.8.SDK",
|
||||
"Microsoft.Net.Component.4.7.2.TargetingPack",
|
||||
"Microsoft.Net.ComponentGroup.DevelopmentPrerequisites",
|
||||
"Microsoft.VisualStudio.Component.TypeScript.TSServer",
|
||||
"Microsoft.VisualStudio.ComponentGroup.WebToolsExtensions",
|
||||
"Microsoft.VisualStudio.Component.JavaScript.TypeScript",
|
||||
"Microsoft.VisualStudio.Component.TextTemplating",
|
||||
"Microsoft.VisualStudio.Component.NuGet",
|
||||
"Microsoft.Component.ClickOnce",
|
||||
"Microsoft.VisualStudio.Component.ManagedDesktop.Core",
|
||||
"Microsoft.NetCore.Component.SDK",
|
||||
"Microsoft.VisualStudio.Component.FSharp",
|
||||
"Microsoft.ComponentGroup.ClickOnce.Publish",
|
||||
"Microsoft.NetCore.Component.DevelopmentTools",
|
||||
"Microsoft.Net.Component.4.8.TargetingPack",
|
||||
"Microsoft.Net.ComponentGroup.4.8.DeveloperTools",
|
||||
"Microsoft.VisualStudio.Component.IntelliTrace.FrontEnd",
|
||||
"Microsoft.VisualStudio.Component.DiagnosticTools",
|
||||
"Microsoft.VisualStudio.Component.EntityFramework",
|
||||
"Microsoft.VisualStudio.Component.LiveUnitTesting",
|
||||
"Microsoft.VisualStudio.Component.Debugger.JustInTime",
|
||||
"Component.Microsoft.VisualStudio.LiveShare.2022",
|
||||
"Microsoft.VisualStudio.Component.IntelliCode",
|
||||
"Microsoft.VisualStudio.Component.Windows10SDK",
|
||||
"Microsoft.VisualStudio.ComponentGroup.MSIX.Packaging",
|
||||
"Microsoft.VisualStudio.Component.ManagedDesktop.Prerequisites",
|
||||
"Microsoft.VisualStudio.Component.DotNetModelBuilder",
|
||||
"Microsoft.VisualStudio.ComponentGroup.WindowsAppSDK.Cs",
|
||||
"Microsoft.ComponentGroup.Blend",
|
||||
"Microsoft.VisualStudio.Workload.ManagedDesktop",
|
||||
"Microsoft.VisualStudio.Component.Windows10SDK.20348",
|
||||
"Microsoft.VisualStudio.Component.Windows10SDK.19041"
|
||||
]
|
||||
}
|
||||
16
src/modules/cmdpal/.wt.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$version": "1.0.0",
|
||||
"snippets":
|
||||
[
|
||||
{
|
||||
"input": "pwsh -c .\\doc\\initial-sdk-spec\\generate-interface.ps1 > .\\extensionsdk\\Microsoft.CmdPal.Extensions\\Microsoft.CmdPal.Extensions.idl",
|
||||
"name": "Generate interface",
|
||||
"description": "Generate the interface from the SDK spec\nThis drops it into Microsoft.CmdPal.Extensions"
|
||||
},
|
||||
{
|
||||
"input": "tasklist | findstr Extension",
|
||||
"name": "List running extensions",
|
||||
"description": "This will list all running extensions, as long as they have 'Extension' in the name (they should)"
|
||||
}
|
||||
]
|
||||
}
|
||||
47
src/modules/cmdpal/Directory.Build.props
Normal file
@@ -0,0 +1,47 @@
|
||||
<Project>
|
||||
|
||||
<PropertyGroup>
|
||||
<Copyright>Copyright (C) 2022 Microsoft Corporation</Copyright>
|
||||
<AssemblyCompany>Microsoft Corp.</AssemblyCompany>
|
||||
<AssemblyCopyright>Copyright (C) 2022 Microsoft Corporation</AssemblyCopyright>
|
||||
<AssemblyProduct>CmdPal</AssemblyProduct>
|
||||
<Company>Microsoft Corporation</Company>
|
||||
<NeutralLanguage>en-US</NeutralLanguage>
|
||||
<Platforms>x64;ARM64</Platforms>
|
||||
<PackageTags>CmdPal</PackageTags>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<AnalysisMode>Recommended</AnalysisMode>
|
||||
<PlatformTarget>$(Platform)</PlatformTarget>
|
||||
<ErrorOnDuplicatePublishOutputFiles>false</ErrorOnDuplicatePublishOutputFiles>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
<PropertyGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
|
||||
<RepositoryUrl>https://github.com/microsoft/PowerToys</RepositoryUrl>
|
||||
<RepositoryType>GitHub</RepositoryType>
|
||||
</PropertyGroup>
|
||||
-->
|
||||
<PropertyGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<_PropertySheetDisplayName>CmdPal.Root.Props</_PropertySheetDisplayName>
|
||||
<ForceImportBeforeCppProps>$(MsbuildThisFileDirectory)\Cpp.Build.props</ForceImportBeforeCppProps>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
|
||||
<PackageReference Include="StyleCop.Analyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<Compile Include="$(MSBuildThisFileDirectory)\..\..\..\src\codeAnalysis\GlobalSuppressions.cs" Link="GlobalSuppressions.cs" />
|
||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)\..\..\..\src\codeAnalysis\StyleCop.json" Link="StyleCop.json" />
|
||||
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
12
src/modules/cmdpal/Directory.CppBuild.props
Normal file
@@ -0,0 +1,12 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<CppPlatformTarget>$(Platform)</CppPlatformTarget>
|
||||
<CppPlatformTarget Condition="'$(Platform)' == 'Win32'">x86</CppPlatformTarget>
|
||||
<CppBaseOutDir>$(SolutionDir)tools\bin\$(CppPlatformTarget)\$(Configuration)\</CppBaseOutDir>
|
||||
<CppOutDir>$(CppBaseOutDir)$(MSBuildProjectName)\</CppOutDir>
|
||||
</PropertyGroup>
|
||||
|
||||
<ImportGroup Label="Shared">
|
||||
<Import Project="$(SolutionDir)build\cppversion\version.vcxitems" Label="Shared" />
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
|
After Width: | Height: | Size: 432 B |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 637 B |
|
After Width: | Height: | Size: 283 B |
BIN
src/modules/cmdpal/Exts/HackerNewsExtension/Assets/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 456 B |
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,27 @@
|
||||
// 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.Diagnostics;
|
||||
using HackerNewsExtension.Data;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace HackerNewsExtension.Commands;
|
||||
|
||||
internal sealed partial class CommentCommand : InvokableCommand
|
||||
{
|
||||
private readonly NewsPost _post;
|
||||
|
||||
internal CommentCommand(NewsPost post)
|
||||
{
|
||||
_post = post;
|
||||
Name = "Open comments";
|
||||
Icon = new("\ue8f2"); // chat bubbles
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(_post.CommentsLink) { UseShellExecute = true });
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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.Diagnostics;
|
||||
using HackerNewsExtension.Data;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace HackerNewsExtension.Commands;
|
||||
|
||||
internal sealed partial class LinkCommand : InvokableCommand
|
||||
{
|
||||
private readonly NewsPost _post;
|
||||
|
||||
internal LinkCommand(NewsPost post)
|
||||
{
|
||||
_post = post;
|
||||
Name = "Open link";
|
||||
Icon = new("\uE8A7");
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(_post.Link) { UseShellExecute = true });
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
16
src/modules/cmdpal/Exts/HackerNewsExtension/Data/NewsPost.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace HackerNewsExtension.Data;
|
||||
|
||||
public sealed class NewsPost
|
||||
{
|
||||
public string Title { get; init; } = string.Empty;
|
||||
|
||||
public string Link { get; init; } = string.Empty;
|
||||
|
||||
public string CommentsLink { get; init; } = string.Empty;
|
||||
|
||||
public string Poster { get; init; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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 Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace HackerNewsExtension;
|
||||
|
||||
public partial class HackerNewsCommandsProvider : ICommandProvider
|
||||
{
|
||||
public string DisplayName => $"Hacker News Commands";
|
||||
|
||||
public IconDataType Icon => new(string.Empty);
|
||||
|
||||
private readonly IListItem[] _actions = [
|
||||
new ListItem(new HackerNewsPage()),
|
||||
];
|
||||
|
||||
#pragma warning disable CA1816 // Dispose methods should call SuppressFinalize
|
||||
public void Dispose() => throw new NotImplementedException();
|
||||
#pragma warning restore CA1816 // Dispose methods should call SuppressFinalize
|
||||
|
||||
public IListItem[] TopLevelCommands()
|
||||
{
|
||||
return _actions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>HackerNewsExtension</RootNamespace>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
|
||||
<UseWinUI>false</UseWinUI>
|
||||
<EnableMsixTooling>true</EnableMsixTooling>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Assets\SplashScreen.scale-200.png" />
|
||||
<Content Include="Assets\LockScreenLogo.scale-200.png" />
|
||||
<Content Include="Assets\Square150x150Logo.scale-200.png" />
|
||||
<Content Include="Assets\Square44x44Logo.scale-200.png" />
|
||||
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
|
||||
<Content Include="Assets\StoreLogo.png" />
|
||||
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\extensionsdk\Microsoft.CmdPal.Extensions.Helpers\Microsoft.CmdPal.Extensions.Helpers.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
|
||||
Tools extension to be activated for this project even if the Windows App SDK Nuget
|
||||
package has not yet been restored.
|
||||
-->
|
||||
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<ProjectCapability Include="Msix" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
|
||||
Explorer "Package and Publish" context menu entry to be enabled for this project even if
|
||||
the Windows App SDK Nuget package has not yet been restored.
|
||||
-->
|
||||
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
|
||||
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap uap3 rescap">
|
||||
|
||||
<Identity
|
||||
Name="HackerNewsExtension"
|
||||
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
|
||||
Version="1.0.0.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>HackerNews Sample Extension</DisplayName>
|
||||
<PublisherDisplayName>Microsoft Corporation</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="HackerNews Sample Extension"
|
||||
Description="HackerNews Sample Extension"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
<Extensions>
|
||||
<com:Extension Category="windows.comServer">
|
||||
<com:ComServer>
|
||||
<com:ExeServer Executable="HackerNewsExtension.exe" Arguments="-RegisterProcessAsComServer" DisplayName="ClementineExtensionApp">
|
||||
<com:Class Id="283DDB0F-1AD9-406F-B359-699BFBD2DA68" DisplayName="HackerNewsExtension" />
|
||||
</com:ExeServer>
|
||||
</com:ComServer>
|
||||
</com:Extension>
|
||||
<uap3:Extension Category="windows.appExtension">
|
||||
<uap3:AppExtension Name="com.microsoft.windows.commandpalette"
|
||||
Id="PG-SP-ID"
|
||||
PublicFolder="Public"
|
||||
DisplayName="Hacker News Sample"
|
||||
Description="Hacker News Extension for Run">
|
||||
<uap3:Properties>
|
||||
<CmdPalProvider>
|
||||
<Activation>
|
||||
<CreateInstance ClassId="283DDB0F-1AD9-406F-B359-699BFBD2DA68" />
|
||||
</Activation>
|
||||
<SupportedInterfaces>
|
||||
<Commands/>
|
||||
</SupportedInterfaces>
|
||||
</CmdPalProvider>
|
||||
</uap3:Properties>
|
||||
</uap3:AppExtension>
|
||||
</uap3:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
@@ -0,0 +1,71 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using HackerNewsExtension.Commands;
|
||||
using HackerNewsExtension.Data;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace HackerNewsExtension;
|
||||
|
||||
internal sealed partial class HackerNewsPage : ListPage
|
||||
{
|
||||
public HackerNewsPage()
|
||||
{
|
||||
Icon = new("https://news.ycombinator.com/favicon.ico");
|
||||
Name = "Hacker News";
|
||||
}
|
||||
|
||||
private static async Task<List<NewsPost>> GetHackerNewsTopPosts()
|
||||
{
|
||||
var posts = new List<NewsPost>();
|
||||
|
||||
using (HttpClient client = new HttpClient())
|
||||
{
|
||||
var response = await client.GetStringAsync("https://news.ycombinator.com/rss");
|
||||
var xdoc = XDocument.Parse(response);
|
||||
var x = xdoc.Descendants("item").First();
|
||||
posts = xdoc.Descendants("item")
|
||||
.Take(20)
|
||||
.Select(item => new NewsPost()
|
||||
{
|
||||
Title = item.Element("title")?.Value ?? string.Empty,
|
||||
Link = item.Element("link")?.Value ?? string.Empty,
|
||||
CommentsLink = item.Element("comments")?.Value ?? string.Empty,
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
return posts;
|
||||
}
|
||||
|
||||
public override ISection[] GetItems()
|
||||
{
|
||||
var t = DoGetItems();
|
||||
t.ConfigureAwait(false);
|
||||
return t.Result;
|
||||
}
|
||||
|
||||
private async Task<ISection[]> DoGetItems()
|
||||
{
|
||||
List<NewsPost> items = await GetHackerNewsTopPosts();
|
||||
this.Loading = false;
|
||||
var s = new ListSection()
|
||||
{
|
||||
Title = "Posts",
|
||||
Items = items.Select((post) => new ListItem(new LinkCommand(post))
|
||||
{
|
||||
Title = post.Title,
|
||||
Subtitle = post.Link,
|
||||
MoreCommands = [new CommandContextItem(new CommentCommand(post))],
|
||||
}).ToArray(),
|
||||
};
|
||||
return [s];
|
||||
}
|
||||
}
|
||||
36
src/modules/cmdpal/Exts/HackerNewsExtension/Program.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
// 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.Threading;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
|
||||
namespace HackerNewsExtension;
|
||||
|
||||
public class Program
|
||||
{
|
||||
[MTAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
if (args.Length > 0 && args[0] == "-RegisterProcessAsComServer")
|
||||
{
|
||||
using ExtensionServer server = new();
|
||||
var extensionDisposedEvent = new ManualResetEvent(false);
|
||||
var extensionInstance = new SampleExtension(extensionDisposedEvent);
|
||||
|
||||
// We are instantiating an extension instance once above, and returning it every time the callback in RegisterExtension below is called.
|
||||
// This makes sure that only one instance of SampleExtension is alive, which is returned every time the host asks for the IExtension object.
|
||||
// If you want to instantiate a new instance each time the host asks, create the new instance inside the delegate.
|
||||
server.RegisterExtension(() => extensionInstance);
|
||||
|
||||
// This will make the main thread wait until the event is signalled by the extension class.
|
||||
// Since we have single instance of the extension object, we exit as sooon as it is disposed.
|
||||
extensionDisposedEvent.WaitOne();
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Not being launched as a Extension... exiting.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<Platform>ARM64</Platform>
|
||||
<RuntimeIdentifier>win-arm64</RuntimeIdentifier>
|
||||
<PublishDir>bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\</PublishDir>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>False</PublishSingleFile>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
|
||||
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<Platform>x64</Platform>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<PublishDir>bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\</PublishDir>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>False</PublishSingleFile>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
|
||||
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">False</PublishTrimmed>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"profiles": {
|
||||
"HackerNewsExtension (Package)": {
|
||||
"commandName": "MsixPackage"
|
||||
},
|
||||
"HackerNewsExtension (Unpackaged)": {
|
||||
"commandName": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
|
||||
namespace HackerNewsExtension;
|
||||
|
||||
[ComVisible(true)]
|
||||
[Guid("283DDB0F-1AD9-406F-B359-699BFBD2DA68")]
|
||||
[ComDefaultInterface(typeof(IExtension))]
|
||||
public sealed partial class SampleExtension : IExtension
|
||||
{
|
||||
private readonly ManualResetEvent _extensionDisposedEvent;
|
||||
|
||||
public SampleExtension(ManualResetEvent extensionDisposedEvent)
|
||||
{
|
||||
this._extensionDisposedEvent = extensionDisposedEvent;
|
||||
}
|
||||
|
||||
public object GetProvider(ProviderType providerType)
|
||||
{
|
||||
switch (providerType)
|
||||
{
|
||||
case ProviderType.Commands:
|
||||
return new HackerNewsCommandsProvider();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this._extensionDisposedEvent.Set();
|
||||
}
|
||||
}
|
||||
19
src/modules/cmdpal/Exts/HackerNewsExtension/app.manifest
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="HackerNewsExtension.app"/>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- The ID below informs the system that this application is compatible with OS features first introduced in Windows 10.
|
||||
It is necessary to support features in unpackaged applications, for example the custom titlebar implementation.
|
||||
For more info see https://docs.microsoft.com/windows/apps/windows-app-sdk/use-windows-app-sdk-run-time#declare-os-compatibility-in-your-application-manifest -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
BIN
src/modules/cmdpal/Exts/MastodonExtension/Assets/StoreLogo.png
Normal file
@@ -0,0 +1,48 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>MastodonExtension</RootNamespace>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
|
||||
<UseWinUI>false</UseWinUI>
|
||||
<EnableMsixTooling>true</EnableMsixTooling>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Assets\SplashScreen.scale-200.png" />
|
||||
<Content Include="Assets\LockScreenLogo.scale-200.png" />
|
||||
<Content Include="Assets\Square150x150Logo.scale-200.png" />
|
||||
<Content Include="Assets\Square44x44Logo.scale-200.png" />
|
||||
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
|
||||
<Content Include="Assets\StoreLogo.png" />
|
||||
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\extensionsdk\Microsoft.CmdPal.Extensions.Helpers\Microsoft.CmdPal.Extensions.Helpers.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
|
||||
Tools extension to be activated for this project even if the Windows App SDK Nuget
|
||||
package has not yet been restored.
|
||||
-->
|
||||
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<ProjectCapability Include="Msix" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
|
||||
Explorer "Package and Publish" context menu entry to be enabled for this project even if
|
||||
the Windows App SDK Nuget package has not yet been restored.
|
||||
-->
|
||||
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,52 @@
|
||||
// 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 Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace MastodonExtension;
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "This is sample code")]
|
||||
internal sealed partial class MastodonExtensionPage : ListPage
|
||||
{
|
||||
public MastodonExtensionPage()
|
||||
{
|
||||
Icon = new("https://mastodon.social/packs/media/icons/android-chrome-36x36-4c61fdb42936428af85afdbf8c6a45a8.png");
|
||||
Name = "Mastodon";
|
||||
}
|
||||
|
||||
public override ISection[] GetItems()
|
||||
{
|
||||
return [
|
||||
new ListSection()
|
||||
{
|
||||
Items = [
|
||||
new ListItem(new NoOpCommand()) { Title = "TODO: Implement your extension here" }
|
||||
],
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "This is sample code")]
|
||||
public partial class MastodonExtensionActionsProvider : ICommandProvider
|
||||
{
|
||||
public string DisplayName => $"Mastodon extension for cmdpal Commands";
|
||||
|
||||
public IconDataType Icon => new(string.Empty);
|
||||
|
||||
private readonly IListItem[] _actions = [
|
||||
new ListItem(new MastodonExtensionPage()),
|
||||
];
|
||||
|
||||
#pragma warning disable CA1816 // Dispose methods should call SuppressFinalize
|
||||
public void Dispose() => throw new NotImplementedException();
|
||||
#pragma warning restore CA1816 // Dispose methods should call SuppressFinalize
|
||||
|
||||
public IListItem[] TopLevelCommands()
|
||||
{
|
||||
return _actions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
|
||||
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap uap3 rescap">
|
||||
|
||||
<Identity
|
||||
Name="MastodonExtension"
|
||||
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
|
||||
Version="0.0.1.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Mastodon extension for cmdpal</DisplayName>
|
||||
<PublisherDisplayName>A Lone Developer</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="Mastodon extension for cmdpal"
|
||||
Description="Mastodon extension for cmdpal"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
<Extensions>
|
||||
<com:Extension Category="windows.comServer">
|
||||
<com:ComServer>
|
||||
<com:ExeServer Executable="MastodonExtension.exe" Arguments="-RegisterProcessAsComServer" DisplayName="ClementineExtensionApp">
|
||||
<com:Class Id="f0e93f1a-2b64-4896-abcc-8d2145480ede" DisplayName="Mastodon extension for cmdpal" />
|
||||
</com:ExeServer>
|
||||
</com:ComServer>
|
||||
</com:Extension>
|
||||
<uap3:Extension Category="windows.appExtension">
|
||||
<uap3:AppExtension Name="com.microsoft.windows.commandpalette"
|
||||
Id="PG-SP-ID"
|
||||
PublicFolder="Public"
|
||||
DisplayName="Mastodon extension for cmdpal"
|
||||
Description="Mastodon extension for cmdpal">
|
||||
<uap3:Properties>
|
||||
<CmdPalProvider>
|
||||
<Activation>
|
||||
<CreateInstance ClassId="f0e93f1a-2b64-4896-abcc-8d2145480ede" />
|
||||
</Activation>
|
||||
<SupportedInterfaces>
|
||||
<Commands/>
|
||||
</SupportedInterfaces>
|
||||
</CmdPalProvider>
|
||||
</uap3:Properties>
|
||||
</uap3:AppExtension>
|
||||
</uap3:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
36
src/modules/cmdpal/Exts/MastodonExtension/Program.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
// 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.Threading;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
|
||||
namespace MastodonExtension;
|
||||
|
||||
public class Program
|
||||
{
|
||||
[MTAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
if (args.Length > 0 && args[0] == "-RegisterProcessAsComServer")
|
||||
{
|
||||
using ExtensionServer server = new();
|
||||
var extensionDisposedEvent = new ManualResetEvent(false);
|
||||
var extensionInstance = new SampleExtension(extensionDisposedEvent);
|
||||
|
||||
// We are instantiating an extension instance once above, and returning it every time the callback in RegisterExtension below is called.
|
||||
// This makes sure that only one instance of SampleExtension is alive, which is returned every time the host asks for the IExtension object.
|
||||
// If you want to instantiate a new instance each time the host asks, create the new instance inside the delegate.
|
||||
server.RegisterExtension(() => extensionInstance);
|
||||
|
||||
// This will make the main thread wait until the event is signalled by the extension class.
|
||||
// Since we have single instance of the extension object, we exit as sooon as it is disposed.
|
||||
extensionDisposedEvent.WaitOne();
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Not being launched as a Extension... exiting.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<Platform>ARM64</Platform>
|
||||
<RuntimeIdentifier>win-arm64</RuntimeIdentifier>
|
||||
<PublishDir>bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\</PublishDir>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>False</PublishSingleFile>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
|
||||
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">False</PublishTrimmed>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<Platform>x64</Platform>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<PublishDir>bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\</PublishDir>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>False</PublishSingleFile>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
|
||||
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">False</PublishTrimmed>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"profiles": {
|
||||
"MastodonExtension (Package)": {
|
||||
"commandName": "MsixPackage"
|
||||
},
|
||||
"MastodonExtension (Unpackaged)": {
|
||||
"commandName": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/modules/cmdpal/Exts/MastodonExtension/SampleExtension.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
|
||||
namespace MastodonExtension;
|
||||
|
||||
[ComVisible(true)]
|
||||
[Guid("f0e93f1a-2b64-4896-abcc-8d2145480ede")]
|
||||
[ComDefaultInterface(typeof(IExtension))]
|
||||
public sealed partial class SampleExtension : IExtension
|
||||
{
|
||||
private readonly ManualResetEvent _extensionDisposedEvent;
|
||||
|
||||
public SampleExtension(ManualResetEvent extensionDisposedEvent)
|
||||
{
|
||||
this._extensionDisposedEvent = extensionDisposedEvent;
|
||||
}
|
||||
|
||||
public object GetProvider(ProviderType providerType)
|
||||
{
|
||||
switch (providerType)
|
||||
{
|
||||
case ProviderType.Commands:
|
||||
return new MastodonExtensionActionsProvider();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this._extensionDisposedEvent.Set();
|
||||
}
|
||||
}
|
||||
19
src/modules/cmdpal/Exts/MastodonExtension/app.manifest
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="HackerNewsExtension.app"/>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- The ID below informs the system that this application is compatible with OS features first introduced in Windows 10.
|
||||
It is necessary to support features in unpackaged applications, for example the custom titlebar implementation.
|
||||
For more info see https://docs.microsoft.com/windows/apps/windows-app-sdk/use-windows-app-sdk-run-time#declare-os-compatibility-in-your-application-manifest -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
|
After Width: | Height: | Size: 432 B |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 637 B |
|
After Width: | Height: | Size: 283 B |
|
After Width: | Height: | Size: 456 B |
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,28 @@
|
||||
// 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 Microsoft.CmdPal.Extensions;
|
||||
|
||||
namespace MediaControlsExtension;
|
||||
|
||||
public partial class MediaActionsProvider : ICommandProvider
|
||||
{
|
||||
public string DisplayName => $"Media controls actions";
|
||||
|
||||
public IconDataType Icon => new(string.Empty);
|
||||
|
||||
private readonly IListItem[] _actions = [
|
||||
new MediaListItem()
|
||||
];
|
||||
|
||||
#pragma warning disable CA1816 // Dispose methods should call SuppressFinalize
|
||||
public void Dispose() => throw new NotImplementedException();
|
||||
#pragma warning restore CA1816 // Dispose methods should call SuppressFinalize
|
||||
|
||||
public IListItem[] TopLevelCommands()
|
||||
{
|
||||
return _actions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>MediaControlsExtension</RootNamespace>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
|
||||
<UseWinUI>false</UseWinUI>
|
||||
<EnableMsixTooling>true</EnableMsixTooling>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="Assets\SplashScreen.scale-200.png" />
|
||||
<Content Include="Assets\LockScreenLogo.scale-200.png" />
|
||||
<Content Include="Assets\Square150x150Logo.scale-200.png" />
|
||||
<Content Include="Assets\Square44x44Logo.scale-200.png" />
|
||||
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" />
|
||||
<Content Include="Assets\StoreLogo.png" />
|
||||
<Content Include="Assets\Wide310x150Logo.scale-200.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Manifest Include="$(ApplicationManifest)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\extensionsdk\Microsoft.CmdPal.Extensions.Helpers\Microsoft.CmdPal.Extensions.Helpers.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging
|
||||
Tools extension to be activated for this project even if the Windows App SDK Nuget
|
||||
package has not yet been restored.
|
||||
-->
|
||||
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<ProjectCapability Include="Msix" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution
|
||||
Explorer "Package and Publish" context menu entry to be enabled for this project even if
|
||||
the Windows App SDK Nuget package has not yet been restored.
|
||||
-->
|
||||
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'">
|
||||
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,89 @@
|
||||
// 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.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
using Windows.Media.Control;
|
||||
|
||||
namespace MediaControlsExtension;
|
||||
|
||||
internal sealed partial class MediaListItem : ListItem
|
||||
{
|
||||
private GlobalSystemMediaTransportControlsSession _mediaSession;
|
||||
|
||||
public MediaListItem()
|
||||
: base(new TogglePlayMediaAction())
|
||||
{
|
||||
var task = GlobalSystemMediaTransportControlsSessionManager.RequestAsync().AsTask();
|
||||
task.ContinueWith(async t =>
|
||||
{
|
||||
var manager = t.Result;
|
||||
_mediaSession = manager.GetCurrentSession();
|
||||
((TogglePlayMediaAction)this.Command).MediaSession = _mediaSession;
|
||||
|
||||
_mediaSession.MediaPropertiesChanged += MediaSession_MediaPropertiesChanged;
|
||||
_mediaSession.PlaybackInfoChanged += MediaSession_PlaybackInfoChanged;
|
||||
|
||||
// mediaSession.TimelinePropertiesChanged += MediaSession_TimelinePropertiesChanged;
|
||||
await this.UpdateProperties();
|
||||
});
|
||||
|
||||
// task.Start();
|
||||
MoreCommands = null;
|
||||
}
|
||||
|
||||
private async Task UpdateProperties()
|
||||
{
|
||||
var properties = await this._mediaSession.TryGetMediaPropertiesAsync().AsTask();
|
||||
|
||||
if (properties == null)
|
||||
{
|
||||
var a = (TogglePlayMediaAction)this.Command;
|
||||
a.Icon = new(string.Empty);
|
||||
a.Name = "No media playing";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.Title = properties.Title;
|
||||
|
||||
// hack
|
||||
((TogglePlayMediaAction)this.Command).Name = this.Title;
|
||||
this.Subtitle = properties.Artist;
|
||||
var status = _mediaSession.GetPlaybackInfo().PlaybackStatus;
|
||||
|
||||
var internalAction = (TogglePlayMediaAction)this.Command;
|
||||
if (status == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Paused)
|
||||
{
|
||||
internalAction.Icon = new("\ue768"); // play
|
||||
internalAction.Name = "Paused";
|
||||
}
|
||||
else if (status == GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing)
|
||||
{
|
||||
internalAction.Icon = new("\ue769"); // pause
|
||||
internalAction.Name = "Playing";
|
||||
}
|
||||
|
||||
MoreCommands = [
|
||||
new CommandContextItem(new PrevNextTrackAction(true, _mediaSession)),
|
||||
new CommandContextItem(new PrevNextTrackAction(false, _mediaSession))
|
||||
];
|
||||
}
|
||||
|
||||
private void MediaSession_TimelinePropertiesChanged(GlobalSystemMediaTransportControlsSession sender, TimelinePropertiesChangedEventArgs args)
|
||||
{
|
||||
_ = UpdateProperties();
|
||||
}
|
||||
|
||||
private void MediaSession_PlaybackInfoChanged(GlobalSystemMediaTransportControlsSession sender, PlaybackInfoChangedEventArgs args)
|
||||
{
|
||||
_ = UpdateProperties();
|
||||
}
|
||||
|
||||
private void MediaSession_MediaPropertiesChanged(GlobalSystemMediaTransportControlsSession sender, MediaPropertiesChangedEventArgs args)
|
||||
{
|
||||
_ = UpdateProperties();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<Package
|
||||
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
|
||||
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
IgnorableNamespaces="uap uap3 rescap">
|
||||
|
||||
<Identity
|
||||
Name="MediaControlsExtension"
|
||||
Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
|
||||
Version="1.0.0.0" />
|
||||
|
||||
<Properties>
|
||||
<DisplayName>Media Controls Sample Extension</DisplayName>
|
||||
<PublisherDisplayName>Microsoft Corporation</PublisherDisplayName>
|
||||
<Logo>Assets\StoreLogo.png</Logo>
|
||||
</Properties>
|
||||
|
||||
<Dependencies>
|
||||
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.19041.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Resources>
|
||||
<Resource Language="x-generate"/>
|
||||
</Resources>
|
||||
|
||||
<Applications>
|
||||
<Application Id="App"
|
||||
Executable="$targetnametoken$.exe"
|
||||
EntryPoint="$targetentrypoint$">
|
||||
<uap:VisualElements
|
||||
DisplayName="Media Controls Sample Extension"
|
||||
Description="Media Controls Sample Extension"
|
||||
BackgroundColor="transparent"
|
||||
Square150x150Logo="Assets\Square150x150Logo.png"
|
||||
Square44x44Logo="Assets\Square44x44Logo.png">
|
||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
|
||||
<uap:SplashScreen Image="Assets\SplashScreen.png" />
|
||||
</uap:VisualElements>
|
||||
<Extensions>
|
||||
<com:Extension Category="windows.comServer">
|
||||
<com:ComServer>
|
||||
<com:ExeServer Executable="MediaControlsExtension.exe" Arguments="-RegisterProcessAsComServer" DisplayName="MediaControlsExtensionApp">
|
||||
<com:Class Id="bb60a98a-0197-4378-9b40-b684f4068d1d" DisplayName="MediaControlsExtension" />
|
||||
</com:ExeServer>
|
||||
</com:ComServer>
|
||||
</com:Extension>
|
||||
<uap3:Extension Category="windows.appExtension">
|
||||
<uap3:AppExtension Name="com.microsoft.windows.commandpalette"
|
||||
Id="PG-SP-ID"
|
||||
PublicFolder="Public"
|
||||
DisplayName="Media Controls Sample Extension"
|
||||
Description="Media Controls for Run">
|
||||
<uap3:Properties>
|
||||
<CmdPalProvider>
|
||||
<Activation>
|
||||
<CreateInstance ClassId="bb60a98a-0197-4378-9b40-b684f4068d1d" />
|
||||
</Activation>
|
||||
<SupportedInterfaces>
|
||||
<Commands/>
|
||||
</SupportedInterfaces>
|
||||
</CmdPalProvider>
|
||||
</uap3:Properties>
|
||||
</uap3:AppExtension>
|
||||
</uap3:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
|
||||
<Capabilities>
|
||||
<Capability Name="internetClient" />
|
||||
<rescap:Capability Name="runFullTrust" />
|
||||
</Capabilities>
|
||||
</Package>
|
||||
@@ -0,0 +1,46 @@
|
||||
// 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 Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
using Windows.Media.Control;
|
||||
|
||||
namespace MediaControlsExtension;
|
||||
|
||||
internal sealed partial class PrevNextTrackAction : InvokableCommand
|
||||
{
|
||||
private readonly GlobalSystemMediaTransportControlsSession _mediaSession;
|
||||
private readonly bool _previous;
|
||||
|
||||
public PrevNextTrackAction(bool previous, GlobalSystemMediaTransportControlsSession s)
|
||||
{
|
||||
_mediaSession = s;
|
||||
_previous = previous;
|
||||
|
||||
if (previous)
|
||||
{
|
||||
Name = "Previous track";
|
||||
Icon = new("\ue892");
|
||||
}
|
||||
else
|
||||
{
|
||||
Name = "Next track";
|
||||
Icon = new("\ue893");
|
||||
}
|
||||
}
|
||||
|
||||
public override ICommandResult Invoke()
|
||||
{
|
||||
if (_previous)
|
||||
{
|
||||
_ = _mediaSession.TrySkipPreviousAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = _mediaSession.TrySkipNextAsync();
|
||||
}
|
||||
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
36
src/modules/cmdpal/Exts/MediaControlsExtension/Program.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
// 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.Threading;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
|
||||
namespace MediaControlsExtension;
|
||||
|
||||
public class Program
|
||||
{
|
||||
[MTAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
if (args.Length > 0 && args[0] == "-RegisterProcessAsComServer")
|
||||
{
|
||||
using ExtensionServer server = new();
|
||||
var extensionDisposedEvent = new ManualResetEvent(false);
|
||||
var extensionInstance = new SampleExtension(extensionDisposedEvent);
|
||||
|
||||
// We are instantiating an extension instance once above, and returning it every time the callback in RegisterExtension below is called.
|
||||
// This makes sure that only one instance of SampleExtension is alive, which is returned every time the host asks for the IExtension object.
|
||||
// If you want to instantiate a new instance each time the host asks, create the new instance inside the delegate.
|
||||
server.RegisterExtension(() => extensionInstance);
|
||||
|
||||
// This will make the main thread wait until the event is signalled by the extension class.
|
||||
// Since we have single instance of the extension object, we exit as sooon as it is disposed.
|
||||
extensionDisposedEvent.WaitOne();
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Not being launched as a Extension... exiting.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<Platform>ARM64</Platform>
|
||||
<RuntimeIdentifier>win-arm64</RuntimeIdentifier>
|
||||
<PublishDir>bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\</PublishDir>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>False</PublishSingleFile>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
|
||||
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">False</PublishTrimmed>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<Platform>x64</Platform>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<PublishDir>bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\</PublishDir>
|
||||
<SelfContained>true</SelfContained>
|
||||
<PublishSingleFile>False</PublishSingleFile>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun>
|
||||
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed>
|
||||
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">False</PublishTrimmed>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"profiles": {
|
||||
"MediaControlsExtension (Package)": {
|
||||
"commandName": "MsixPackage"
|
||||
},
|
||||
"MediaControlsExtension (Unpackaged)": {
|
||||
"commandName": "Project"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
|
||||
namespace MediaControlsExtension;
|
||||
|
||||
[ComVisible(true)]
|
||||
[Guid("bb60a98a-0197-4378-9b40-b684f4068d1d")]
|
||||
[ComDefaultInterface(typeof(IExtension))]
|
||||
public sealed partial class SampleExtension : IExtension
|
||||
{
|
||||
private readonly ManualResetEvent _extensionDisposedEvent;
|
||||
|
||||
public SampleExtension(ManualResetEvent extensionDisposedEvent)
|
||||
{
|
||||
this._extensionDisposedEvent = extensionDisposedEvent;
|
||||
}
|
||||
|
||||
public object GetProvider(ProviderType providerType)
|
||||
{
|
||||
switch (providerType)
|
||||
{
|
||||
case ProviderType.Commands:
|
||||
return new MediaActionsProvider();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
this._extensionDisposedEvent.Set();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// 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 Microsoft.CmdPal.Extensions.Helpers;
|
||||
using Windows.Media.Control;
|
||||
|
||||
namespace MediaControlsExtension;
|
||||
|
||||
public sealed partial class TogglePlayMediaAction : InvokableCommand
|
||||
{
|
||||
public GlobalSystemMediaTransportControlsSession MediaSession { get; set; }
|
||||
|
||||
public TogglePlayMediaAction()
|
||||
{
|
||||
Name = "No media playing";
|
||||
Icon = new(string.Empty);
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
if (MediaSession != null)
|
||||
{
|
||||
_ = MediaSession.TryTogglePlayPauseAsync();
|
||||
}
|
||||
|
||||
return CommandResult.KeepOpen();
|
||||
}
|
||||
}
|
||||
19
src/modules/cmdpal/Exts/MediaControlsExtension/app.manifest
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="1.0.0.0" name="MediaControlsExtension.app"/>
|
||||
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- The ID below informs the system that this application is compatible with OS features first introduced in Windows 10.
|
||||
It is necessary to support features in unpackaged applications, for example the custom titlebar implementation.
|
||||
For more info see https://docs.microsoft.com/windows/apps/windows-app-sdk/use-windows-app-sdk-run-time#declare-os-compatibility-in-your-application-manifest -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||
</application>
|
||||
</compatibility>
|
||||
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
</assembly>
|
||||
@@ -0,0 +1,86 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
public sealed partial class AllAppsPage : ListPage
|
||||
{
|
||||
private ISection allAppsSection = new ListSection();
|
||||
|
||||
public AllAppsPage()
|
||||
{
|
||||
StringMatcher.Instance = new StringMatcher();
|
||||
this.Name = "All Apps";
|
||||
this.Icon = new("\ue71d");
|
||||
this.ShowDetails = true;
|
||||
this.Loading = true;
|
||||
this.PlaceholderText = "Search installed apps...";
|
||||
}
|
||||
|
||||
public override ISection[] GetItems()
|
||||
{
|
||||
if (this.allAppsSection == null || allAppsSection.Items.Length == 0)
|
||||
{
|
||||
var apps = GetPrograms();
|
||||
this.Loading = false;
|
||||
this.allAppsSection = new ListSection()
|
||||
{
|
||||
Title = "Apps",
|
||||
Items = apps
|
||||
.Select((app) => new AppListItem(app))
|
||||
.ToArray(),
|
||||
};
|
||||
}
|
||||
|
||||
return [allAppsSection];
|
||||
}
|
||||
|
||||
internal static List<AppItem> GetPrograms()
|
||||
{
|
||||
// NOTE TO SELF:
|
||||
//
|
||||
// There's logic in Win32Program.All() here to pick the "sources" for programs.
|
||||
// I've manually hardcoded it to:
|
||||
// * StartMenuProgramPaths
|
||||
// * DesktopProgramPaths
|
||||
// * RegistryAppProgramPaths
|
||||
// for now. I've disabled the "PATH" source too, because it's n o i s y
|
||||
//
|
||||
// This also doesn't include Packaged apps, cause they're enumerated entirely separately.
|
||||
var cache = AppCache.Instance.Value;
|
||||
var uwps = cache.UWPs;
|
||||
var win32s = cache.Win32s;
|
||||
var uwpResults = uwps
|
||||
.Where((application) => application.Enabled /*&& application.Valid*/)
|
||||
.Select(app =>
|
||||
new AppItem
|
||||
{
|
||||
Name = app.Name,
|
||||
Subtitle = app.Description,
|
||||
IcoPath = app.LogoType != LogoType.Error ? app.LogoPath : string.Empty,
|
||||
DirPath = app.Location,
|
||||
UserModelId = app.UserModelId,
|
||||
|
||||
// ExePath = app.FullPath,
|
||||
});
|
||||
var win32Results = win32s
|
||||
.Where((application) => application.Enabled /*&& application.Valid*/)
|
||||
.Select(app =>
|
||||
new AppItem
|
||||
{
|
||||
Name = app.Name,
|
||||
Subtitle = app.Description,
|
||||
IcoPath = app.FullPath, // similarly, this should be IcoPath, but :shrug:
|
||||
ExePath = app.FullPath,
|
||||
DirPath = app.Location,
|
||||
});
|
||||
|
||||
return uwpResults.Concat(win32Results).OrderBy(app => app.Name).ToList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
internal sealed partial class AppAction : InvokableCommand
|
||||
{
|
||||
private readonly AppItem _app;
|
||||
|
||||
internal AppAction(AppItem app)
|
||||
{
|
||||
_app = app;
|
||||
|
||||
Name = "Run";
|
||||
Icon = new(_app.IcoPath);
|
||||
}
|
||||
|
||||
internal static async Task StartApp(string aumid)
|
||||
{
|
||||
var appManager = new ApplicationActivationManager();
|
||||
const ActivateOptions noFlags = ActivateOptions.None;
|
||||
await Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
appManager.ActivateApplication(aumid, /*queryArguments*/ string.Empty, noFlags, out var unusedPid);
|
||||
}
|
||||
catch (System.Exception)
|
||||
{
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
internal static async Task StartExe(string path)
|
||||
{
|
||||
var appManager = new ApplicationActivationManager();
|
||||
|
||||
// const ActivateOptions noFlags = ActivateOptions.None;
|
||||
await Task.Run(() =>
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
|
||||
});
|
||||
}
|
||||
|
||||
internal async Task Launch()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_app.ExePath))
|
||||
{
|
||||
await StartApp(_app.UserModelId);
|
||||
}
|
||||
else
|
||||
{
|
||||
await StartExe(_app.ExePath);
|
||||
}
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
_ = Launch();
|
||||
return CommandResult.GoHome();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// 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;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
public sealed class AppCache
|
||||
{
|
||||
private readonly IList<Win32Program> _win32s = Win32Program.All();
|
||||
private readonly IList<UWPApplication> _uwps = UWP.All();
|
||||
|
||||
public IList<Win32Program> Win32s => _win32s;
|
||||
|
||||
public IList<UWPApplication> UWPs => _uwps;
|
||||
|
||||
public static readonly Lazy<AppCache> Instance = new(() => new());
|
||||
}
|
||||
24
src/modules/cmdpal/Exts/Microsoft.CmdPal.Ext.Apps/AppItem.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
internal sealed class AppItem
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string Subtitle { get; set; } = string.Empty;
|
||||
|
||||
public string IcoPath { get; set; } = string.Empty;
|
||||
|
||||
public string ExePath { get; set; } = string.Empty;
|
||||
|
||||
public string DirPath { get; set; } = string.Empty;
|
||||
|
||||
public string UserModelId { get; set; } = string.Empty;
|
||||
|
||||
public AppItem()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// 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 Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
internal sealed partial class AppListItem : ListItem
|
||||
{
|
||||
private readonly AppItem _app;
|
||||
|
||||
public AppListItem(AppItem app)
|
||||
: base(new AppAction(app))
|
||||
{
|
||||
_app = app;
|
||||
Title = app.Name;
|
||||
Subtitle = app.Subtitle;
|
||||
Tags = [new Tag() { Text = "App" }];
|
||||
|
||||
Details = new Details()
|
||||
{
|
||||
Title = this.Title,
|
||||
HeroImage = Command?.Icon ?? new(string.Empty),
|
||||
Body = "### App",
|
||||
};
|
||||
|
||||
if (string.IsNullOrEmpty(app.UserModelId))
|
||||
{
|
||||
// Win32 exe or other non UWP app
|
||||
MoreCommands = [
|
||||
new CommandContextItem(
|
||||
new OpenPathAction(app.DirPath)
|
||||
{
|
||||
Name = "Open location",
|
||||
Icon = new("\ue838"),
|
||||
})
|
||||
];
|
||||
}
|
||||
else
|
||||
{
|
||||
// UWP app
|
||||
MoreCommands = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Microsoft.CmdPal.Ext.Apps</RootNamespace>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.IO.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Windows.CsWin32">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
<ProjectReference Include="..\..\extensionsdk\Microsoft.CmdPal.Extensions.Helpers\Microsoft.CmdPal.Extensions.Helpers.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,19 @@
|
||||
GetPhysicallyInstalledSystemMemory
|
||||
GlobalMemoryStatusEx
|
||||
GetSystemInfo
|
||||
CoCreateInstance
|
||||
SetForegroundWindow
|
||||
IsIconic
|
||||
RegisterHotKey
|
||||
SetWindowLongPtr
|
||||
CallWindowProc
|
||||
ShowWindow
|
||||
SetForegroundWindow
|
||||
SetFocus
|
||||
SetActiveWindow
|
||||
MonitorFromWindow
|
||||
GetMonitorInfo
|
||||
SHCreateStreamOnFileEx
|
||||
CoAllowSetForegroundWindow
|
||||
SHCreateStreamOnFileEx
|
||||
SHLoadIndirectString
|
||||
@@ -0,0 +1,30 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
// NOTE this is pretty close to what we'd put in the SDK
|
||||
internal sealed partial class OpenPathAction(string target) : InvokableCommand
|
||||
{
|
||||
private readonly string _target = target;
|
||||
|
||||
internal static async Task LaunchTarget(string t)
|
||||
{
|
||||
await Task.Run(() =>
|
||||
{
|
||||
Process.Start(new ProcessStartInfo(t) { UseShellExecute = true });
|
||||
});
|
||||
}
|
||||
|
||||
public override CommandResult Invoke()
|
||||
{
|
||||
LaunchTarget(_target).Start();
|
||||
|
||||
return CommandResult.GoHome();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
// Reference : https://stackoverflow.com/questions/32122679/getting-icon-of-modern-windows-app-from-a-desktop-application
|
||||
[Guid("5842a140-ff9f-4166-8f5c-62f5b7b0c781")]
|
||||
[ComImport]
|
||||
public class AppxFactory
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.Win32.System.Com;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
public static class AppxPackageHelper
|
||||
{
|
||||
private static readonly IAppxFactory AppxFactory = (IAppxFactory)new AppxFactory();
|
||||
|
||||
// This function returns a list of attributes of applications
|
||||
internal static IEnumerable<IAppxManifestApplication> GetAppsFromManifest(IStream stream)
|
||||
{
|
||||
var reader = AppxFactory.CreateManifestReader(stream);
|
||||
var manifestApps = reader.GetApplications();
|
||||
|
||||
while (manifestApps.GetHasCurrent())
|
||||
{
|
||||
var manifestApp = manifestApps.GetCurrent();
|
||||
var hr = manifestApp.GetStringValue("AppListEntry", out var appListEntry);
|
||||
_ = CheckHRAndReturnOrThrow(hr, appListEntry);
|
||||
if (appListEntry != "none")
|
||||
{
|
||||
yield return manifestApp;
|
||||
}
|
||||
|
||||
manifestApps.MoveNext();
|
||||
}
|
||||
}
|
||||
|
||||
public static T CheckHRAndReturnOrThrow<T>(int hr, T result)
|
||||
{
|
||||
// HRESULT.S_OK
|
||||
if (hr != 0)
|
||||
{
|
||||
Marshal.ThrowExceptionForHR((int)hr);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// 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.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
// Reference : https://github.com/MicrosoftEdge/edge-launcher/blob/108e63df0b4cb5cd9d5e45aa7a264690851ec51d/MIcrosoftEdgeLauncherCsharp/Program.cs
|
||||
[Flags]
|
||||
public enum ActivateOptions
|
||||
{
|
||||
None = 0x00000000,
|
||||
DesignMode = 0x00000001,
|
||||
NoErrorUI = 0x00000002,
|
||||
NoSplashScreen = 0x00000004,
|
||||
}
|
||||
|
||||
// ApplicationActivationManager
|
||||
[ComImport]
|
||||
[Guid("2e941141-7f97-4756-ba1d-9decde894a3d")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IApplicationActivationManager
|
||||
{
|
||||
IntPtr ActivateApplication([In] string appUserModelId, [In] string arguments, [In] ActivateOptions options, [Out] out uint processId);
|
||||
|
||||
IntPtr ActivateForFile([In] string appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] string verb, [Out] out uint processId);
|
||||
|
||||
IntPtr ActivateForProtocol([In] string appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out uint processId);
|
||||
}
|
||||
|
||||
// Application Activation Manager Class
|
||||
[ComImport]
|
||||
[Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")]
|
||||
public class ApplicationActivationManager : IApplicationActivationManager
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)/*, PreserveSig*/]
|
||||
public extern IntPtr ActivateApplication([In] string appUserModelId, [In] string arguments, [In] ActivateOptions options, [Out] out uint processId);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public extern IntPtr ActivateForFile([In] string appUserModelId, [In] IntPtr /*IShellItemArray* */ itemArray, [In] string verb, [Out] out uint processId);
|
||||
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
public extern IntPtr ActivateForProtocol([In] string appUserModelId, [In] IntPtr /* IShellItemArray* */itemArray, [Out] out uint processId);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
using Windows.Win32.System.Com;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
[Guid("BEB94909-E451-438B-B5A7-D79E767B75D8")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IAppxFactory
|
||||
{
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Implements COM Interface")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Implements COM Interface")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Implements COM Interface")]
|
||||
void _VtblGap0_2(); // skip 2 methods
|
||||
|
||||
internal IAppxManifestReader CreateManifestReader(IStream inputStream);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
[Guid("5DA89BF4-3773-46BE-B650-7E744863B7E8")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IAppxManifestApplication
|
||||
{
|
||||
[PreserveSig]
|
||||
int GetStringValue([MarshalAs(UnmanagedType.LPWStr)] string name, [MarshalAs(UnmanagedType.LPWStr)] out string value);
|
||||
|
||||
[PreserveSig]
|
||||
int GetAppUserModelId([MarshalAs(UnmanagedType.LPWStr)] out string value);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
[Guid("9EB8A55A-F04B-4D0D-808D-686185D4847A")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IAppxManifestApplicationsEnumerator
|
||||
{
|
||||
IAppxManifestApplication GetCurrent();
|
||||
|
||||
bool GetHasCurrent();
|
||||
|
||||
bool MoveNext();
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
[Guid("03FAF64D-F26F-4B2C-AAF7-8FE7789B8BCA")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IAppxManifestProperties
|
||||
{
|
||||
[PreserveSig]
|
||||
int GetBoolValue([MarshalAs(UnmanagedType.LPWStr)] string name, out bool value);
|
||||
|
||||
[PreserveSig]
|
||||
int GetStringValue([MarshalAs(UnmanagedType.LPWStr)] string name, [MarshalAs(UnmanagedType.LPWStr)] out string value);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
[Guid("4E1BD148-55A0-4480-A3D1-15544710637C")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IAppxManifestReader
|
||||
{
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Implements COM Interface")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Implements COM Interface")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Implements COM Interface")]
|
||||
void _VtblGap0_1(); // skip 1 method
|
||||
|
||||
IAppxManifestProperties GetProperties();
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:Element should begin with upper-case letter", Justification = "Implements COM Interface")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Implements COM Interface")]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Implements COM Interface")]
|
||||
void _VtblGap1_5(); // skip 5 methods
|
||||
|
||||
IAppxManifestApplicationsEnumerator GetApplications();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
public interface IPackage
|
||||
{
|
||||
string Name { get; }
|
||||
|
||||
string FullName { get; }
|
||||
|
||||
string FamilyName { get; }
|
||||
|
||||
bool IsFramework { get; }
|
||||
|
||||
bool IsDevelopmentMode { get; }
|
||||
|
||||
string InstalledLocation { get; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// 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.Collections.Generic;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
public interface IPackageManager
|
||||
{
|
||||
IEnumerable<IPackage> FindPackagesForCurrentUser();
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
public interface IProgram
|
||||
{
|
||||
// List<ContextMenuResult> ContextMenus(string queryArguments, IPublicAPI api);
|
||||
// Result Result(string query, string queryArguments, IPublicAPI api);
|
||||
string UniqueIdentifier { get; set; }
|
||||
|
||||
string Name { get; }
|
||||
|
||||
string Description { get; set; }
|
||||
|
||||
string Location { get; }
|
||||
|
||||
bool Enabled { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
public interface IShellLinkHelper
|
||||
{
|
||||
string RetrieveTargetPath(string path);
|
||||
|
||||
string Description { get; set; }
|
||||
|
||||
string Arguments { get; set; }
|
||||
|
||||
bool HasArguments { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
public enum LogoType
|
||||
{
|
||||
Error,
|
||||
Colored,
|
||||
HighContrast,
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
internal sealed class Native
|
||||
{
|
||||
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1838:Avoid 'StringBuilder' parameters for P/Invokes", Justification = "Part of API, can't remove")]
|
||||
public static extern int SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, int cchOutBuf, IntPtr ppvReserved);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Principal;
|
||||
using Windows.Management.Deployment;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
public class PackageManagerWrapper : IPackageManager
|
||||
{
|
||||
private readonly PackageManager _packageManager;
|
||||
|
||||
public PackageManagerWrapper()
|
||||
{
|
||||
_packageManager = new PackageManager();
|
||||
}
|
||||
|
||||
public IEnumerable<IPackage> FindPackagesForCurrentUser()
|
||||
{
|
||||
var user = WindowsIdentity.GetCurrent().User;
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
var pkgs = _packageManager.FindPackagesForUser(user.Value);
|
||||
|
||||
return pkgs.Select(PackageWrapper.GetWrapperFromPackage).Where(package => package != null);
|
||||
}
|
||||
|
||||
return Enumerable.Empty<IPackage>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
// 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.IO;
|
||||
using Windows.Foundation.Metadata;
|
||||
using Package = Windows.ApplicationModel.Package;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
public class PackageWrapper : IPackage
|
||||
{
|
||||
public string Name { get; } = string.Empty;
|
||||
|
||||
public string FullName { get; } = string.Empty;
|
||||
|
||||
public string FamilyName { get; } = string.Empty;
|
||||
|
||||
public bool IsFramework { get; }
|
||||
|
||||
public bool IsDevelopmentMode { get; }
|
||||
|
||||
public string InstalledLocation { get; } = string.Empty;
|
||||
|
||||
public PackageWrapper()
|
||||
{
|
||||
}
|
||||
|
||||
public PackageWrapper(string name, string fullName, string familyName, bool isFramework, bool isDevelopmentMode, string installedLocation)
|
||||
{
|
||||
Name = name;
|
||||
FullName = fullName;
|
||||
FamilyName = familyName;
|
||||
IsFramework = isFramework;
|
||||
IsDevelopmentMode = isDevelopmentMode;
|
||||
InstalledLocation = installedLocation;
|
||||
}
|
||||
|
||||
private static readonly Lazy<bool> IsPackageDotInstallationPathAvailable = new(() =>
|
||||
ApiInformation.IsPropertyPresent(typeof(Package).FullName, nameof(Package.InstalledLocation.Path)));
|
||||
|
||||
public static PackageWrapper GetWrapperFromPackage(Package package)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(package);
|
||||
|
||||
string path;
|
||||
try
|
||||
{
|
||||
path = IsPackageDotInstallationPathAvailable.Value ? GetInstalledPath(package) : package.InstalledLocation.Path;
|
||||
}
|
||||
catch (Exception e) when (e is ArgumentException || e is FileNotFoundException || e is DirectoryNotFoundException)
|
||||
{
|
||||
// ProgramLogger.Exception($"Exception {package.Id.Name}", e, MethodBase.GetCurrentMethod().DeclaringType, "Path could not be determined");
|
||||
return new PackageWrapper(
|
||||
package.Id.Name,
|
||||
package.Id.FullName,
|
||||
package.Id.FamilyName,
|
||||
package.IsFramework,
|
||||
package.IsDevelopmentMode,
|
||||
string.Empty);
|
||||
}
|
||||
|
||||
return new PackageWrapper(
|
||||
package.Id.Name,
|
||||
package.Id.FullName,
|
||||
package.Id.FamilyName,
|
||||
package.IsFramework,
|
||||
package.IsDevelopmentMode,
|
||||
path);
|
||||
}
|
||||
|
||||
// This is a separate method so the reference to .InstalledPath won't be loaded in API versions which do not support this API (e.g. older then Build 19041)
|
||||
private static string GetInstalledPath(Package package)
|
||||
=> package.InstalledLocation.Path;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
/// <summary>
|
||||
/// Contains user added folder location contents as well as all user disabled applications
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Win32 class applications set UniqueIdentifier using their full file path</para>
|
||||
/// <para>UWP class applications set UniqueIdentifier using their Application User Model ID</para>
|
||||
/// <para>Custom user added program sources set UniqueIdentifier using their location</para>
|
||||
/// </remarks>
|
||||
public class ProgramSource
|
||||
{
|
||||
public string Location { get; set; } = string.Empty;
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
public string UniqueIdentifier { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
// 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.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices.ComTypes;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
public class ShellLinkHelper : IShellLinkHelper
|
||||
{
|
||||
[Flags]
|
||||
private enum SLGP_FLAGS
|
||||
{
|
||||
SLGP_SHORTPATH = 0x1,
|
||||
SLGP_UNCPRIORITY = 0x2,
|
||||
SLGP_RAWPATH = 0x4,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Matching COM")]
|
||||
private struct WIN32_FIND_DATAW
|
||||
{
|
||||
public uint dwFileAttributes;
|
||||
public long ftCreationTime;
|
||||
public long ftLastAccessTime;
|
||||
public long ftLastWriteTime;
|
||||
public uint nFileSizeHigh;
|
||||
public uint nFileSizeLow;
|
||||
public uint dwReserved0;
|
||||
public uint dwReserved1;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
|
||||
public string cFileName;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
|
||||
public string cAlternateFileName;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Implements COM Interface")]
|
||||
public enum SLR_FLAGS
|
||||
{
|
||||
SLR_NO_UI = 0x1,
|
||||
SLR_ANY_MATCH = 0x2,
|
||||
SLR_UPDATE = 0x4,
|
||||
SLR_NOUPDATE = 0x8,
|
||||
SLR_NOSEARCH = 0x10,
|
||||
SLR_NOTRACK = 0x20,
|
||||
SLR_NOLINKINFO = 0x40,
|
||||
SLR_INVOKE_MSI = 0x80,
|
||||
}
|
||||
|
||||
// Reference : http://www.pinvoke.net/default.aspx/Interfaces.IShellLinkW
|
||||
|
||||
// The IShellLink interface allows Shell links to be created, modified, and resolved
|
||||
[ComImport]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[Guid("000214F9-0000-0000-C000-000000000046")]
|
||||
private interface IShellLinkW
|
||||
{
|
||||
/// <summary>Retrieves the path and file name of a Shell link object</summary>
|
||||
void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, ref WIN32_FIND_DATAW pfd, SLGP_FLAGS fFlags);
|
||||
|
||||
/// <summary>Retrieves the list of item identifiers for a Shell link object</summary>
|
||||
void GetIDList(out IntPtr ppidl);
|
||||
|
||||
/// <summary>Sets the pointer to an item identifier list (PIDL) for a Shell link object.</summary>
|
||||
void SetIDList(IntPtr pidl);
|
||||
|
||||
/// <summary>Retrieves the description string for a Shell link object</summary>
|
||||
void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
|
||||
|
||||
/// <summary>Sets the description for a Shell link object. The description can be any application-defined string</summary>
|
||||
void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
|
||||
|
||||
/// <summary>Retrieves the name of the working directory for a Shell link object</summary>
|
||||
void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
|
||||
|
||||
/// <summary>Sets the name of the working directory for a Shell link object</summary>
|
||||
void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
|
||||
|
||||
/// <summary>Retrieves the command-line arguments associated with a Shell link object</summary>
|
||||
void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);
|
||||
|
||||
/// <summary>Sets the command-line arguments for a Shell link object</summary>
|
||||
void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
|
||||
|
||||
/// <summary>Retrieves the hot key for a Shell link object</summary>
|
||||
void GetHotkey(out short pwHotkey);
|
||||
|
||||
/// <summary>Sets a hot key for a Shell link object</summary>
|
||||
void SetHotkey(short wHotkey);
|
||||
|
||||
/// <summary>Retrieves the show command for a Shell link object</summary>
|
||||
void GetShowCmd(out int piShowCmd);
|
||||
|
||||
/// <summary>Sets the show command for a Shell link object. The show command sets the initial show state of the window.</summary>
|
||||
void SetShowCmd(int iShowCmd);
|
||||
|
||||
/// <summary>Retrieves the location (path and index) of the icon for a Shell link object</summary>
|
||||
void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon);
|
||||
|
||||
/// <summary>Sets the location (path and index) of the icon for a Shell link object</summary>
|
||||
void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
|
||||
|
||||
/// <summary>Sets the relative path to the Shell link object</summary>
|
||||
void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
|
||||
|
||||
/// <summary>Attempts to find the target of a Shell link, even if it has been moved or renamed</summary>
|
||||
void Resolve(ref IntPtr hwnd, SLR_FLAGS fFlags);
|
||||
|
||||
/// <summary>Sets the path and file name of a Shell link object</summary>
|
||||
void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
|
||||
}
|
||||
|
||||
[ComImport]
|
||||
[Guid("00021401-0000-0000-C000-000000000046")]
|
||||
private class ShellLink
|
||||
{
|
||||
}
|
||||
|
||||
// Contains the description of the app
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
// Contains the arguments to the app
|
||||
public string Arguments { get; set; } = string.Empty;
|
||||
|
||||
public bool HasArguments { get; set; }
|
||||
|
||||
// Retrieve the target path using Shell Link
|
||||
public string RetrieveTargetPath(string path)
|
||||
{
|
||||
var link = new ShellLink();
|
||||
const int STGM_READ = 0;
|
||||
|
||||
try
|
||||
{
|
||||
((IPersistFile)link).Load(path, STGM_READ);
|
||||
}
|
||||
catch (System.IO.FileNotFoundException)
|
||||
{
|
||||
// Log.Exception("Path could not be retrieved", ex, GetType(), path);
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var hwnd = default(IntPtr);
|
||||
((IShellLinkW)link).Resolve(ref hwnd, 0);
|
||||
|
||||
const int MAX_PATH = 260;
|
||||
var buffer = new StringBuilder(MAX_PATH);
|
||||
|
||||
var data = default(WIN32_FIND_DATAW);
|
||||
((IShellLinkW)link).GetPath(buffer, buffer.Capacity, ref data, SLGP_FLAGS.SLGP_SHORTPATH);
|
||||
var target = buffer.ToString();
|
||||
|
||||
// To set the app description
|
||||
if (!string.IsNullOrEmpty(target))
|
||||
{
|
||||
buffer = new StringBuilder(MAX_PATH);
|
||||
try
|
||||
{
|
||||
((IShellLinkW)link).GetDescription(buffer, MAX_PATH);
|
||||
Description = buffer.ToString();
|
||||
}
|
||||
catch (System.Exception )
|
||||
{
|
||||
// Log.Exception($"Failed to fetch description for {target}, {e.Message}", e, GetType());
|
||||
Description = string.Empty;
|
||||
}
|
||||
|
||||
var argumentBuffer = new StringBuilder(MAX_PATH);
|
||||
((IShellLinkW)link).GetArguments(argumentBuffer, argumentBuffer.Capacity);
|
||||
Arguments = argumentBuffer.ToString();
|
||||
|
||||
// Set variable to true if the program takes in any arguments
|
||||
if (argumentBuffer.Length != 0)
|
||||
{
|
||||
HasArguments = true;
|
||||
}
|
||||
}
|
||||
|
||||
// To release unmanaged memory
|
||||
Marshal.ReleaseComObject(link);
|
||||
|
||||
return target;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.System.Com;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
[Serializable]
|
||||
public partial class UWP
|
||||
{
|
||||
private static readonly IPath Path = new FileSystem().Path;
|
||||
|
||||
private static readonly Dictionary<string, PackageVersion> _versionFromNamespace = new()
|
||||
{
|
||||
{ "http://schemas.microsoft.com/appx/manifest/foundation/windows10", PackageVersion.Windows10 },
|
||||
{ "http://schemas.microsoft.com/appx/2013/manifest", PackageVersion.Windows81 },
|
||||
{ "http://schemas.microsoft.com/appx/2010/manifest", PackageVersion.Windows8 },
|
||||
};
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string FullName { get; }
|
||||
|
||||
public string FamilyName { get; }
|
||||
|
||||
public string Location { get; set; } = string.Empty;
|
||||
|
||||
// Localized path based on windows display language
|
||||
public string LocationLocalized { get; set; } = string.Empty;
|
||||
|
||||
public IList<UWPApplication> Apps { get; private set; } = new List<UWPApplication>();
|
||||
|
||||
public PackageVersion Version { get; set; }
|
||||
|
||||
public static IPackageManager PackageManagerWrapper { get; set; } = new PackageManagerWrapper();
|
||||
|
||||
public UWP(IPackage package)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(package);
|
||||
|
||||
Name = package.Name;
|
||||
FullName = package.FullName;
|
||||
FamilyName = package.FamilyName;
|
||||
}
|
||||
|
||||
public void InitializeAppInfo(string installedLocation)
|
||||
{
|
||||
Location = installedLocation;
|
||||
LocationLocalized = installedLocation; // Main.ShellLocalizationHelper.GetLocalizedPath(installedLocation);
|
||||
var path = Path.Combine(installedLocation, "AppxManifest.xml");
|
||||
|
||||
var namespaces = XmlNamespaces(path);
|
||||
InitPackageVersion(namespaces);
|
||||
|
||||
const uint noAttribute = 0x80;
|
||||
|
||||
// const STGM exclusiveRead = STGM.READ;
|
||||
uint access = 0; // STGM.READ
|
||||
var hResult = PInvoke.SHCreateStreamOnFileEx(path, access, noAttribute, false, null, out IStream stream);
|
||||
|
||||
// S_OK
|
||||
if (hResult == 0)
|
||||
{
|
||||
Apps = AppxPackageHelper.GetAppsFromManifest(stream).Select(appInManifest => new UWPApplication(appInManifest, this)).Where(a =>
|
||||
{
|
||||
var valid =
|
||||
!string.IsNullOrEmpty(a.UserModelId) &&
|
||||
!string.IsNullOrEmpty(a.DisplayName) &&
|
||||
a.AppListEntry != "none";
|
||||
|
||||
return valid;
|
||||
}).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
// var e = Marshal.GetExceptionForHR((int)hResult);
|
||||
// ProgramLogger.Exception("Error caused while trying to get the details of the UWP program", e, GetType(), path);
|
||||
Apps = Array.Empty<UWPApplication>();
|
||||
}
|
||||
}
|
||||
|
||||
// http://www.hanselman.com/blog/GetNamespacesFromAnXMLDocumentWithXPathDocumentAndLINQToXML.aspx
|
||||
private static string[] XmlNamespaces(string path)
|
||||
{
|
||||
var z = XDocument.Load(path);
|
||||
if (z.Root != null)
|
||||
{
|
||||
var namespaces = z.Root.Attributes().
|
||||
Where(a => a.IsNamespaceDeclaration).
|
||||
GroupBy(
|
||||
a => a.Name.Namespace == XNamespace.None ? string.Empty : a.Name.LocalName,
|
||||
a => XNamespace.Get(a.Value)).Select(
|
||||
g => g.First().ToString()).ToArray();
|
||||
return namespaces;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Log.Error($"Error occurred while trying to get the XML from {path}", MethodBase.GetCurrentMethod().DeclaringType);
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitPackageVersion(string[] namespaces)
|
||||
{
|
||||
foreach (var n in _versionFromNamespace.Keys.Where(namespaces.Contains))
|
||||
{
|
||||
Version = _versionFromNamespace[n];
|
||||
return;
|
||||
}
|
||||
|
||||
// ProgramLogger.Exception($"|Trying to get the package version of the UWP program, but a unknown UWP appmanifest version {FullName} from location {Location} is returned.", new FormatException(), GetType(), Location);
|
||||
Version = PackageVersion.Unknown;
|
||||
}
|
||||
|
||||
public static UWPApplication[] All()
|
||||
{
|
||||
var windows10 = new Version(10, 0);
|
||||
var support = Environment.OSVersion.Version.Major >= windows10.Major;
|
||||
if (support)
|
||||
{
|
||||
var applications = CurrentUserPackages().AsParallel().SelectMany(p =>
|
||||
{
|
||||
UWP u;
|
||||
try
|
||||
{
|
||||
u = new UWP(p);
|
||||
u.InitializeAppInfo(p.InstalledLocation);
|
||||
}
|
||||
catch (Exception )
|
||||
{
|
||||
// ProgramLogger.Exception($"Unable to convert Package to UWP for {p.FullName}", e, MethodBase.GetCurrentMethod().DeclaringType, p.InstalledLocation);
|
||||
return Array.Empty<UWPApplication>();
|
||||
}
|
||||
|
||||
return u.Apps;
|
||||
});
|
||||
|
||||
var updatedListWithoutDisabledApps = applications.Select(x => x);
|
||||
|
||||
return updatedListWithoutDisabledApps.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
return Array.Empty<UWPApplication>();
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<IPackage> CurrentUserPackages()
|
||||
{
|
||||
return PackageManagerWrapper.FindPackagesForCurrentUser().Where(p =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var f = p.IsFramework;
|
||||
var path = p.InstalledLocation;
|
||||
return !f && !string.IsNullOrEmpty(path);
|
||||
}
|
||||
catch (Exception )
|
||||
{
|
||||
// ProgramLogger.Exception("An unexpected error occurred and unable to verify if package is valid", e, MethodBase.GetCurrentMethod().DeclaringType, "id");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return FamilyName;
|
||||
}
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1309:Use ordinal string comparison", Justification = "Using CurrentCultureIgnoreCase since this is used with FamilyName")]
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj is UWP uwp)
|
||||
{
|
||||
// Using CurrentCultureIgnoreCase since this is used with FamilyName
|
||||
return FamilyName.Equals(uwp.FamilyName, StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
// Using CurrentCultureIgnoreCase since this is used with FamilyName
|
||||
return FamilyName.GetHashCode(StringComparison.CurrentCultureIgnoreCase);
|
||||
}
|
||||
|
||||
public enum PackageVersion
|
||||
{
|
||||
Windows10,
|
||||
Windows81,
|
||||
Windows8,
|
||||
Unknown,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,471 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
|
||||
using PackageVersion = Microsoft.CmdPal.Ext.Apps.Programs.UWP.PackageVersion;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
[Serializable]
|
||||
public class UWPApplication : IProgram
|
||||
{
|
||||
private static readonly FileSystem FileSystem = new();
|
||||
private static readonly IPath Path = FileSystem.Path;
|
||||
private static readonly IFile File = FileSystem.File;
|
||||
|
||||
public string AppListEntry { get; set; } = string.Empty;
|
||||
|
||||
public string UniqueIdentifier { get; set; } = string.Empty;
|
||||
|
||||
public string DisplayName { get; set; } = string.Empty;
|
||||
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
public string UserModelId { get; set; } = string.Empty;
|
||||
|
||||
public string BackgroundColor { get; set; } = string.Empty;
|
||||
|
||||
public string EntryPoint { get; set; } = string.Empty;
|
||||
|
||||
public string Name => DisplayName;
|
||||
|
||||
public string Location => Package.Location;
|
||||
|
||||
// Localized path based on windows display language
|
||||
public string LocationLocalized => Package.LocationLocalized;
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public bool CanRunElevated { get; set; }
|
||||
|
||||
public string LogoPath { get; set; } = string.Empty;
|
||||
|
||||
public LogoType LogoType { get; set; }
|
||||
|
||||
public UWP Package { get; set; }
|
||||
|
||||
private readonly string logoUri;
|
||||
|
||||
private const string ContrastWhite = "contrast-white";
|
||||
|
||||
private const string ContrastBlack = "contrast-black";
|
||||
|
||||
// Function to set the subtitle based on the Type of application
|
||||
private static string SetSubtitle()
|
||||
{
|
||||
return string.Empty; // Properties.Resources.powertoys_run_plugin_program_packaged_application;
|
||||
}
|
||||
|
||||
public UWPApplication(IAppxManifestApplication manifestApp, UWP package)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(manifestApp);
|
||||
|
||||
var hr = manifestApp.GetAppUserModelId(out var tmpUserModelId);
|
||||
UserModelId = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpUserModelId);
|
||||
|
||||
hr = manifestApp.GetAppUserModelId(out var tmpUniqueIdentifier);
|
||||
UniqueIdentifier = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpUniqueIdentifier);
|
||||
|
||||
hr = manifestApp.GetStringValue("DisplayName", out var tmpDisplayName);
|
||||
DisplayName = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpDisplayName);
|
||||
|
||||
hr = manifestApp.GetStringValue("Description", out var tmpDescription);
|
||||
Description = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpDescription);
|
||||
|
||||
hr = manifestApp.GetStringValue("BackgroundColor", out var tmpBackgroundColor);
|
||||
BackgroundColor = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpBackgroundColor);
|
||||
|
||||
hr = manifestApp.GetStringValue("EntryPoint", out var tmpEntryPoint);
|
||||
EntryPoint = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, tmpEntryPoint);
|
||||
|
||||
Package = package ?? throw new ArgumentNullException(nameof(package));
|
||||
|
||||
DisplayName = ResourceFromPri(package.FullName, DisplayName);
|
||||
Description = ResourceFromPri(package.FullName, Description);
|
||||
logoUri = LogoUriFromManifest(manifestApp);
|
||||
UpdateLogoPath();
|
||||
|
||||
// logoUri = "";
|
||||
Enabled = true;
|
||||
CanRunElevated = IfApplicationCanRunElevated();
|
||||
}
|
||||
|
||||
private bool IfApplicationCanRunElevated()
|
||||
{
|
||||
if (EntryPoint == "Windows.FullTrustApplication")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var manifest = Package.Location + "\\AppxManifest.xml";
|
||||
if (File.Exists(manifest))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check the manifest to verify if the Trust Level for the application is "mediumIL"
|
||||
var file = File.ReadAllText(manifest);
|
||||
var xmlDoc = new XmlDocument();
|
||||
xmlDoc.LoadXml(file);
|
||||
var xmlRoot = xmlDoc.DocumentElement;
|
||||
if (xmlRoot != null)
|
||||
{
|
||||
var namespaceManager = new XmlNamespaceManager(xmlDoc.NameTable);
|
||||
namespaceManager.AddNamespace("uap10", "http://schemas.microsoft.com/appx/manifest/uap/windows10/10");
|
||||
|
||||
// According to
|
||||
// https://learn.microsoft.com/windows/apps/desktop/modernize/grant-identity-to-nonpackaged-apps#create-a-package-manifest-for-the-sparse-package
|
||||
// and https://learn.microsoft.com/uwp/schemas/appxpackage/uapmanifestschema/element-application#attributes
|
||||
var trustLevelNode = xmlRoot.SelectSingleNode("//*[local-name()='Application' and @uap10:TrustLevel]", namespaceManager);
|
||||
|
||||
if (trustLevelNode?.Attributes?["uap10:TrustLevel"]?.Value == "mediumIL")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ProgramLogger.Exception($"Unable to parse manifest file for {DisplayName}", e, MethodBase.GetCurrentMethod().DeclaringType, manifest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal string ResourceFromPri(string packageFullName, string resourceReference)
|
||||
{
|
||||
const string prefix = "ms-resource:";
|
||||
|
||||
// Using OrdinalIgnoreCase since this is used internally
|
||||
if (!string.IsNullOrWhiteSpace(resourceReference) && resourceReference.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// magic comes from @talynone
|
||||
// https://github.com/talynone/Wox.Plugin.WindowsUniversalAppLauncher/blob/master/StoreAppLauncher/Helpers/NativeApiHelper.cs#L139-L153
|
||||
var key = resourceReference.Substring(prefix.Length);
|
||||
string parsed;
|
||||
var parsedFallback = string.Empty;
|
||||
|
||||
// Using Ordinal/OrdinalIgnoreCase since these are used internally
|
||||
if (key.StartsWith("//", StringComparison.Ordinal))
|
||||
{
|
||||
parsed = prefix + key;
|
||||
}
|
||||
else if (key.StartsWith('/'))
|
||||
{
|
||||
parsed = prefix + "//" + key;
|
||||
}
|
||||
else if (key.Contains("resources", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
parsed = prefix + key;
|
||||
}
|
||||
else
|
||||
{
|
||||
parsed = prefix + "///resources/" + key;
|
||||
|
||||
// e.g. for Windows Terminal version >= 1.12 DisplayName and Description resources are not in the 'resources' subtree
|
||||
parsedFallback = prefix + "///" + key;
|
||||
}
|
||||
|
||||
var outBuffer = new StringBuilder(128);
|
||||
|
||||
var source = $"@{{{packageFullName}? {parsed}}}";
|
||||
var hResult = Native.SHLoadIndirectString(source, outBuffer, outBuffer.Capacity, IntPtr.Zero);
|
||||
if (hResult != 0)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(parsedFallback))
|
||||
{
|
||||
var sourceFallback = $"@{{{packageFullName}? {parsedFallback}}}";
|
||||
hResult = Native.SHLoadIndirectString(sourceFallback, outBuffer, outBuffer.Capacity, IntPtr.Zero);
|
||||
|
||||
// HRESULT.S_OK
|
||||
if (hResult == 0)
|
||||
{
|
||||
var loaded = outBuffer.ToString();
|
||||
if (!string.IsNullOrEmpty(loaded))
|
||||
{
|
||||
return loaded;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ProgramLogger.Exception($"Can't load null or empty result pri {sourceFallback} in uwp location {Package.Location}", new ArgumentNullException(null), GetType(), Package.Location);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/Wox-launcher/Wox/issues/964
|
||||
// known hresult 2147942522:
|
||||
// 'Microsoft Corporation' violates pattern constraint of '\bms-resource:.{1,256}'.
|
||||
// for
|
||||
// Microsoft.MicrosoftOfficeHub_17.7608.23501.0_x64__8wekyb3d8bbwe: ms-resource://Microsoft.MicrosoftOfficeHub/officehubintl/AppManifest_GetOffice_Description
|
||||
// Microsoft.BingFoodAndDrink_3.0.4.336_x64__8wekyb3d8bbwe: ms-resource:AppDescription
|
||||
|
||||
// var e = Marshal.GetExceptionForHR((int)hResult);
|
||||
// ProgramLogger.Exception($"Load pri failed {source} with HResult {hResult} and location {Package.Location}", e, GetType(), Package.Location);
|
||||
return string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
var loaded = outBuffer.ToString();
|
||||
if (!string.IsNullOrEmpty(loaded))
|
||||
{
|
||||
return loaded;
|
||||
}
|
||||
else
|
||||
{
|
||||
// ProgramLogger.Exception($"Can't load null or empty result pri {source} in uwp location {Package.Location}", new ArgumentNullException(null), GetType(), Package.Location);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return resourceReference;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Dictionary<PackageVersion, string> _logoKeyFromVersion = new()
|
||||
{
|
||||
{ PackageVersion.Windows10, "Square44x44Logo" },
|
||||
{ PackageVersion.Windows81, "Square30x30Logo" },
|
||||
{ PackageVersion.Windows8, "SmallLogo" },
|
||||
};
|
||||
|
||||
internal string LogoUriFromManifest(IAppxManifestApplication app)
|
||||
{
|
||||
if (_logoKeyFromVersion.TryGetValue(Package.Version, out var key))
|
||||
{
|
||||
var hr = app.GetStringValue(key, out var logoUriFromApp);
|
||||
_ = AppxPackageHelper.CheckHRAndReturnOrThrow(hr, logoUriFromApp);
|
||||
return logoUriFromApp;
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateLogoPath(/*Theme theme*/)
|
||||
{
|
||||
LogoPathFromUri(logoUri/*, theme*/);
|
||||
}
|
||||
|
||||
// scale factors on win10: https://learn.microsoft.com/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets#asset-size-tables,
|
||||
private static readonly Dictionary<PackageVersion, List<int>> _scaleFactors = new()
|
||||
{
|
||||
{ PackageVersion.Windows10, new List<int> { 100, 125, 150, 200, 400 } },
|
||||
{ PackageVersion.Windows81, new List<int> { 100, 120, 140, 160, 180 } },
|
||||
{ PackageVersion.Windows8, new List<int> { 100 } },
|
||||
};
|
||||
|
||||
private bool SetScaleIcons(string path, string colorScheme, bool highContrast = false)
|
||||
{
|
||||
var extension = Path.GetExtension(path);
|
||||
if (extension != null)
|
||||
{
|
||||
var end = path.Length - extension.Length;
|
||||
var prefix = path.Substring(0, end);
|
||||
var paths = new List<string> { };
|
||||
|
||||
if (!highContrast)
|
||||
{
|
||||
paths.Add(path);
|
||||
}
|
||||
|
||||
if (_scaleFactors.TryGetValue(Package.Version, out var factors))
|
||||
{
|
||||
foreach (var factor in factors)
|
||||
{
|
||||
if (highContrast)
|
||||
{
|
||||
paths.Add($"{prefix}.scale-{factor}_{colorScheme}{extension}");
|
||||
paths.Add($"{prefix}.{colorScheme}_scale-{factor}{extension}");
|
||||
}
|
||||
else
|
||||
{
|
||||
paths.Add($"{prefix}.scale-{factor}{extension}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var selectedIconPath = paths.FirstOrDefault(File.Exists);
|
||||
if (!string.IsNullOrEmpty(selectedIconPath))
|
||||
{
|
||||
LogoPath = selectedIconPath;
|
||||
if (highContrast)
|
||||
{
|
||||
LogoType = LogoType.HighContrast;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogoType = LogoType.Colored;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool SetTargetSizeIcon(string path, string colorScheme, bool highContrast = false)
|
||||
{
|
||||
var extension = Path.GetExtension(path);
|
||||
if (extension != null)
|
||||
{
|
||||
var end = path.Length - extension.Length;
|
||||
var prefix = path.Substring(0, end);
|
||||
var paths = new List<string> { };
|
||||
const int appIconSize = 36;
|
||||
var targetSizes = new List<int> { 16, 24, 30, 36, 44, 60, 72, 96, 128, 180, 256 }.AsParallel();
|
||||
var pathFactorPairs = new Dictionary<string, int>();
|
||||
|
||||
foreach (var factor in targetSizes)
|
||||
{
|
||||
if (highContrast)
|
||||
{
|
||||
var suffixThemePath = $"{prefix}.targetsize-{factor}_{colorScheme}{extension}";
|
||||
var prefixThemePath = $"{prefix}.{colorScheme}_targetsize-{factor}{extension}";
|
||||
|
||||
paths.Add(suffixThemePath);
|
||||
paths.Add(prefixThemePath);
|
||||
pathFactorPairs.Add(suffixThemePath, factor);
|
||||
pathFactorPairs.Add(prefixThemePath, factor);
|
||||
}
|
||||
else
|
||||
{
|
||||
var simplePath = $"{prefix}.targetsize-{factor}{extension}";
|
||||
var altformUnPlatedPath = $"{prefix}.targetsize-{factor}_altform-unplated{extension}";
|
||||
|
||||
paths.Add(simplePath);
|
||||
paths.Add(altformUnPlatedPath);
|
||||
pathFactorPairs.Add(simplePath, factor);
|
||||
pathFactorPairs.Add(altformUnPlatedPath, factor);
|
||||
}
|
||||
}
|
||||
|
||||
var selectedIconPath = paths.OrderBy(x => Math.Abs(pathFactorPairs.GetValueOrDefault(x) - appIconSize)).FirstOrDefault(File.Exists);
|
||||
if (!string.IsNullOrEmpty(selectedIconPath))
|
||||
{
|
||||
LogoPath = selectedIconPath;
|
||||
if (highContrast)
|
||||
{
|
||||
LogoType = LogoType.HighContrast;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogoType = LogoType.Colored;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool SetColoredIcon(string path, string colorScheme)
|
||||
{
|
||||
var isSetColoredScaleIcon = SetScaleIcons(path, colorScheme);
|
||||
if (isSetColoredScaleIcon)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var isSetColoredTargetIcon = SetTargetSizeIcon(path, colorScheme);
|
||||
if (isSetColoredTargetIcon)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var isSetHighContrastScaleIcon = SetScaleIcons(path, colorScheme, true);
|
||||
if (isSetHighContrastScaleIcon)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var isSetHighContrastTargetIcon = SetTargetSizeIcon(path, colorScheme, true);
|
||||
if (isSetHighContrastTargetIcon)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool SetHighContrastIcon(string path, string colorScheme)
|
||||
{
|
||||
var isSetHighContrastScaleIcon = SetScaleIcons(path, colorScheme, true);
|
||||
if (isSetHighContrastScaleIcon)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var isSetHighContrastTargetIcon = SetTargetSizeIcon(path, colorScheme, true);
|
||||
if (isSetHighContrastTargetIcon)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var isSetColoredScaleIcon = SetScaleIcons(path, colorScheme);
|
||||
if (isSetColoredScaleIcon)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var isSetColoredTargetIcon = SetTargetSizeIcon(path, colorScheme);
|
||||
if (isSetColoredTargetIcon)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal void LogoPathFromUri(string uri/*, Theme theme*/)
|
||||
{
|
||||
// all https://learn.microsoft.com/windows/uwp/controls-and-patterns/tiles-and-notifications-app-assets
|
||||
// windows 10 https://msdn.microsoft.com/library/windows/apps/dn934817.aspx
|
||||
// windows 8.1 https://msdn.microsoft.com/library/windows/apps/hh965372.aspx#target_size
|
||||
// windows 8 https://msdn.microsoft.com/library/windows/apps/br211475.aspx
|
||||
string path;
|
||||
bool isLogoUriSet;
|
||||
|
||||
// Using Ordinal since this is used internally with uri
|
||||
if (uri.Contains('\\', StringComparison.Ordinal))
|
||||
{
|
||||
path = Path.Combine(Package.Location, uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
// for C:\Windows\MiracastView etc
|
||||
path = Path.Combine(Package.Location, "Assets", uri);
|
||||
}
|
||||
|
||||
isLogoUriSet = SetColoredIcon(path, ContrastBlack) || SetColoredIcon(path, ContrastWhite);
|
||||
|
||||
if (!isLogoUriSet)
|
||||
{
|
||||
LogoPath = string.Empty;
|
||||
LogoType = LogoType.Error;
|
||||
|
||||
// ProgramLogger.Exception($"|{UserModelId} can't find logo uri for {uri} in package location: {Package.Location}", new FileNotFoundException(), GetType(), Package.Location);
|
||||
}
|
||||
}
|
||||
|
||||
private const int _dpiScale100 = 96;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{DisplayName}: {Description}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,755 @@
|
||||
// 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.IO;
|
||||
using System.Linq;
|
||||
using System.Security;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Apps.Programs;
|
||||
|
||||
[Serializable]
|
||||
public class Win32Program // : IProgram
|
||||
{
|
||||
public static readonly Win32Program InvalidProgram = new()
|
||||
{
|
||||
Valid = false,
|
||||
Enabled = false,
|
||||
};
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
// Localized name based on windows display language
|
||||
public string NameLocalized { get; set; } = string.Empty;
|
||||
|
||||
public string UniqueIdentifier { get; set; } = string.Empty;
|
||||
|
||||
public string IcoPath { get; set; } = string.Empty;
|
||||
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
// Path of app executable or lnk target executable
|
||||
public string FullPath { get; set; } = string.Empty;
|
||||
|
||||
// Localized path based on windows display language
|
||||
public string FullPathLocalized { get; set; } = string.Empty;
|
||||
|
||||
public string ParentDirectory { get; set; } = string.Empty;
|
||||
|
||||
public string ExecutableName { get; set; } = string.Empty;
|
||||
|
||||
// Localized executable name based on windows display language
|
||||
public string ExecutableNameLocalized { get; set; } = string.Empty;
|
||||
|
||||
// Path to the lnk file on LnkProgram
|
||||
public string LnkFilePath { get; set; } = string.Empty;
|
||||
|
||||
public string LnkResolvedExecutableName { get; set; } = string.Empty;
|
||||
|
||||
// Localized path based on windows display language
|
||||
public string LnkResolvedExecutableNameLocalized { get; set; } = string.Empty;
|
||||
|
||||
public bool Valid { get; set; }
|
||||
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public bool HasArguments => !string.IsNullOrEmpty(Arguments);
|
||||
|
||||
public string Arguments { get; set; } = string.Empty;
|
||||
|
||||
public string Location => ParentDirectory;
|
||||
|
||||
public ApplicationType AppType { get; set; }
|
||||
|
||||
public static IShellLinkHelper ShellLinkHelper { get; set; } = new ShellLinkHelper();
|
||||
|
||||
private const string ShortcutExtension = "lnk";
|
||||
private const string ApplicationReferenceExtension = "appref-ms";
|
||||
private const string InternetShortcutExtension = "url";
|
||||
private static readonly HashSet<string> ExecutableApplicationExtensions = new(StringComparer.OrdinalIgnoreCase) { "exe", "bat", "bin", "com", "cpl", "msc", "msi", "cmd", "ps1", "job", "msp", "mst", "sct", "ws", "wsh", "wsf" };
|
||||
|
||||
private const string ProxyWebApp = "_proxy.exe";
|
||||
private const string AppIdArgument = "--app-id";
|
||||
|
||||
public enum ApplicationType
|
||||
{
|
||||
WebApplication = 0,
|
||||
InternetShortcutApplication = 1,
|
||||
Win32Application = 2,
|
||||
ShortcutApplication = 3,
|
||||
ApprefApplication = 4,
|
||||
RunCommand = 5,
|
||||
Folder = 6,
|
||||
GenericFile = 7,
|
||||
}
|
||||
|
||||
// Function to calculate the score of a result
|
||||
private int Score(string query)
|
||||
{
|
||||
return query.Length + Name.Length;
|
||||
}
|
||||
|
||||
public bool IsWebApplication()
|
||||
{
|
||||
// To Filter PWAs when the user searches for the main application
|
||||
// All Chromium based applications contain the --app-id argument
|
||||
// Reference : https://codereview.chromium.org/399045
|
||||
// Using Ordinal IgnoreCase since this is used internally
|
||||
return !string.IsNullOrEmpty(FullPath) &&
|
||||
!string.IsNullOrEmpty(Arguments) &&
|
||||
FullPath.Contains(ProxyWebApp, StringComparison.OrdinalIgnoreCase) &&
|
||||
Arguments.Contains(AppIdArgument, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
// Condition to Filter pinned Web Applications or PWAs when searching for the main application
|
||||
public bool FilterWebApplication(string query)
|
||||
{
|
||||
// If the app is not a web application, then do not filter it
|
||||
if (!IsWebApplication())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var subqueries = query?.Split() ?? Array.Empty<string>();
|
||||
var nameContainsQuery = false;
|
||||
var pathContainsQuery = false;
|
||||
|
||||
// check if any space separated query is a part of the app name or path name
|
||||
foreach (var subquery in subqueries)
|
||||
{
|
||||
// Using OrdinalIgnoreCase since these are used internally
|
||||
if (FullPath.Contains(subquery, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
pathContainsQuery = true;
|
||||
}
|
||||
|
||||
if (Name.Contains(subquery, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
nameContainsQuery = true;
|
||||
}
|
||||
}
|
||||
|
||||
return pathContainsQuery && !nameContainsQuery;
|
||||
}
|
||||
|
||||
// Function to set the subtitle based on the Type of application
|
||||
private string GetSubtitle()
|
||||
{
|
||||
switch (AppType)
|
||||
{
|
||||
default:
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public bool QueryEqualsNameForRunCommands(string query)
|
||||
{
|
||||
if (query != null && AppType == ApplicationType.RunCommand)
|
||||
{
|
||||
// Using OrdinalIgnoreCase since this is used internally
|
||||
if (!query.Equals(Name, StringComparison.OrdinalIgnoreCase) && !query.Equals(ExecutableName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private ProcessStartInfo GetProcessStartInfo(string programArguments, RunAsType runAs = RunAsType.None)
|
||||
{
|
||||
return new ProcessStartInfo
|
||||
{
|
||||
FileName = LnkFilePath ?? FullPath,
|
||||
WorkingDirectory = ParentDirectory,
|
||||
UseShellExecute = true,
|
||||
Arguments = programArguments,
|
||||
Verb = runAs == RunAsType.Administrator ? "runAs" : runAs == RunAsType.OtherUser ? "runAsUser" : string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
private enum RunAsType
|
||||
{
|
||||
None,
|
||||
Administrator,
|
||||
OtherUser,
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ExecutableName;
|
||||
}
|
||||
|
||||
private static Win32Program CreateWin32Program(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var parDir = Directory.GetParent(path)?.FullName ?? string.Empty;
|
||||
return new Win32Program
|
||||
{
|
||||
Name = Path.GetFileNameWithoutExtension(path),
|
||||
ExecutableName = Path.GetFileName(path),
|
||||
IcoPath = path,
|
||||
|
||||
// Using InvariantCulture since this is user facing
|
||||
FullPath = path,
|
||||
UniqueIdentifier = path,
|
||||
ParentDirectory = parDir,
|
||||
Description = string.Empty,
|
||||
Valid = true,
|
||||
Enabled = true,
|
||||
AppType = ApplicationType.Win32Application,
|
||||
|
||||
// Localized name, path and executable based on windows display language
|
||||
NameLocalized = Path.GetFileNameWithoutExtension(path), // Main.ShellLocalizationHelper.GetLocalizedName(path),
|
||||
FullPathLocalized = path, // Main.ShellLocalizationHelper.GetLocalizedPath(path),
|
||||
ExecutableNameLocalized = Path.GetFileName(path), // Path.GetFileName(Main.ShellLocalizationHelper.GetLocalizedPath(path)),
|
||||
};
|
||||
}
|
||||
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
|
||||
{
|
||||
// ProgramLogger.Warn($"|Permission denied when trying to load the program from {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
return InvalidProgram;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ProgramLogger.Exception($"|An unexpected error occurred in the calling method CreateWin32Program at {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
return InvalidProgram;
|
||||
}
|
||||
}
|
||||
|
||||
// This function filters Internet Shortcut programs
|
||||
private static Win32Program InternetShortcutProgram(string path)
|
||||
{
|
||||
return InvalidProgram;
|
||||
}
|
||||
|
||||
private static Win32Program LnkProgram(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var program = CreateWin32Program(path);
|
||||
var target = ShellLinkHelper.RetrieveTargetPath(path);
|
||||
|
||||
if (!string.IsNullOrEmpty(target))
|
||||
{
|
||||
if (!(File.Exists(target) || Directory.Exists(target)))
|
||||
{
|
||||
// If the link points nowhere, consider it invalid.
|
||||
return InvalidProgram;
|
||||
}
|
||||
|
||||
program.LnkFilePath = program.FullPath;
|
||||
program.LnkResolvedExecutableName = Path.GetFileName(target);
|
||||
program.LnkResolvedExecutableNameLocalized = Path.GetFileName(target); // Path.GetFileName(Main.ShellLocalizationHelper.GetLocalizedPath(target));
|
||||
|
||||
// Using CurrentCulture since this is user facing
|
||||
program.FullPath = Path.GetFullPath(target);
|
||||
program.FullPathLocalized = Path.GetFullPath(target); // Main.ShellLocalizationHelper.GetLocalizedPath(target);
|
||||
|
||||
program.Arguments = ShellLinkHelper.Arguments;
|
||||
|
||||
// A .lnk could be a (Chrome) PWA, set correct AppType
|
||||
program.AppType = program.IsWebApplication()
|
||||
? ApplicationType.WebApplication
|
||||
: GetAppTypeFromPath(target);
|
||||
|
||||
var description = ShellLinkHelper.Description;
|
||||
if (!string.IsNullOrEmpty(description))
|
||||
{
|
||||
program.Description = description;
|
||||
}
|
||||
else
|
||||
{
|
||||
var info = FileVersionInfo.GetVersionInfo(path);
|
||||
|
||||
if (!string.IsNullOrEmpty(info?.FileDescription))
|
||||
{
|
||||
program.Description = info.FileDescription;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return program;
|
||||
}
|
||||
catch (System.IO.FileLoadException)
|
||||
{
|
||||
// ProgramLogger.Warn($"Couldn't load the link file at {path}. This might be caused by a new link being created and locked by the OS.", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
return InvalidProgram;
|
||||
}
|
||||
|
||||
// Only do a catch all in production. This is so make developer aware of any unhandled exception and add the exception handling in.
|
||||
// Error caused likely due to trying to get the description of the program
|
||||
catch (Exception)
|
||||
{
|
||||
// ProgramLogger.Exception($"|An unexpected error occurred in the calling method LnkProgram at {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
return InvalidProgram;
|
||||
}
|
||||
}
|
||||
|
||||
private static Win32Program ExeProgram(string path)
|
||||
{
|
||||
try
|
||||
{
|
||||
var program = CreateWin32Program(path);
|
||||
var info = FileVersionInfo.GetVersionInfo(path);
|
||||
if (!string.IsNullOrEmpty(info?.FileDescription))
|
||||
{
|
||||
program.Description = info.FileDescription;
|
||||
}
|
||||
|
||||
return program;
|
||||
}
|
||||
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
|
||||
{
|
||||
// ProgramLogger.Warn($"|Permission denied when trying to load the program from {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
return InvalidProgram;
|
||||
}
|
||||
catch (FileNotFoundException)
|
||||
{
|
||||
// ProgramLogger.Warn($"|Unable to locate exe file at {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
return InvalidProgram;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ProgramLogger.Exception($"|An unexpected error occurred in the calling method ExeProgram at {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
return InvalidProgram;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to get the application type, given the path to the application
|
||||
public static ApplicationType GetAppTypeFromPath(string path)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(path);
|
||||
|
||||
var extension = Extension(path);
|
||||
|
||||
// Using OrdinalIgnoreCase since these are used internally with paths
|
||||
if (ExecutableApplicationExtensions.Contains(extension))
|
||||
{
|
||||
return ApplicationType.Win32Application;
|
||||
}
|
||||
else if (extension.Equals(ShortcutExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ApplicationType.ShortcutApplication;
|
||||
}
|
||||
else if (extension.Equals(ApplicationReferenceExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ApplicationType.ApprefApplication;
|
||||
}
|
||||
else if (extension.Equals(InternetShortcutExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return ApplicationType.InternetShortcutApplication;
|
||||
}
|
||||
else if (string.IsNullOrEmpty(extension))
|
||||
{
|
||||
return ApplicationType.Folder;
|
||||
}
|
||||
|
||||
return ApplicationType.GenericFile;
|
||||
}
|
||||
|
||||
// Function to get the Win32 application, given the path to the application
|
||||
public static Win32Program? GetAppFromPath(string path)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(path);
|
||||
|
||||
Win32Program? app;
|
||||
switch (GetAppTypeFromPath(path))
|
||||
{
|
||||
case ApplicationType.Win32Application:
|
||||
app = ExeProgram(path);
|
||||
break;
|
||||
case ApplicationType.ShortcutApplication:
|
||||
app = LnkProgram(path);
|
||||
break;
|
||||
case ApplicationType.ApprefApplication:
|
||||
app = CreateWin32Program(path);
|
||||
app.AppType = ApplicationType.ApprefApplication;
|
||||
break;
|
||||
case ApplicationType.InternetShortcutApplication:
|
||||
app = InternetShortcutProgram(path);
|
||||
break;
|
||||
case ApplicationType.WebApplication:
|
||||
case ApplicationType.RunCommand:
|
||||
case ApplicationType.Folder:
|
||||
case ApplicationType.GenericFile:
|
||||
default:
|
||||
app = null;
|
||||
break;
|
||||
}
|
||||
|
||||
// if the app is valid, only then return the application, else return null
|
||||
return app?.Valid == true
|
||||
? app
|
||||
: null;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> ProgramPaths(string directory, IList<string> suffixes, bool recursiveSearch = true)
|
||||
{
|
||||
if (!Directory.Exists(directory))
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
var files = new List<string>();
|
||||
var folderQueue = new Queue<string>();
|
||||
folderQueue.Enqueue(directory);
|
||||
|
||||
// Keep track of already visited directories to avoid cycles.
|
||||
var alreadyVisited = new HashSet<string>();
|
||||
|
||||
do
|
||||
{
|
||||
var currentDirectory = folderQueue.Dequeue();
|
||||
|
||||
if (alreadyVisited.Contains(currentDirectory))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
alreadyVisited.Add(currentDirectory);
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var suffix in suffixes)
|
||||
{
|
||||
try
|
||||
{
|
||||
files.AddRange(Directory.EnumerateFiles(currentDirectory, $"*.{suffix}", SearchOption.TopDirectoryOnly));
|
||||
}
|
||||
catch (DirectoryNotFoundException )
|
||||
{
|
||||
// ProgramLogger.Warn("|The directory trying to load the program from does not exist", e, MethodBase.GetCurrentMethod().DeclaringType, currentDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
|
||||
{
|
||||
// ProgramLogger.Warn($"|Permission denied when trying to load programs from {currentDirectory}", e, MethodBase.GetCurrentMethod().DeclaringType, currentDirectory);
|
||||
}
|
||||
catch (Exception )
|
||||
{
|
||||
// ProgramLogger.Exception($"|An unexpected error occurred in the calling method ProgramPaths at {currentDirectory}", e, MethodBase.GetCurrentMethod().DeclaringType, currentDirectory);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// If the search is set to be non-recursive, then do not enqueue the child directories.
|
||||
if (!recursiveSearch)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var childDirectory in Directory.EnumerateDirectories(currentDirectory, "*", new EnumerationOptions()
|
||||
{
|
||||
// https://learn.microsoft.com/dotnet/api/system.io.enumerationoptions?view=net-6.0
|
||||
// Exclude directories with the Reparse Point file attribute, to avoid loops due to symbolic links / directory junction / mount points.
|
||||
AttributesToSkip = FileAttributes.Hidden | FileAttributes.System | FileAttributes.ReparsePoint,
|
||||
RecurseSubdirectories = false,
|
||||
}))
|
||||
{
|
||||
folderQueue.Enqueue(childDirectory);
|
||||
}
|
||||
}
|
||||
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
|
||||
{
|
||||
// ProgramLogger.Warn($"|Permission denied when trying to load programs from {currentDirectory}", e, MethodBase.GetCurrentMethod().DeclaringType, currentDirectory);
|
||||
}
|
||||
catch (Exception )
|
||||
{
|
||||
// ProgramLogger.Exception($"|An unexpected error occurred in the calling method ProgramPaths at {currentDirectory}", e, MethodBase.GetCurrentMethod().DeclaringType, currentDirectory);
|
||||
}
|
||||
}
|
||||
while (folderQueue.Count > 0);
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
private static string Extension(string path)
|
||||
{
|
||||
// Using InvariantCulture since this is user facing
|
||||
var extension = Path.GetExtension(path)?.ToLowerInvariant();
|
||||
|
||||
return !string.IsNullOrEmpty(extension)
|
||||
? extension.Substring(1)
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
private static IEnumerable<string> CustomProgramPaths(IEnumerable<ProgramSource> sources, IList<string> suffixes)
|
||||
=> sources?.Where(programSource => Directory.Exists(programSource.Location) && programSource.Enabled)
|
||||
.SelectMany(programSource => ProgramPaths(programSource.Location, suffixes))
|
||||
.ToList() ?? Enumerable.Empty<string>();
|
||||
|
||||
// Function to obtain the list of applications, the locations of which have been added to the env variable PATH
|
||||
private static List<string> PathEnvironmentProgramPaths(IList<string> suffixes)
|
||||
{
|
||||
// To get all the locations stored in the PATH env variable
|
||||
var pathEnvVariable = Environment.GetEnvironmentVariable("PATH") ?? string.Empty;
|
||||
var searchPaths = pathEnvVariable.Split(Path.PathSeparator);
|
||||
var toFilterAllPaths = new List<string>();
|
||||
var isRecursiveSearch = true;
|
||||
|
||||
foreach (var path in searchPaths)
|
||||
{
|
||||
if (path.Length > 0)
|
||||
{
|
||||
// to expand any environment variables present in the path
|
||||
var directory = Environment.ExpandEnvironmentVariables(path);
|
||||
var paths = ProgramPaths(directory, suffixes, !isRecursiveSearch);
|
||||
toFilterAllPaths.AddRange(paths);
|
||||
}
|
||||
}
|
||||
|
||||
return toFilterAllPaths;
|
||||
}
|
||||
|
||||
private static List<string> IndexPath(IList<string> suffixes, List<string> indexLocations)
|
||||
=> indexLocations
|
||||
.SelectMany(indexLocation => ProgramPaths(indexLocation, suffixes))
|
||||
.ToList();
|
||||
|
||||
private static List<string> StartMenuProgramPaths(IList<string> suffixes)
|
||||
{
|
||||
var directory1 = Environment.GetFolderPath(Environment.SpecialFolder.StartMenu);
|
||||
var directory2 = Environment.GetFolderPath(Environment.SpecialFolder.CommonStartMenu);
|
||||
var indexLocation = new List<string>() { directory1, directory2 };
|
||||
|
||||
return IndexPath(suffixes, indexLocation);
|
||||
}
|
||||
|
||||
private static List<string> DesktopProgramPaths(IList<string> suffixes)
|
||||
{
|
||||
var directory1 = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
||||
var directory2 = Environment.GetFolderPath(Environment.SpecialFolder.CommonDesktopDirectory);
|
||||
|
||||
var indexLocation = new List<string>() { directory1, directory2 };
|
||||
|
||||
return IndexPath(suffixes, indexLocation);
|
||||
}
|
||||
|
||||
private static List<string> RegistryAppProgramPaths(IList<string> suffixes)
|
||||
{
|
||||
// https://msdn.microsoft.com/library/windows/desktop/ee872121
|
||||
const string appPaths = @"SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths";
|
||||
var paths = new List<string>();
|
||||
using (var root = Registry.LocalMachine.OpenSubKey(appPaths))
|
||||
{
|
||||
if (root != null)
|
||||
{
|
||||
paths.AddRange(GetPathsFromRegistry(root));
|
||||
}
|
||||
}
|
||||
|
||||
using (var root = Registry.CurrentUser.OpenSubKey(appPaths))
|
||||
{
|
||||
if (root != null)
|
||||
{
|
||||
paths.AddRange(GetPathsFromRegistry(root));
|
||||
}
|
||||
}
|
||||
|
||||
return paths
|
||||
.Where(path => suffixes.Any(suffix => path.EndsWith(suffix, StringComparison.InvariantCultureIgnoreCase)))
|
||||
.Select(ExpandEnvironmentVariables)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static IEnumerable<string> GetPathsFromRegistry(RegistryKey root)
|
||||
=> root
|
||||
.GetSubKeyNames()
|
||||
.Select(x => GetPathFromRegistrySubkey(root, x));
|
||||
|
||||
private static string GetPathFromRegistrySubkey(RegistryKey root, string subkey)
|
||||
{
|
||||
var path = string.Empty;
|
||||
try
|
||||
{
|
||||
using (var key = root.OpenSubKey(subkey))
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var defaultValue = string.Empty;
|
||||
path = key.GetValue(defaultValue) as string;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
// fix path like this: ""\"C:\\folder\\executable.exe\""
|
||||
return path = path.Trim('"', ' ');
|
||||
}
|
||||
catch (Exception e) when (e is SecurityException || e is UnauthorizedAccessException)
|
||||
{
|
||||
// ProgramLogger.Warn($"|Permission denied when trying to load the program from {path}", e, MethodBase.GetCurrentMethod().DeclaringType, path);
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private static string ExpandEnvironmentVariables(string path)
|
||||
{
|
||||
return path != null
|
||||
? Environment.ExpandEnvironmentVariables(path)
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
// Overriding the object.GetHashCode() function to aid in removing duplicates while adding and removing apps from the concurrent dictionary storage
|
||||
public override int GetHashCode()
|
||||
=> Win32ProgramEqualityComparer.Default.GetHashCode(this);
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is Win32Program win32Program && Win32ProgramEqualityComparer.Default.Equals(this, win32Program);
|
||||
}
|
||||
|
||||
private sealed class Win32ProgramEqualityComparer : IEqualityComparer<Win32Program>
|
||||
{
|
||||
public static readonly Win32ProgramEqualityComparer Default = new();
|
||||
|
||||
public bool Equals(Win32Program? app1, Win32Program? app2)
|
||||
{
|
||||
if (app1 == null && app2 == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return app1 != null
|
||||
&& app2 != null
|
||||
&& (app1.Name?.ToUpperInvariant(), app1.ExecutableName?.ToUpperInvariant(), app1.FullPath?.ToUpperInvariant())
|
||||
.Equals((app2.Name?.ToUpperInvariant(), app2.ExecutableName?.ToUpperInvariant(), app2.FullPath?.ToUpperInvariant()));
|
||||
}
|
||||
|
||||
public int GetHashCode(Win32Program obj)
|
||||
=> (obj.Name?.ToUpperInvariant(), obj.ExecutableName?.ToUpperInvariant(), obj.FullPath?.ToUpperInvariant()).GetHashCode();
|
||||
}
|
||||
|
||||
public static List<Win32Program> DeduplicatePrograms(IEnumerable<Win32Program> programs)
|
||||
=> new HashSet<Win32Program>(programs, Win32ProgramEqualityComparer.Default).ToList();
|
||||
|
||||
private static Win32Program GetProgramFromPath(string path)
|
||||
{
|
||||
var extension = Extension(path);
|
||||
if (ExecutableApplicationExtensions.Contains(extension))
|
||||
{
|
||||
return ExeProgram(path);
|
||||
}
|
||||
|
||||
switch (extension)
|
||||
{
|
||||
case ShortcutExtension:
|
||||
return LnkProgram(path);
|
||||
case ApplicationReferenceExtension:
|
||||
return CreateWin32Program(path);
|
||||
case InternetShortcutExtension:
|
||||
return InternetShortcutProgram(path);
|
||||
default:
|
||||
return new Win32Program();
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryGetIcoPathForRunCommandProgram(Win32Program program, out string icoPath)
|
||||
{
|
||||
icoPath = string.Empty;
|
||||
|
||||
if (program.AppType != ApplicationType.RunCommand)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(program.FullPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://msdn.microsoft.com/library/windows/desktop/ee872121
|
||||
try
|
||||
{
|
||||
var redirectionPath = string.Empty; // ReparsePoint.GetTarget(program.FullPath);
|
||||
if (string.IsNullOrEmpty(redirectionPath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
icoPath = ExpandEnvironmentVariables(redirectionPath);
|
||||
return true;
|
||||
}
|
||||
catch (IOException )
|
||||
{
|
||||
// ProgramLogger.Warn($"|Error whilst retrieving the redirection path from app execution alias {program.FullPath}", e, MethodBase.GetCurrentMethod().DeclaringType, program.FullPath);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Win32Program GetRunCommandProgramFromPath(string path)
|
||||
{
|
||||
var program = GetProgramFromPath(path) ?? new Win32Program();
|
||||
program.AppType = ApplicationType.RunCommand;
|
||||
|
||||
if (TryGetIcoPathForRunCommandProgram(program, out var icoPath))
|
||||
{
|
||||
program.IcoPath = icoPath;
|
||||
}
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
public static IList<Win32Program> All()
|
||||
{
|
||||
// ArgumentNullException.ThrowIfNull(settings);
|
||||
var settings_ProgramSuffixes = new List<string>() { "bat", "appref-ms", "exe", "lnk", "url" };
|
||||
var settings_RunCommandSuffixes = new List<string>() { "bat", "appref-ms", "exe", "lnk", "url", "cpl", "msc" };
|
||||
try
|
||||
{
|
||||
// Set an initial size to an expected size to prevent multiple hashSet resizes
|
||||
const int defaultHashsetSize = 1000;
|
||||
|
||||
// Multiple paths could have the same programPaths and we don't want to resolve / lookup them multiple times
|
||||
var paths = new HashSet<string>(defaultHashsetSize);
|
||||
var runCommandPaths = new HashSet<string>(defaultHashsetSize);
|
||||
|
||||
// Parallelize multiple sources, and priority based on paths which most likely contain .lnks which are formatted
|
||||
var sources = new (bool IsEnabled, Func<IEnumerable<string>> GetPaths)[]
|
||||
{
|
||||
// (true, () => CustomProgramPaths(settings.ProgramSources, settings.ProgramSuffixes)),
|
||||
(/*settings.EnableStartMenuSource*/ true, () => StartMenuProgramPaths(settings_ProgramSuffixes)),
|
||||
(/*settings.EnableDesktopSource*/true, () => DesktopProgramPaths(settings_ProgramSuffixes)),
|
||||
(/*settings.EnableRegistrySource*/true, () => RegistryAppProgramPaths(settings_ProgramSuffixes)),
|
||||
};
|
||||
|
||||
// Run commands are always set as AppType "RunCommand"
|
||||
var runCommandSources = new (bool IsEnabled, Func<IEnumerable<string>> GetPaths)[]
|
||||
{
|
||||
(/*settings.EnablePathEnvironmentVariableSource*/ false, () => PathEnvironmentProgramPaths(settings_RunCommandSuffixes)),
|
||||
};
|
||||
|
||||
// Get all paths but exclude all normal .Executables
|
||||
paths.UnionWith(sources
|
||||
.AsParallel()
|
||||
.SelectMany(source => source.IsEnabled ? source.GetPaths() : Enumerable.Empty<string>())
|
||||
.Where(path => !ExecutableApplicationExtensions.Contains(Extension(path))));
|
||||
runCommandPaths.UnionWith(runCommandSources
|
||||
.AsParallel()
|
||||
.SelectMany(source => source.IsEnabled ? source.GetPaths() : Enumerable.Empty<string>()));
|
||||
|
||||
var programs = paths.AsParallel().Select(source => GetProgramFromPath(source));
|
||||
var runCommandPrograms = runCommandPaths.AsParallel().Select(source => GetRunCommandProgramFromPath(source));
|
||||
|
||||
return DeduplicatePrograms(programs.Concat(runCommandPrograms).Where(program => program?.Valid == true));
|
||||
}
|
||||
catch (Exception )
|
||||
{
|
||||
// ProgramLogger.Exception("An unexpected error occurred", e, MethodBase.GetCurrentMethod().DeclaringType, "Not available");
|
||||
return Array.Empty<Win32Program>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// 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.IO;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
internal sealed partial class AddBookmarkForm : Form
|
||||
{
|
||||
internal event TypedEventHandler<object, object?>? AddedAction;
|
||||
|
||||
public override string TemplateJson()
|
||||
{
|
||||
var json = $$"""
|
||||
{
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.5",
|
||||
"body": [
|
||||
{
|
||||
"type": "Input.Text",
|
||||
"style": "text",
|
||||
"id": "name",
|
||||
"label": "Name",
|
||||
"isRequired": true,
|
||||
"errorMessage": "Name is required"
|
||||
},
|
||||
{
|
||||
"type": "Input.Text",
|
||||
"style": "text",
|
||||
"id": "bookmark",
|
||||
"label": "URL or File Path",
|
||||
"isRequired": true,
|
||||
"errorMessage": "URL or File Path is required"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "Action.Submit",
|
||||
"title": "Save",
|
||||
"data": {
|
||||
"name": "name",
|
||||
"bookmark": "bookmark"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
return json;
|
||||
}
|
||||
|
||||
public override string DataJson() => throw new NotImplementedException();
|
||||
|
||||
public override string StateJson() => throw new NotImplementedException();
|
||||
|
||||
public override CommandResult SubmitForm(string payload)
|
||||
{
|
||||
var formInput = JsonNode.Parse(payload);
|
||||
if (formInput == null)
|
||||
{
|
||||
return CommandResult.GoHome();
|
||||
}
|
||||
|
||||
// get the name and url out of the values
|
||||
var formName = formInput["name"] ?? string.Empty;
|
||||
var formBookmark = formInput["bookmark"] ?? string.Empty;
|
||||
var hasPlaceholder = formBookmark.ToString().Contains('{') && formBookmark.ToString().Contains('}');
|
||||
|
||||
// Determine the type of the bookmark
|
||||
string bookmarkType;
|
||||
|
||||
if (formBookmark.ToString().StartsWith("http://", StringComparison.OrdinalIgnoreCase) || formBookmark.ToString().StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
bookmarkType = "web";
|
||||
}
|
||||
else if (File.Exists(formBookmark.ToString()))
|
||||
{
|
||||
bookmarkType = "file";
|
||||
}
|
||||
else if (Directory.Exists(formBookmark.ToString()))
|
||||
{
|
||||
bookmarkType = "folder";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Default to web if we can't determine the type
|
||||
bookmarkType = "web";
|
||||
}
|
||||
|
||||
var formData = new BookmarkData()
|
||||
{
|
||||
Name = formName.ToString(),
|
||||
Bookmark = formBookmark.ToString(),
|
||||
Type = bookmarkType,
|
||||
};
|
||||
|
||||
// Construct a new json blob with the name and url
|
||||
var jsonPath = BookmarksCommandProvider.StateJsonPath();
|
||||
var data = Bookmarks.ReadFromFile(jsonPath);
|
||||
|
||||
data.Data.Add(formData);
|
||||
|
||||
Bookmarks.WriteToFile(jsonPath, data);
|
||||
|
||||
AddedAction?.Invoke(this, null);
|
||||
return CommandResult.GoHome();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// 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 Microsoft.CmdPal.Extensions;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
internal sealed partial class AddBookmarkPage : FormPage
|
||||
{
|
||||
private readonly AddBookmarkForm _addBookmark = new();
|
||||
|
||||
internal event TypedEventHandler<object, object?>? AddedAction
|
||||
{
|
||||
add => _addBookmark.AddedAction += value;
|
||||
remove => _addBookmark.AddedAction -= value;
|
||||
}
|
||||
|
||||
public override IForm[] Forms() => [_addBookmark];
|
||||
|
||||
public AddBookmarkPage()
|
||||
{
|
||||
this.Icon = new("\ued0e");
|
||||
this.Name = "Add a Bookmark";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
public class BookmarkData
|
||||
{
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string Bookmark { get; set; } = string.Empty;
|
||||
|
||||
public string Type { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// 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.Text.Json.Serialization;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
[JsonSerializable(typeof(BookmarkData))]
|
||||
[JsonSerializable(typeof(string))]
|
||||
[JsonSerializable(typeof(string))]
|
||||
internal sealed partial class BookmarkDataContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// 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.Linq;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.CmdPal.Extensions.Helpers;
|
||||
using Windows.System;
|
||||
|
||||
namespace Microsoft.CmdPal.Ext.Bookmarks;
|
||||
|
||||
internal sealed partial class BookmarkPlaceholderForm : Form
|
||||
{
|
||||
private readonly List<string> _placeholderNames;
|
||||
|
||||
private readonly string _bookmark = string.Empty;
|
||||
|
||||
// TODO pass in an array of placeholders
|
||||
public BookmarkPlaceholderForm(string name, string url, string type)
|
||||
{
|
||||
_bookmark = url;
|
||||
Regex r = new Regex(Regex.Escape("{") + "(.*?)" + Regex.Escape("}"));
|
||||
MatchCollection matches = r.Matches(url);
|
||||
_placeholderNames = matches.Select(m => m.Groups[1].Value).ToList();
|
||||
}
|
||||
|
||||
public override string TemplateJson()
|
||||
{
|
||||
var inputs = _placeholderNames.Select(p =>
|
||||
{
|
||||
return $$"""
|
||||
{
|
||||
"type": "Input.Text",
|
||||
"style": "text",
|
||||
"id": "{{p}}",
|
||||
"label": "{{p}}",
|
||||
"isRequired": true,
|
||||
"errorMessage": "{{p}} is required"
|
||||
}
|
||||
""";
|
||||
}).ToList();
|
||||
|
||||
var allInputs = string.Join(",", inputs);
|
||||
|
||||
var json = $$"""
|
||||
{
|
||||
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
||||
"type": "AdaptiveCard",
|
||||
"version": "1.5",
|
||||
"body": [
|
||||
""" + allInputs + """
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"type": "Action.Submit",
|
||||
"title": "Open",
|
||||
"data": {
|
||||
"placeholder": "placeholder"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
return json;
|
||||
}
|
||||
|
||||
public override string DataJson() => throw new NotImplementedException();
|
||||
|
||||
public override string StateJson() => throw new NotImplementedException();
|
||||
|
||||
public override CommandResult SubmitForm(string payload)
|
||||
{
|
||||
var target = _bookmark;
|
||||
|
||||
// parse the submitted JSON and then open the link
|
||||
var formInput = JsonNode.Parse(payload);
|
||||
var formObject = formInput?.AsObject();
|
||||
if (formObject == null)
|
||||
{
|
||||
return CommandResult.GoHome();
|
||||
}
|
||||
|
||||
foreach (var (key, value) in formObject)
|
||||
{
|
||||
var placeholderString = $"{{{key}}}";
|
||||
var placeholderData = value?.ToString();
|
||||
target = target.Replace(placeholderString, placeholderData);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Uri? uri = UrlAction.GetUri(target);
|
||||
if (uri != null)
|
||||
{
|
||||
_ = Launcher.LaunchUriAsync(uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
// throw new UriFormatException("The provided URL is not valid.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error launching URL: {ex.Message}");
|
||||
}
|
||||
|
||||
return CommandResult.GoHome();
|
||||
}
|
||||
}
|
||||