Merge branch 'main' into main

This commit is contained in:
Clint Rutkas
2024-10-21 20:29:41 -07:00
committed by GitHub
574 changed files with 24118 additions and 7 deletions

6
.gitignore vendored
View File

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

View File

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

View File

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

View 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

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

View 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"
]
}

View 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)"
}
]
}

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@@ -0,0 +1,10 @@
{
"profiles": {
"HackerNewsExtension (Package)": {
"commandName": "MsixPackage"
},
"HackerNewsExtension (Unpackaged)": {
"commandName": "Project"
}
}
}

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

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

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

View File

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

View File

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

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

View File

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

View File

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

View File

@@ -0,0 +1,10 @@
{
"profiles": {
"MastodonExtension (Package)": {
"commandName": "MsixPackage"
},
"MastodonExtension (Unpackaged)": {
"commandName": "Project"
}
}
}

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

@@ -0,0 +1,10 @@
{
"profiles": {
"MediaControlsExtension (Package)": {
"commandName": "MsixPackage"
},
"MediaControlsExtension (Unpackaged)": {
"commandName": "Project"
}
}
}

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

View File

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

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

View File

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

View File

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

View File

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

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

View File

@@ -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 = [];
}
}
}

View File

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

View File

@@ -0,0 +1,19 @@
GetPhysicallyInstalledSystemMemory
GlobalMemoryStatusEx
GetSystemInfo
CoCreateInstance
SetForegroundWindow
IsIconic
RegisterHotKey
SetWindowLongPtr
CallWindowProc
ShowWindow
SetForegroundWindow
SetFocus
SetActiveWindow
MonitorFromWindow
GetMonitorInfo
SHCreateStreamOnFileEx
CoAllowSetForegroundWindow
SHCreateStreamOnFileEx
SHLoadIndirectString

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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 Microsoft.CmdPal.Ext.Apps.Programs;
public interface IShellLinkHelper
{
string RetrieveTargetPath(string path);
string Description { get; set; }
string Arguments { get; set; }
bool HasArguments { get; set; }
}

View File

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

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More