Rename the [Ee]xts dir to ext (#38852)

**WARNING:** This PR will probably blow up all in-flight PRs

at some point in the early days of CmdPal, two of us created seperate
`Exts` and `exts` dirs. Depending on what the casing was on the branch
that you checked one of those out from, it'd get stuck like that on your
PC forever.

Windows didn't care, so we never noticed.

But GitHub does care, and now browsing the source on GitHub is basically
impossible.

Closes #38081
This commit is contained in:
Mike Griese
2025-04-15 06:07:22 -05:00
committed by GitHub
parent 60f50d853b
commit 2b5181b4c9
379 changed files with 35 additions and 35 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -0,0 +1,14 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.64706 0C9.69012 0 10.5357 0.884576 10.5357 1.97576L10.5351 2.4H13.2353C13.6576 2.4 14 2.75818 14 3.2V7C13.4477 7 13 7.44772 13 8C13 8.55229 13.4477 9 14 9V12.8023C14 13.2442 13.6576 13.6023 13.2353 13.6023L10.5351 13.6016L10.5357 14.0242C10.5357 15.1154 9.69012 16 8.64706 16C7.604 16 6.75846 15.1154 6.75846 14.0242L6.75823 13.6016L4.05882 13.6023C3.63649 13.6023 3.29412 13.2442 3.29412 12.8023L3.29335 9.9768L2.88859 9.97696C1.84555 9.97696 1 9.09232 1 8.0012C1 6.91 1.84555 6.02542 2.88859 6.02542L3.29335 6.0248L3.29412 3.2C3.29412 2.75818 3.63649 2.4 4.05882 2.4H6.75823L6.75846 1.97576C6.75846 0.884576 7.604 0 8.64706 0Z" fill="url(#paint0_linear_1889_18231)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.39833 4.18539C7.43685 4.07388 7.53504 4 7.64474 4H9.42105C9.50736 4 9.58818 4.04595 9.63735 4.12297C9.68651 4.19999 9.69776 4.29829 9.66745 4.38603L8.81395 6.85714H10.7368C10.8413 6.85714 10.9359 6.92426 10.9779 7.02818C11.0198 7.13209 11.0011 7.2531 10.9303 7.33651L7.04875 11.9079C6.96343 12.0084 6.82451 12.0292 6.71732 11.9576C6.61012 11.886 6.56466 11.7419 6.60886 11.614L7.65974 8.57142H6.26316C6.17685 8.57142 6.09602 8.52547 6.04686 8.44845C5.9977 8.37143 5.98645 8.27313 6.01676 8.18539L7.39833 4.18539Z" fill="url(#paint1_linear_1889_18231)"/>
<defs>
<linearGradient id="paint0_linear_1889_18231" x1="5.5" y1="1" x2="11.5" y2="15" gradientUnits="userSpaceOnUse">
<stop stop-color="#097ED1"/>
<stop offset="1" stop-color="#0F5499"/>
</linearGradient>
<linearGradient id="paint1_linear_1889_18231" x1="6" y1="5.28571" x2="11.4103" y2="10.2689" gradientUnits="userSpaceOnUse">
<stop stop-color="#32BCEF"/>
<stop offset="1" stop-color="#128FDD"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1,70 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1874_17802)">
<g clip-path="url(#clip1_1874_17802)">
<path d="M0 3C0 2.44772 0.447715 2 1 2H14C14.5523 2 15 2.44772 15 3V12C15 13.6569 13.6569 15 12 15H3C1.34315 15 0 13.6569 0 12V3Z" fill="url(#paint0_linear_1874_17802)"/>
<rect x="3" y="4" width="9" height="9" rx="0.666667" fill="white" fill-opacity="0.5"/>
<g filter="url(#filter0_d_1874_17802)">
<path d="M7 5H4V8H7V5Z" fill="#F25022"/>
<path d="M11 5H8V8H11V5Z" fill="#7FBA00"/>
<path d="M11 9H8V12H11V9Z" fill="#FFB900"/>
<path d="M7 9H4V12H7V9Z" fill="#00A4EF"/>
</g>
<path d="M3.5 2.5V1C3.5 0.723858 3.72386 0.5 4 0.5H11C11.2761 0.5 11.5 0.723858 11.5 1V2.5" stroke="url(#paint1_linear_1874_17802)" stroke-linecap="round"/>
<rect x="4" width="7" height="1" fill="url(#paint2_linear_1874_17802)"/>
<mask id="mask0_1874_17802" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="4" y="0" width="7" height="1">
<rect x="4" width="7" height="1" fill="url(#paint3_linear_1874_17802)"/>
</mask>
<g mask="url(#mask0_1874_17802)">
<g filter="url(#filter1_dd_1874_17802)">
</g>
</g>
</g>
</g>
<defs>
<filter id="filter0_d_1874_17802" x="3.33333" y="4.5" width="8.33333" height="8.33333" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="0.166667"/>
<feGaussianBlur stdDeviation="0.333333"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1874_17802"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1874_17802" result="shape"/>
</filter>
<filter id="filter1_dd_1874_17802" x="2" y="-1.66667" width="11" height="5" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="-0.666667"/>
<feGaussianBlur stdDeviation="0.166667"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1874_17802"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="-0.666667"/>
<feGaussianBlur stdDeviation="0.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_1874_17802" result="effect2_dropShadow_1874_17802"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_1874_17802" result="shape"/>
</filter>
<linearGradient id="paint0_linear_1874_17802" x1="3.40909" y1="0.079545" x2="6.52747" y2="14.4721" gradientUnits="userSpaceOnUse">
<stop offset="0.270833" stop-color="white"/>
<stop offset="1" stop-color="#DFDFDF"/>
</linearGradient>
<linearGradient id="paint1_linear_1874_17802" x1="7.5" y1="0.5" x2="7.63195" y2="2.65519" gradientUnits="userSpaceOnUse">
<stop stop-color="#30DAFF"/>
<stop offset="1" stop-color="#0094D4"/>
</linearGradient>
<linearGradient id="paint2_linear_1874_17802" x1="7.11111" y1="2.61652e-08" x2="7.11111" y2="1" gradientUnits="userSpaceOnUse">
<stop stop-color="#22BCFF"/>
<stop offset="1" stop-color="#0088F0"/>
</linearGradient>
<linearGradient id="paint3_linear_1874_17802" x1="7.11111" y1="2.61652e-08" x2="7.11111" y2="1" gradientUnits="userSpaceOnUse">
<stop stop-color="#28AFEA"/>
<stop offset="1" stop-color="#3CCBF4"/>
</linearGradient>
<clipPath id="clip0_1874_17802">
<rect width="16" height="16" fill="white"/>
</clipPath>
<clipPath id="clip1_1874_17802">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,70 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1874_17861)">
<g clip-path="url(#clip1_1874_17861)">
<path d="M0 3C0 2.44772 0.447715 2 1 2H14C14.5523 2 15 2.44772 15 3V12C15 13.6569 13.6569 15 12 15H3C1.34315 15 0 13.6569 0 12V3Z" fill="url(#paint0_linear_1874_17861)"/>
<rect x="3" y="4" width="9" height="9" rx="0.666667" fill="#243A5F" fill-opacity="0.5"/>
<g filter="url(#filter0_d_1874_17861)">
<path d="M7 5H4V8H7V5Z" fill="#F25022"/>
<path d="M11 5H8V8H11V5Z" fill="#7FBA00"/>
<path d="M11 9H8V12H11V9Z" fill="#FFB900"/>
<path d="M7 9H4V12H7V9Z" fill="#00A4EF"/>
</g>
<path d="M3.5 2.5V1C3.5 0.723858 3.72386 0.5 4 0.5H11C11.2761 0.5 11.5 0.723858 11.5 1V2.5" stroke="url(#paint1_linear_1874_17861)" stroke-linecap="round"/>
<rect x="4" width="7" height="1" fill="url(#paint2_linear_1874_17861)"/>
<mask id="mask0_1874_17861" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="4" y="0" width="7" height="1">
<rect x="4" width="7" height="1" fill="url(#paint3_linear_1874_17861)"/>
</mask>
<g mask="url(#mask0_1874_17861)">
<g filter="url(#filter1_dd_1874_17861)">
</g>
</g>
</g>
</g>
<defs>
<filter id="filter0_d_1874_17861" x="3.33333" y="4.5" width="8.33333" height="8.33333" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="0.166667"/>
<feGaussianBlur stdDeviation="0.333333"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1874_17861"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_1874_17861" result="shape"/>
</filter>
<filter id="filter1_dd_1874_17861" x="2" y="-1.66667" width="11" height="5" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="-0.666667"/>
<feGaussianBlur stdDeviation="0.166667"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1874_17861"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="-0.666667"/>
<feGaussianBlur stdDeviation="0.5"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.2 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_1874_17861" result="effect2_dropShadow_1874_17861"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_1874_17861" result="shape"/>
</filter>
<linearGradient id="paint0_linear_1874_17861" x1="7.5" y1="2" x2="10.3558" y2="15.4551" gradientUnits="userSpaceOnUse">
<stop stop-color="#0669BC"/>
<stop offset="1" stop-color="#243A5F"/>
</linearGradient>
<linearGradient id="paint1_linear_1874_17861" x1="7.5" y1="0.5" x2="7.63195" y2="2.65519" gradientUnits="userSpaceOnUse">
<stop stop-color="#30DAFF"/>
<stop offset="1" stop-color="#0094D4"/>
</linearGradient>
<linearGradient id="paint2_linear_1874_17861" x1="7.11111" y1="2.61652e-08" x2="7.11111" y2="1" gradientUnits="userSpaceOnUse">
<stop stop-color="#22BCFF"/>
<stop offset="1" stop-color="#0088F0"/>
</linearGradient>
<linearGradient id="paint3_linear_1874_17861" x1="7.11111" y1="2.61652e-08" x2="7.11111" y2="1" gradientUnits="userSpaceOnUse">
<stop stop-color="#28AFEA"/>
<stop offset="1" stop-color="#3CCBF4"/>
</linearGradient>
<clipPath id="clip0_1874_17861">
<rect width="16" height="16" fill="white"/>
</clipPath>
<clipPath id="clip1_1874_17861">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,97 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1771_11727)">
<path d="M2.01172 1C2.01172 0.447715 2.45943 0 3.01172 0H13.0117C13.564 0 14.0117 0.447715 14.0117 1V11C14.0117 11.5523 13.564 12 13.0117 12H3.01172C2.45943 12 2.01172 11.5523 2.01172 11V1Z" fill="#9C640A"/>
<mask id="mask0_1771_11727" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="2" y="0" width="13" height="12">
<path d="M2.01172 1C2.01172 0.447715 2.45943 0 3.01172 0H13.0117C13.564 0 14.0117 0.447715 14.0117 1V11C14.0117 11.5523 13.564 12 13.0117 12H3.01172C2.45943 12 2.01172 11.5523 2.01172 11V1Z" fill="#9C640A"/>
</mask>
<g mask="url(#mask0_1771_11727)">
<g filter="url(#filter0_dd_1771_11727)">
<path d="M1.01172 3C1.01172 2.44772 1.45943 2 2.01172 2H14.0117C14.564 2 15.0117 2.44772 15.0117 3V13C15.0117 13.5523 14.564 14 14.0117 14H2.01172C1.45943 14 1.01172 13.5523 1.01172 13V3Z" fill="#BC822A"/>
</g>
</g>
<path d="M1.01172 3C1.01172 2.44772 1.45943 2 2.01172 2H14.0117C14.564 2 15.0117 2.44772 15.0117 3V13C15.0117 13.5523 14.564 14 14.0117 14H2.01172C1.45943 14 1.01172 13.5523 1.01172 13V3Z" fill="#BC822A"/>
<mask id="mask1_1771_11727" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="1" y="2" width="15" height="12">
<path d="M1.01172 3C1.01172 2.44772 1.45943 2 2.01172 2H14.0117C14.564 2 15.0117 2.44772 15.0117 3V13C15.0117 13.5523 14.564 14 14.0117 14H2.01172C1.45943 14 1.01172 13.5523 1.01172 13V3Z" fill="#BC822A"/>
</mask>
<g mask="url(#mask1_1771_11727)">
<g filter="url(#filter1_dd_1771_11727)">
<path d="M0.0117188 5C0.0117188 4.44772 0.459434 4 1.01172 4H15.0117C15.564 4 16.0117 4.44772 16.0117 5V15C16.0117 15.5523 15.564 16 15.0117 16H1.01172C0.459434 16 0.0117188 15.5523 0.0117188 15V5Z" fill="#D9D9D9"/>
<path d="M0.0117188 5C0.0117188 4.44772 0.459434 4 1.01172 4H15.0117C15.564 4 16.0117 4.44772 16.0117 5V15C16.0117 15.5523 15.564 16 15.0117 16H1.01172C0.459434 16 0.0117188 15.5523 0.0117188 15V5Z" fill="url(#paint0_linear_1771_11727)"/>
<path d="M0.0117188 5C0.0117188 4.44772 0.459434 4 1.01172 4H15.0117C15.564 4 16.0117 4.44772 16.0117 5V15C16.0117 15.5523 15.564 16 15.0117 16H1.01172C0.459434 16 0.0117188 15.5523 0.0117188 15V5Z" fill="url(#paint1_linear_1771_11727)"/>
</g>
</g>
<path d="M0.0117188 5C0.0117188 4.44772 0.459434 4 1.01172 4H15.0117C15.564 4 16.0117 4.44772 16.0117 5V15C16.0117 15.5523 15.564 16 15.0117 16H1.01172C0.459434 16 0.0117188 15.5523 0.0117188 15V5Z" fill="#D9D9D9"/>
<path d="M0.0117188 5C0.0117188 4.44772 0.459434 4 1.01172 4H15.0117C15.564 4 16.0117 4.44772 16.0117 5V15C16.0117 15.5523 15.564 16 15.0117 16H1.01172C0.459434 16 0.0117188 15.5523 0.0117188 15V5Z" fill="url(#paint2_linear_1771_11727)"/>
<path d="M0.0117188 5C0.0117188 4.44772 0.459434 4 1.01172 4H15.0117C15.564 4 16.0117 4.44772 16.0117 5V15C16.0117 15.5523 15.564 16 15.0117 16H1.01172C0.459434 16 0.0117188 15.5523 0.0117188 15V5Z" fill="url(#paint3_linear_1771_11727)"/>
<g filter="url(#filter2_dd_1771_11727)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.01172 7C9.01172 6.44772 8.564 6 8.01172 6C7.45943 6 7.01172 6.44772 7.01172 7V10.5858L6.21883 9.79289C5.8283 9.40237 5.19514 9.40237 4.80461 9.79289C4.41409 10.1834 4.41409 10.8166 4.80461 11.2071L7.30461 13.7071C7.69514 14.0976 8.3283 14.0976 8.71883 13.7071L11.2188 11.2071C11.6093 10.8166 11.6093 10.1834 11.2188 9.79289C10.8283 9.40237 10.1951 9.40237 9.80461 9.79289L9.01172 10.5858V7Z" fill="url(#paint4_linear_1771_11727)"/>
</g>
</g>
<defs>
<filter id="filter0_dd_1771_11727" x="0.345052" y="1.00333" width="15.3333" height="13.3333" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="-0.33"/>
<feGaussianBlur stdDeviation="0.333333"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.32 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1771_11727"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="-0.07"/>
<feGaussianBlur stdDeviation="0.0333333"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_1771_11727" result="effect2_dropShadow_1771_11727"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_1771_11727" result="shape"/>
</filter>
<filter id="filter1_dd_1771_11727" x="-0.654948" y="3.00333" width="17.3333" height="13.3333" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="-0.33"/>
<feGaussianBlur stdDeviation="0.333333"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.32 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1771_11727"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="-0.07"/>
<feGaussianBlur stdDeviation="0.0333333"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_1771_11727" result="effect2_dropShadow_1771_11727"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_1771_11727" result="shape"/>
</filter>
<filter id="filter2_dd_1771_11727" x="3.84505" y="5.66667" width="8.33333" height="9.33333" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="0.333333"/>
<feGaussianBlur stdDeviation="0.333333"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.32 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_1771_11727"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset dy="0.0666667"/>
<feGaussianBlur stdDeviation="0.0333333"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.24 0"/>
<feBlend mode="normal" in2="effect1_dropShadow_1771_11727" result="effect2_dropShadow_1771_11727"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow_1771_11727" result="shape"/>
</filter>
<linearGradient id="paint0_linear_1771_11727" x1="0.0117188" y1="4" x2="11.5317" y2="19.36" gradientUnits="userSpaceOnUse">
<stop stop-color="#DEB678"/>
<stop offset="1" stop-color="#C59141"/>
</linearGradient>
<linearGradient id="paint1_linear_1771_11727" x1="4.51172" y1="2.5" x2="9.51172" y2="17.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#DEB678"/>
<stop offset="1" stop-color="#B57F2D"/>
</linearGradient>
<linearGradient id="paint2_linear_1771_11727" x1="0.0117188" y1="4" x2="11.5317" y2="19.36" gradientUnits="userSpaceOnUse">
<stop stop-color="#DEB678"/>
<stop offset="1" stop-color="#C59141"/>
</linearGradient>
<linearGradient id="paint3_linear_1771_11727" x1="4.51172" y1="2.5" x2="9.51172" y2="17.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#DEB678"/>
<stop offset="1" stop-color="#B57F2D"/>
</linearGradient>
<linearGradient id="paint4_linear_1771_11727" x1="6.10263" y1="4.81818" x2="8.55203" y2="13.3911" gradientUnits="userSpaceOnUse">
<stop offset="0.270833" stop-color="white"/>
<stop offset="1" stop-color="#F0F0F0"/>
</linearGradient>
<clipPath id="clip0_1771_11727">
<rect width="16" height="16" fill="white" transform="translate(0.0117188)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@@ -0,0 +1,111 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\Common.Dotnet.CsWinRT.props" />
<PropertyGroup>
<RootNamespace>Microsoft.CmdPal.Ext.WinGet</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
<PublishProfile>win-$(Platform).pubxml</PublishProfile>
<UseWinUI>false</UseWinUI>
<Nullable>enable</Nullable>
<EnableMsixTooling>true</EnableMsixTooling>
<OutputPath>$(SolutionDir)$(Platform)\$(Configuration)\WinUI3Apps\CmdPal</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
<PropertyGroup>
<CsWinRTIncludes>Microsoft.Management.Deployment</CsWinRTIncludes>
<CsWinRTGeneratedFilesDir>$(OutDir)</CsWinRTGeneratedFilesDir>
</PropertyGroup>
<ItemGroup>
<None Remove="Assets\Extension.png" />
<None Remove="Assets\Extension.svg" />
<None Remove="Assets\Store.dark.png" />
<None Remove="Assets\Store.dark.svg" />
<None Remove="Assets\Store.light.png" />
<None Remove="Assets\Store.light.svg" />
<None Remove="Assets\WinGet.png" />
<None Remove="Assets\WinGet.svg" />
</ItemGroup>
<ItemGroup>
<Manifest Include="$(ApplicationManifest)" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\common\ManagedCommon\ManagedCommon.csproj" />
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
<ProjectReference Include="..\..\Microsoft.CmdPal.Common\Microsoft.CmdPal.Common.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>
<ItemGroup>
<CsWinRTInputs Include="$(PkgMicrosoft_WindowsPackageManager_ComInterop)\lib\uap10.0\Microsoft.Management.Deployment.winmd" />
<!-- Before v1.10 of this package, it wasn't in the uap10.0 dir -->
<Content Include="$(PkgMicrosoft_WindowsPackageManager_ComInterop)\lib\uap10.0\Microsoft.Management.Deployment.winmd" Link="Microsoft.Management.Deployment.winmd" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWin32">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.WindowsPackageManager.ComInterop">
<NoWarn>NU1701</NoWarn>
<GeneratePathProperty>true</GeneratePathProperty>
<IncludeAssets>none</IncludeAssets>
</PackageReference>
</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>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DependentUpon>Resources.resx</DependentUpon>
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
</ItemGroup>
<ItemGroup>
<Content Update="Assets\Extension.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Extension.svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Store.dark.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Store.dark.svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Store.light.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Store.light.svg">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,6 @@
CoCreateInstance
CoMarshalInterface
CoUnmarshalInterface
CreateStreamOnHGlobal
MSHCTX
MSHLFLAGS

View File

@@ -0,0 +1,230 @@
// 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.Globalization;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Management.Deployment;
using Windows.Foundation;
namespace Microsoft.CmdPal.Ext.WinGet.Pages;
public partial class InstallPackageCommand : InvokableCommand
{
private readonly CatalogPackage _package;
private readonly StatusMessage _installBanner = new();
private IAsyncOperationWithProgress<InstallResult, InstallProgress>? _installAction;
private IAsyncOperationWithProgress<UninstallResult, UninstallProgress>? _unInstallAction;
private Task? _installTask;
public bool IsInstalled { get; private set; }
public static IconInfo CompletedIcon { get; } = new("\uE930"); // Completed
public static IconInfo DownloadIcon { get; } = new("\uE896"); // Download
public static IconInfo DeleteIcon { get; } = new("\uE74D"); // Delete
public event EventHandler<InstallPackageCommand>? InstallStateChanged;
private static readonly CompositeFormat UninstallingPackage = System.Text.CompositeFormat.Parse(Properties.Resources.winget_uninstalling_package);
private static readonly CompositeFormat InstallingPackage = System.Text.CompositeFormat.Parse(Properties.Resources.winget_installing_package);
private static readonly CompositeFormat InstallPackageFinished = System.Text.CompositeFormat.Parse(Properties.Resources.winget_install_package_finished);
private static readonly CompositeFormat UninstallPackageFinished = System.Text.CompositeFormat.Parse(Properties.Resources.winget_uninstall_package_finished);
private static readonly CompositeFormat QueuedPackageDownload = System.Text.CompositeFormat.Parse(Properties.Resources.winget_queued_package_download);
private static readonly CompositeFormat InstallPackageFinishing = System.Text.CompositeFormat.Parse(Properties.Resources.winget_install_package_finishing);
private static readonly CompositeFormat QueuedPackageUninstall = System.Text.CompositeFormat.Parse(Properties.Resources.winget_queued_package_uninstall);
private static readonly CompositeFormat UninstallPackageFinishing = System.Text.CompositeFormat.Parse(Properties.Resources.winget_uninstall_package_finishing);
private static readonly CompositeFormat DownloadProgress = System.Text.CompositeFormat.Parse(Properties.Resources.winget_download_progress);
internal bool SkipDependencies { get; set; }
public InstallPackageCommand(CatalogPackage package, bool isInstalled)
{
_package = package;
IsInstalled = isInstalled;
UpdateAppearance();
}
internal void FakeChangeStatus()
{
IsInstalled = !IsInstalled;
UpdateAppearance();
}
private void UpdateAppearance()
{
Icon = IsInstalled ? CompletedIcon : DownloadIcon;
Name = IsInstalled ? Properties.Resources.winget_uninstall_name : Properties.Resources.winget_install_name;
}
public override ICommandResult Invoke()
{
// TODO: LOCK in here, so this can only be invoked once until the
// install / uninstall is done. Just use like, an atomic
if (_installTask != null)
{
return CommandResult.KeepOpen();
}
if (IsInstalled)
{
// Uninstall
_installBanner.State = MessageState.Info;
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, UninstallingPackage, _package.Name);
WinGetExtensionHost.Instance.ShowStatus(_installBanner, StatusContext.Extension);
var installOptions = WinGetStatics.WinGetFactory.CreateUninstallOptions();
installOptions.PackageUninstallScope = PackageUninstallScope.Any;
_unInstallAction = WinGetStatics.Manager.UninstallPackageAsync(_package, installOptions);
var handler = new AsyncOperationProgressHandler<UninstallResult, UninstallProgress>(OnUninstallProgress);
_unInstallAction.Progress = handler;
_installTask = Task.Run(() => TryDoInstallOperation(_unInstallAction));
}
else
{
// Install
_installBanner.State = MessageState.Info;
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, InstallingPackage, _package.Name);
WinGetExtensionHost.Instance.ShowStatus(_installBanner, StatusContext.Extension);
var installOptions = WinGetStatics.WinGetFactory.CreateInstallOptions();
installOptions.PackageInstallScope = PackageInstallScope.Any;
installOptions.SkipDependencies = SkipDependencies;
_installAction = WinGetStatics.Manager.InstallPackageAsync(_package, installOptions);
var handler = new AsyncOperationProgressHandler<InstallResult, InstallProgress>(OnInstallProgress);
_installAction.Progress = handler;
_installTask = Task.Run(() => TryDoInstallOperation(_installAction));
}
return CommandResult.KeepOpen();
}
private async void TryDoInstallOperation<T_Operation, T_Progress>(
IAsyncOperationWithProgress<T_Operation, T_Progress> action)
{
try
{
await action.AsTask();
_installBanner.Message = IsInstalled ?
string.Format(CultureInfo.CurrentCulture, UninstallPackageFinished, _package.Name) :
string.Format(CultureInfo.CurrentCulture, InstallPackageFinished, _package.Name);
_installBanner.Progress = null;
_installBanner.State = MessageState.Success;
_installTask = null;
_ = Task.Run(() =>
{
Thread.Sleep(2500);
if (_installTask == null)
{
WinGetExtensionHost.Instance.HideStatus(_installBanner);
}
});
InstallStateChanged?.Invoke(this, this);
}
catch (Exception ex)
{
_installBanner.State = MessageState.Error;
_installBanner.Progress = null;
_installBanner.Message = ex.Message;
_installTask = null;
}
}
private static string FormatBytes(ulong bytes)
{
const long KB = 1024;
const long MB = KB * 1024;
const long GB = MB * 1024;
return bytes >= GB
? $"{bytes / (double)GB:F2} GB"
: bytes >= MB ?
$"{bytes / (double)MB:F2} MB"
: bytes >= KB
? $"{bytes / (double)KB:F2} KB"
: $"{bytes} bytes";
}
private void OnInstallProgress(
IAsyncOperationWithProgress<InstallResult, InstallProgress> operation,
InstallProgress progress)
{
switch (progress.State)
{
case PackageInstallProgressState.Queued:
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, QueuedPackageDownload, _package.Name);
break;
case PackageInstallProgressState.Downloading:
if (progress.BytesRequired > 0)
{
var downloadText = string.Format(CultureInfo.CurrentCulture, DownloadProgress, FormatBytes(progress.BytesDownloaded), FormatBytes(progress.BytesRequired));
_installBanner.Progress ??= new ProgressState() { IsIndeterminate = false };
var downloaded = progress.BytesDownloaded / (float)progress.BytesRequired;
var percent = downloaded * 100.0f;
((ProgressState)_installBanner.Progress).ProgressPercent = (uint)percent;
_installBanner.Message = downloadText;
}
break;
case PackageInstallProgressState.Installing:
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, InstallingPackage, _package.Name);
_installBanner.Progress = new ProgressState() { IsIndeterminate = true };
break;
case PackageInstallProgressState.PostInstall:
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, InstallPackageFinishing, _package.Name);
break;
case PackageInstallProgressState.Finished:
_installBanner.Message = Properties.Resources.winget_install_finished;
// progressBar.IsIndeterminate(false);
_installBanner.Progress = null;
_installBanner.State = MessageState.Success;
break;
default:
_installBanner.Message = string.Empty;
break;
}
}
private void OnUninstallProgress(
IAsyncOperationWithProgress<UninstallResult, UninstallProgress> operation,
UninstallProgress progress)
{
switch (progress.State)
{
case PackageUninstallProgressState.Queued:
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, QueuedPackageUninstall, _package.Name);
break;
case PackageUninstallProgressState.Uninstalling:
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, UninstallingPackage, _package.Name);
_installBanner.Progress = new ProgressState() { IsIndeterminate = true };
break;
case PackageUninstallProgressState.PostUninstall:
_installBanner.Message = string.Format(CultureInfo.CurrentCulture, UninstallPackageFinishing, _package.Name);
break;
case PackageUninstallProgressState.Finished:
_installBanner.Message = Properties.Resources.winget_uninstall_finished;
_installBanner.Progress = null;
_installBanner.State = MessageState.Success;
break;
default:
_installBanner.Message = string.Empty;
break;
}
}
}

View File

@@ -0,0 +1,225 @@
// 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.Linq;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Management.Deployment;
using Windows.Foundation.Metadata;
namespace Microsoft.CmdPal.Ext.WinGet.Pages;
public partial class InstallPackageListItem : ListItem
{
private readonly CatalogPackage _package;
// Lazy-init the details
private readonly Lazy<Details?> _details;
public override IDetails? Details { get => _details.Value; set => base.Details = value; }
private InstallPackageCommand? _installCommand;
public InstallPackageListItem(CatalogPackage package)
: base(new NoOpCommand())
{
_package = package;
var version = _package.DefaultInstallVersion;
var versionTagText = "Unknown";
if (version != null)
{
versionTagText = version.Version == "Unknown" && version.PackageCatalog.Info.Id == "StoreEdgeFD" ? "msstore" : version.Version;
}
Title = _package.Name;
Subtitle = _package.Id;
Tags = [new Tag() { Text = versionTagText }];
_details = new Lazy<Details?>(() => BuildDetails(version));
_ = Task.Run(UpdatedInstalledStatus);
}
private Details? BuildDetails(PackageVersionInfo? version)
{
var metadata = version?.GetCatalogPackageMetadata();
if (metadata != null)
{
if (metadata.Tags.Where(t => t.Equals(WinGetExtensionPage.ExtensionsTag, StringComparison.OrdinalIgnoreCase)).Any())
{
if (_installCommand != null)
{
_installCommand.SkipDependencies = true;
}
}
var description = string.IsNullOrEmpty(metadata.Description) ? metadata.ShortDescription : metadata.Description;
var detailsBody = $"""
{description}
""";
IconInfo heroIcon = new(string.Empty);
var icons = metadata.Icons;
if (icons.Count > 0)
{
// There's also a .Theme property we could probably use to
// switch between default or individual icons.
heroIcon = new IconInfo(icons[0].Url);
}
return new Details()
{
Body = detailsBody,
Title = metadata.PackageName,
HeroImage = heroIcon,
Metadata = GetDetailsMetadata(metadata).ToArray(),
};
}
return null;
}
private List<IDetailsElement> GetDetailsMetadata(CatalogPackageMetadata metadata)
{
List<IDetailsElement> detailsElements = [];
// key -> {text, url}
Dictionary<string, (string, string)> simpleData = new()
{
{ Properties.Resources.winget_author, (metadata.Author, string.Empty) },
{ Properties.Resources.winget_publisher, (metadata.Publisher, metadata.PublisherUrl) },
{ Properties.Resources.winget_copyright, (metadata.Copyright, metadata.CopyrightUrl) },
{ Properties.Resources.winget_license, (metadata.License, metadata.LicenseUrl) },
{ Properties.Resources.winget_publisher_support, (string.Empty, metadata.PublisherSupportUrl) },
// The link to the release notes will only show up if there is an
// actual URL for the release notes
{ Properties.Resources.winget_view_release_notes, (string.IsNullOrEmpty(metadata.ReleaseNotesUrl) ? string.Empty : Properties.Resources.winget_view_online, metadata.ReleaseNotesUrl) },
// These can be l o n g
{ Properties.Resources.winget_release_notes, (metadata.ReleaseNotes, string.Empty) },
};
var docs = metadata.Documentations.ToArray();
foreach (var item in docs)
{
simpleData.Add(item.DocumentLabel, (string.Empty, item.DocumentUrl));
}
UriCreationOptions options = default;
foreach (var kv in simpleData)
{
var text = string.IsNullOrEmpty(kv.Value.Item1) ? kv.Value.Item2 : kv.Value.Item1;
var target = kv.Value.Item2;
if (!string.IsNullOrEmpty(text))
{
Uri? uri = null;
Uri.TryCreate(target, options, out uri);
DetailsElement pair = new()
{
Key = kv.Key,
Data = new DetailsLink() { Link = uri, Text = text },
};
detailsElements.Add(pair);
}
}
if (metadata.Tags.Any())
{
DetailsElement pair = new()
{
Key = "Tags",
Data = new DetailsTags() { Tags = metadata.Tags.Select(t => new Tag(t)).ToArray() },
};
detailsElements.Add(pair);
}
return detailsElements;
}
private async void UpdatedInstalledStatus()
{
var status = await _package.CheckInstalledStatusAsync();
var isInstalled = _package.InstalledVersion != null;
// might be an uninstall command
InstallPackageCommand installCommand = new(_package, isInstalled);
if (isInstalled)
{
this.Icon = InstallPackageCommand.CompletedIcon;
this.Command = new NoOpCommand();
List<IContextItem> contextMenu = [];
CommandContextItem uninstallContextItem = new(installCommand)
{
IsCritical = true,
Icon = InstallPackageCommand.DeleteIcon,
};
if (WinGetStatics.AppSearchCallback != null)
{
var callback = WinGetStatics.AppSearchCallback;
var installedApp = callback(_package.DefaultInstallVersion == null ? _package.Name : _package.DefaultInstallVersion.DisplayName);
if (installedApp != null)
{
this.Command = installedApp.Command;
contextMenu = [.. installedApp.MoreCommands];
}
}
contextMenu.Add(uninstallContextItem);
this.MoreCommands = contextMenu.ToArray();
return;
}
// didn't find the app
_installCommand = new InstallPackageCommand(_package, isInstalled);
this.Command = _installCommand;
Icon = _installCommand.Icon;
_installCommand.InstallStateChanged += InstallStateChangedHandler;
}
private void InstallStateChangedHandler(object? sender, InstallPackageCommand e)
{
if (!ApiInformation.IsApiContractPresent("Microsoft.Management.Deployment.WindowsPackageManagerContract", 12))
{
Logger.LogError($"RefreshPackageCatalogAsync isn't available");
e.FakeChangeStatus();
Command = e;
Icon = (IconInfo?)Command.Icon;
return;
}
_ = Task.Run(() =>
{
Stopwatch s = new();
Logger.LogDebug($"Starting RefreshPackageCatalogAsync");
s.Start();
var refs = WinGetStatics.AvailableCatalogs.ToArray();
foreach (var catalog in refs)
{
var operation = catalog.RefreshPackageCatalogAsync();
operation.Wait();
}
s.Stop();
Logger.LogDebug($"RefreshPackageCatalogAsync took {s.ElapsedMilliseconds}ms");
}).ContinueWith((previous) =>
{
if (previous.IsCompletedSuccessfully)
{
Logger.LogDebug($"Updating InstalledStatus");
UpdatedInstalledStatus();
}
});
}
}

View File

@@ -0,0 +1,271 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using ManagedCommon;
using Microsoft.CmdPal.Ext.WinGet.Pages;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Management.Deployment;
namespace Microsoft.CmdPal.Ext.WinGet;
internal sealed partial class WinGetExtensionPage : DynamicListPage, IDisposable
{
private static readonly CompositeFormat ErrorMessage = System.Text.CompositeFormat.Parse(Properties.Resources.winget_unexpected_error);
private readonly string _tag = string.Empty;
public bool HasTag => !string.IsNullOrEmpty(_tag);
private readonly Lock _resultsLock = new();
private CancellationTokenSource? _cancellationTokenSource;
private Task<IEnumerable<CatalogPackage>>? _currentSearchTask;
private IEnumerable<CatalogPackage>? _results;
public static IconInfo WinGetIcon { get; } = IconHelpers.FromRelativePath("Assets\\WinGet.svg");
public static IconInfo ExtensionsIcon { get; } = IconHelpers.FromRelativePath("Assets\\Extension.svg");
public static string ExtensionsTag => "windows-commandpalette-extension";
private readonly StatusMessage _errorMessage = new() { State = MessageState.Error };
public WinGetExtensionPage(string tag = "")
{
Icon = tag == ExtensionsTag ? ExtensionsIcon : WinGetIcon;
Name = Properties.Resources.winget_page_name;
_tag = tag;
ShowDetails = true;
}
public override IListItem[] GetItems()
{
IListItem[] items = [];
lock (_resultsLock)
{
// emptySearchForTag ===
// we don't have results yet, we haven't typed anything, and we're searching for a tag
bool emptySearchForTag = _results == null &&
string.IsNullOrEmpty(SearchText) &&
HasTag;
if (emptySearchForTag)
{
IsLoading = true;
DoUpdateSearchText(string.Empty);
return items;
}
if (_results != null && _results.Any())
{
ListItem[] results = _results.Select(PackageToListItem).ToArray();
IsLoading = false;
return results;
}
}
EmptyContent = new CommandItem(new NoOpCommand())
{
Icon = WinGetIcon,
Title = (string.IsNullOrEmpty(SearchText) && !HasTag) ?
Properties.Resources.winget_placeholder_text :
Properties.Resources.winget_no_packages_found,
};
IsLoading = false;
return items;
}
private static ListItem PackageToListItem(CatalogPackage p) => new InstallPackageListItem(p);
public override void UpdateSearchText(string oldSearch, string newSearch)
{
if (newSearch == oldSearch)
{
return;
}
DoUpdateSearchText(newSearch);
}
private void DoUpdateSearchText(string newSearch)
{
// Cancel any ongoing search
if (_cancellationTokenSource != null)
{
Logger.LogDebug("Cancelling old search", memberName: nameof(DoUpdateSearchText));
_cancellationTokenSource.Cancel();
}
_cancellationTokenSource = new CancellationTokenSource();
CancellationToken cancellationToken = _cancellationTokenSource.Token;
IsLoading = true;
// Save the latest search task
_currentSearchTask = DoSearchAsync(newSearch, cancellationToken);
// Await the task to ensure only the latest one gets processed
_ = ProcessSearchResultsAsync(_currentSearchTask, newSearch);
}
private async Task ProcessSearchResultsAsync(
Task<IEnumerable<CatalogPackage>> searchTask,
string newSearch)
{
try
{
IEnumerable<CatalogPackage> results = await searchTask;
// Ensure this is still the latest task
if (_currentSearchTask == searchTask)
{
// Process the results (e.g., update UI)
UpdateWithResults(results, newSearch);
}
}
catch (OperationCanceledException)
{
// Handle cancellation gracefully (e.g., log or ignore)
Logger.LogDebug($" Cancelled search for '{newSearch}'");
}
catch (Exception ex)
{
// Handle other exceptions
Logger.LogError(ex.Message);
}
}
private void UpdateWithResults(IEnumerable<CatalogPackage> results, string query)
{
Logger.LogDebug($"Completed search for '{query}'");
lock (_resultsLock)
{
this._results = results;
}
RaiseItemsChanged(this._results.Count());
}
private async Task<IEnumerable<CatalogPackage>> DoSearchAsync(string query, CancellationToken ct)
{
// Were we already canceled?
ct.ThrowIfCancellationRequested();
Stopwatch stopwatch = new();
stopwatch.Start();
if (string.IsNullOrEmpty(query)
&& string.IsNullOrEmpty(_tag))
{
return [];
}
string searchDebugText = $"{query}{(HasTag ? "+" : string.Empty)}{_tag}";
Logger.LogDebug($"Starting search for '{searchDebugText}'");
HashSet<CatalogPackage> results = new(new PackageIdCompare());
// Default selector: this is the way to do a `winget search <query>`
PackageMatchFilter selector = WinGetStatics.WinGetFactory.CreatePackageMatchFilter();
selector.Field = Microsoft.Management.Deployment.PackageMatchField.CatalogDefault;
selector.Value = query;
selector.Option = PackageFieldMatchOption.ContainsCaseInsensitive;
FindPackagesOptions opts = WinGetStatics.WinGetFactory.CreateFindPackagesOptions();
opts.Selectors.Add(selector);
// testing
opts.ResultLimit = 25;
// Selectors is "OR", Filters is "AND"
if (HasTag)
{
PackageMatchFilter tagFilter = WinGetStatics.WinGetFactory.CreatePackageMatchFilter();
tagFilter.Field = Microsoft.Management.Deployment.PackageMatchField.Tag;
tagFilter.Value = _tag;
tagFilter.Option = PackageFieldMatchOption.ContainsCaseInsensitive;
opts.Filters.Add(tagFilter);
}
// Clean up here, then...
ct.ThrowIfCancellationRequested();
Lazy<Task<PackageCatalog>> catalogTask = HasTag ? WinGetStatics.CompositeWingetCatalog : WinGetStatics.CompositeAllCatalog;
// Both these catalogs should have been instantiated by the
// WinGetStatics static ctor when we were created.
PackageCatalog catalog = await catalogTask.Value;
if (catalog == null)
{
// This error should have already been displayed by WinGetStatics
return [];
}
// foreach (var catalog in connections)
{
Logger.LogDebug($" Searching {catalog.Info.Name} ({query})", memberName: nameof(DoSearchAsync));
ct.ThrowIfCancellationRequested();
// BODGY, re: microsoft/winget-cli#5151
// FindPackagesAsync isn't actually async.
Task<FindPackagesResult> internalSearchTask = Task.Run(() => catalog.FindPackages(opts), ct);
FindPackagesResult searchResults = await internalSearchTask;
// TODO more error handling like this:
if (searchResults.Status != FindPackagesResultStatus.Ok)
{
_errorMessage.Message = string.Format(CultureInfo.CurrentCulture, ErrorMessage, searchResults.Status);
WinGetExtensionHost.Instance.ShowStatus(_errorMessage, StatusContext.Page);
return [];
}
Logger.LogDebug($" got results for ({query})", memberName: nameof(DoSearchAsync));
foreach (Management.Deployment.MatchResult? match in searchResults.Matches.ToArray())
{
ct.ThrowIfCancellationRequested();
// Print the packages
CatalogPackage package = match.CatalogPackage;
results.Add(package);
}
Logger.LogDebug($" ({searchDebugText}): count: {results.Count}", memberName: nameof(DoSearchAsync));
}
stopwatch.Stop();
Logger.LogDebug($"Search \"{searchDebugText}\" took {stopwatch.ElapsedMilliseconds}ms", memberName: nameof(DoSearchAsync));
return results;
}
public void Dispose() => throw new NotImplementedException();
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1402:File may only contain a single type", Justification = "I just like it")]
public sealed class PackageIdCompare : IEqualityComparer<CatalogPackage>
{
public bool Equals(CatalogPackage? x, CatalogPackage? y) =>
(x?.Id == y?.Id)
&& (x?.DefaultInstallVersion?.PackageCatalog == y?.DefaultInstallVersion?.PackageCatalog);
public int GetHashCode([DisallowNull] CatalogPackage obj) => obj.Id.GetHashCode();
}

View File

@@ -0,0 +1,351 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.CmdPal.Ext.WinGet.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CmdPal.Ext.WinGet.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
public static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Author.
/// </summary>
public static string winget_author {
get {
return ResourceManager.GetString("winget_author", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Copyright.
/// </summary>
public static string winget_copyright {
get {
return ResourceManager.GetString("winget_copyright", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Error {0}. Are you connected to the internet?.
/// </summary>
public static string winget_create_catalog_error {
get {
return ResourceManager.GetString("winget_create_catalog_error", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to WinGet.
/// </summary>
public static string winget_display_name {
get {
return ResourceManager.GetString("winget_display_name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Downloading. {0} of {1}.
/// </summary>
public static string winget_download_progress {
get {
return ResourceManager.GetString("winget_download_progress", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Downloading.
/// </summary>
public static string winget_downloading {
get {
return ResourceManager.GetString("winget_downloading", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Install Extensions.
/// </summary>
public static string winget_install_extensions_name {
get {
return ResourceManager.GetString("winget_install_extensions_name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Search for extensions on WinGet.
/// </summary>
public static string winget_install_extensions_subtitle {
get {
return ResourceManager.GetString("winget_install_extensions_subtitle", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Install Command Palette extensions.
/// </summary>
public static string winget_install_extensions_title {
get {
return ResourceManager.GetString("winget_install_extensions_title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Finished install.
/// </summary>
public static string winget_install_finished {
get {
return ResourceManager.GetString("winget_install_finished", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Install.
/// </summary>
public static string winget_install_name {
get {
return ResourceManager.GetString("winget_install_name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Finished install for {0}.
/// </summary>
public static string winget_install_package_finished {
get {
return ResourceManager.GetString("winget_install_package_finished", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Finishing install for {0}....
/// </summary>
public static string winget_install_package_finishing {
get {
return ResourceManager.GetString("winget_install_package_finishing", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Installing {0}....
/// </summary>
public static string winget_installing_package {
get {
return ResourceManager.GetString("winget_installing_package", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to License.
/// </summary>
public static string winget_license {
get {
return ResourceManager.GetString("winget_license", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to No packages found.
/// </summary>
public static string winget_no_packages_found {
get {
return ResourceManager.GetString("winget_no_packages_found", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Search WinGet.
/// </summary>
public static string winget_page_name {
get {
return ResourceManager.GetString("winget_page_name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Start typing to search for packages.
/// </summary>
public static string winget_placeholder_text {
get {
return ResourceManager.GetString("winget_placeholder_text", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Publisher.
/// </summary>
public static string winget_publisher {
get {
return ResourceManager.GetString("winget_publisher", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Publisher Support.
/// </summary>
public static string winget_publisher_support {
get {
return ResourceManager.GetString("winget_publisher_support", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Queued {0} for download....
/// </summary>
public static string winget_queued_package_download {
get {
return ResourceManager.GetString("winget_queued_package_download", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Queued {0} for uninstall....
/// </summary>
public static string winget_queued_package_uninstall {
get {
return ResourceManager.GetString("winget_queued_package_uninstall", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Release Notes.
/// </summary>
public static string winget_release_notes {
get {
return ResourceManager.GetString("winget_release_notes", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Search for extensions in the Store.
/// </summary>
public static string winget_search_store_title {
get {
return ResourceManager.GetString("winget_search_store_title", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unexpected error: {0}.
/// </summary>
public static string winget_unexpected_error {
get {
return ResourceManager.GetString("winget_unexpected_error", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Finished uninstall..
/// </summary>
public static string winget_uninstall_finished {
get {
return ResourceManager.GetString("winget_uninstall_finished", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Uninstall.
/// </summary>
public static string winget_uninstall_name {
get {
return ResourceManager.GetString("winget_uninstall_name", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Finished uninstall for {0}.
/// </summary>
public static string winget_uninstall_package_finished {
get {
return ResourceManager.GetString("winget_uninstall_package_finished", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Finishing uninstall for {0}....
/// </summary>
public static string winget_uninstall_package_finishing {
get {
return ResourceManager.GetString("winget_uninstall_package_finishing", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Uninstalling {0}....
/// </summary>
public static string winget_uninstalling_package {
get {
return ResourceManager.GetString("winget_uninstalling_package", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to View online.
/// </summary>
public static string winget_view_online {
get {
return ResourceManager.GetString("winget_view_online", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to View Release Notes.
/// </summary>
public static string winget_view_release_notes {
get {
return ResourceManager.GetString("winget_view_release_notes", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,250 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="winget_display_name" xml:space="preserve">
<value>WinGet</value>
<comment></comment>
</data>
<data name="winget_install_extensions_name" xml:space="preserve">
<value>Install Extensions</value>
<comment></comment>
</data>
<data name="winget_install_extensions_title" xml:space="preserve">
<value>Install Command Palette extensions</value>
<comment></comment>
</data>
<data name="winget_install_extensions_subtitle" xml:space="preserve">
<value>Search for extensions on WinGet</value>
<comment></comment>
</data>
<data name="winget_search_store_title" xml:space="preserve">
<value>Search for extensions in the Store</value>
<comment></comment>
</data>
<data name="winget_page_name" xml:space="preserve">
<value>Search WinGet</value>
<comment></comment>
</data>
<data name="winget_create_catalog_error" xml:space="preserve">
<value>Error {0}. Are you connected to the internet?</value>
<comment></comment>
</data>
<data name="winget_uninstall_name" xml:space="preserve">
<value>Uninstall</value>
<comment></comment>
</data>
<data name="winget_install_name" xml:space="preserve">
<value>Install</value>
<comment></comment>
</data>
<data name="winget_uninstalling_package" xml:space="preserve">
<value>Uninstalling {0}...</value>
<comment>{0} will be replaced by the name of an app package</comment>
</data>
<data name="winget_installing_package" xml:space="preserve">
<value>Installing {0}...</value>
<comment>{0} will be replaced by the name of an app package</comment>
</data>
<data name="winget_install_package_finished" xml:space="preserve">
<value>Finished install for {0}</value>
<comment>{0} will be replaced by the name of an app package</comment>
</data>
<data name="winget_uninstall_package_finished" xml:space="preserve">
<value>Finished uninstall for {0}</value>
<comment>{0} will be replaced by the name of an app package</comment>
</data>
<data name="winget_downloading" xml:space="preserve">
<value>Downloading</value>
<comment></comment>
</data>
<data name="winget_queued_package_download" xml:space="preserve">
<value>Queued {0} for download...</value>
<comment>{0} will be replaced by the name of an app package</comment>
</data>
<data name="winget_download_progress" xml:space="preserve">
<value>Downloading. {0} of {1}</value>
<comment>{0} will be replaced with a number of bytes downloaded, and {1} will be replaced with the total number to download</comment>
</data>
<data name="winget_install_package_finishing" xml:space="preserve">
<value>Finishing install for {0}...</value>
<comment>{0} will be replaced by the name of an app package</comment>
</data>
<data name="winget_install_finished" xml:space="preserve">
<value>Finished install</value>
<comment></comment>
</data>
<data name="winget_queued_package_uninstall" xml:space="preserve">
<value>Queued {0} for uninstall...</value>
<comment>{0} will be replaced by the name of an app package</comment>
</data>
<data name="winget_uninstall_package_finishing" xml:space="preserve">
<value>Finishing uninstall for {0}...</value>
<comment>{0} will be replaced by the name of an app package</comment>
</data>
<data name="winget_uninstall_finished" xml:space="preserve">
<value>Finished uninstall.</value>
<comment></comment>
</data>
<data name="winget_author" xml:space="preserve">
<value>Author</value>
<comment></comment>
</data>
<data name="winget_publisher" xml:space="preserve">
<value>Publisher</value>
<comment></comment>
</data>
<data name="winget_copyright" xml:space="preserve">
<value>Copyright</value>
<comment></comment>
</data>
<data name="winget_license" xml:space="preserve">
<value>License</value>
<comment></comment>
</data>
<data name="winget_release_notes" xml:space="preserve">
<value>Release Notes</value>
<comment></comment>
</data>
<data name="winget_view_release_notes" xml:space="preserve">
<value>View Release Notes</value>
<comment></comment>
</data>
<data name="winget_view_online" xml:space="preserve">
<value>View online</value>
<comment></comment>
</data>
<data name="winget_publisher_support" xml:space="preserve">
<value>Publisher Support</value>
<comment></comment>
</data>
<data name="winget_placeholder_text" xml:space="preserve">
<value>Start typing to search for packages</value>
<comment></comment>
</data>
<data name="winget_no_packages_found" xml:space="preserve">
<value>No packages found</value>
<comment></comment>
</data>
<data name="winget_unexpected_error" xml:space="preserve">
<value>Unexpected error: {0}</value>
<comment>{0} will be replaced by an error code</comment>
</data>
</root>

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 System;
using System.IO;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.WinGet;
public partial class WinGetExtensionCommandsProvider : CommandProvider
{
public WinGetExtensionCommandsProvider()
{
DisplayName = Properties.Resources.winget_display_name;
Id = "WinGet";
Icon = WinGetExtensionPage.WinGetIcon;
_ = WinGetStatics.Manager;
}
private readonly ICommandItem[] _commands = [
new ListItem(new WinGetExtensionPage()),
new ListItem(
new WinGetExtensionPage(WinGetExtensionPage.ExtensionsTag) { Title = Properties.Resources.winget_install_extensions_title })
{
Title = Properties.Resources.winget_install_extensions_title,
Subtitle = Properties.Resources.winget_install_extensions_subtitle,
},
new ListItem(
new OpenUrlCommand("ms-windows-store://assoc/?Tags=AppExtension-com.microsoft.commandpalette"))
{
Title = Properties.Resources.winget_search_store_title,
Icon = IconHelpers.FromRelativePaths("Assets\\Store.light.svg", "Assets\\Store.dark.svg"),
},
];
public override ICommandItem[] TopLevelCommands() => _commands;
public override void InitializeWithHost(IExtensionHost host) => WinGetExtensionHost.Instance.Initialize(host);
public void SetAllLookup(Func<string, ICommandItem?> callback) => WinGetStatics.AppSearchCallback = callback;
}

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 Microsoft.CmdPal.Common;
namespace Microsoft.CmdPal.Ext.WinGet;
public partial class WinGetExtensionHost
{
internal static ExtensionHostInstance Instance { get; } = new();
}

View File

@@ -0,0 +1,114 @@
// 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.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CommandPalette.Extensions;
using Microsoft.CommandPalette.Extensions.Toolkit;
using Microsoft.Management.Deployment;
using Windows.Foundation.Metadata;
using WindowsPackageManager.Interop;
namespace Microsoft.CmdPal.Ext.WinGet;
internal static class WinGetStatics
{
public static WindowsPackageManagerStandardFactory WinGetFactory { get; private set; }
public static PackageManager Manager { get; private set; }
public static IReadOnlyList<PackageCatalogReference> AvailableCatalogs { get; private set; }
private static readonly PackageCatalogReference _wingetCatalog;
private static readonly PackageCatalogReference _storeCatalog;
public static Lazy<Task<PackageCatalog>> CompositeAllCatalog { get; } = new(() => GetCompositeCatalog(true));
public static Lazy<Task<PackageCatalog>> CompositeWingetCatalog { get; } = new(() => GetCompositeCatalog(false));
private static readonly StatusMessage _errorMessage = new() { State = MessageState.Error };
public static Func<string, ICommandItem?>? AppSearchCallback { get; set; }
private static readonly CompositeFormat CreateCatalogErrorMessage = System.Text.CompositeFormat.Parse(Properties.Resources.winget_create_catalog_error);
static WinGetStatics()
{
WinGetFactory = new WindowsPackageManagerStandardFactory();
// Create Package Manager and get available catalogs
Manager = WinGetFactory.CreatePackageManager();
_wingetCatalog = Manager.GetPredefinedPackageCatalog(PredefinedPackageCatalog.OpenWindowsCatalog);
_storeCatalog = Manager.GetPredefinedPackageCatalog(PredefinedPackageCatalog.MicrosoftStore);
AvailableCatalogs = [
_wingetCatalog,
_storeCatalog,
];
if (ApiInformation.IsApiContractPresent("Microsoft.Management.Deployment.WindowsPackageManagerContract", 8))
{
foreach (var catalogReference in AvailableCatalogs)
{
catalogReference.PackageCatalogBackgroundUpdateInterval = new(0);
}
}
// Immediately start the lazy-init of the all packages catalog, but
// leave the winget one to be initialized as needed
_ = Task.Run(() =>
{
_ = CompositeAllCatalog.Value;
// _ = CompositeWingetCatalog.Value;
});
}
internal static async Task<PackageCatalog> GetCompositeCatalog(bool all)
{
Stopwatch stopwatch = new();
Debug.WriteLine($"Starting GetCompositeCatalog({all}) fetch");
stopwatch.Start();
// Create the composite catalog
var createCompositePackageCatalogOptions = WinGetFactory.CreateCreateCompositePackageCatalogOptions();
if (all)
{
// Add winget and the store to this catalog
foreach (var catalogReference in AvailableCatalogs.ToArray())
{
createCompositePackageCatalogOptions.Catalogs.Add(catalogReference);
}
}
else
{
createCompositePackageCatalogOptions.Catalogs.Add(_wingetCatalog);
}
// Searches only the catalogs provided, but will correlated with installed items
createCompositePackageCatalogOptions.CompositeSearchBehavior = CompositeSearchBehavior.RemotePackagesFromAllCatalogs;
var catalogRef = WinGetStatics.Manager.CreateCompositePackageCatalog(createCompositePackageCatalogOptions);
var connectResult = await catalogRef.ConnectAsync();
var compositeCatalog = connectResult.PackageCatalog;
stopwatch.Stop();
Debug.WriteLine($"GetCompositeCatalog({all}) fetch took {stopwatch.ElapsedMilliseconds}ms");
if (connectResult.Status == ConnectResultStatus.CatalogError)
{
_errorMessage.Message = string.Format(CultureInfo.CurrentCulture, CreateCatalogErrorMessage, connectResult.ExtendedErrorCode.HResult);
WinGetExtensionHost.Instance.ShowStatus(_errorMessage, StatusContext.Extension);
}
return compositeCatalog;
}
}

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.Collections.Generic;
using WindowsPackageManager.Interop;
namespace Microsoft.CmdPal.Ext.WinGet.WindowsPackageManager.Interop;
#nullable disable
internal sealed class ClassModel
{
/// <summary>
/// Gets the interface for the projected class type generated by CsWinRT
/// </summary>
public Type InterfaceType { get; init; }
/// <summary>
/// Gets the projected class type generated by CsWinRT
/// </summary>
public Type ProjectedClassType { get; init; }
/// <summary>
/// Gets the Clsids for each context (e.g. OutOfProcProd, OutOfProcDev)
/// </summary>
public IReadOnlyDictionary<ClsidContext, Guid> Clsids { get; init; }
/// <summary>
/// Get CLSID based on the provided context
/// </summary>
/// <param name="context">Context</param>
/// <returns>CLSID for the provided context.</returns>
/// <exception cref="InvalidOperationException">Throw an exception if the clsid context is not available for the current instance.</exception>
public Guid GetClsid(ClsidContext context)
{
return !Clsids.TryGetValue(context, out var clsid)
? throw new InvalidOperationException($"{ProjectedClassType.FullName} is not implemented in context {context}")
: clsid;
}
/// <summary>
/// Get IID corresponding to the COM object
/// </summary>
/// <returns>IID.</returns>
public Guid GetIid() => InterfaceType.GUID;
}

View File

@@ -0,0 +1,120 @@
// 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 Microsoft.CmdPal.Ext.WinGet.WindowsPackageManager.Interop;
using Microsoft.Management.Deployment;
namespace WindowsPackageManager.Interop;
internal static class ClassesDefinition
{
private static Dictionary<Type, ClassModel> Classes { get; } = new()
{
[typeof(PackageManager)] = new()
{
ProjectedClassType = typeof(PackageManager),
InterfaceType = typeof(IPackageManager),
Clsids = new Dictionary<ClsidContext, Guid>()
{
[ClsidContext.Prod] = new Guid("C53A4F16-787E-42A4-B304-29EFFB4BF597"),
[ClsidContext.Dev] = new Guid("74CB3139-B7C5-4B9E-9388-E6616DEA288C"),
},
},
[typeof(FindPackagesOptions)] = new()
{
ProjectedClassType = typeof(FindPackagesOptions),
InterfaceType = typeof(IFindPackagesOptions),
Clsids = new Dictionary<ClsidContext, Guid>()
{
[ClsidContext.Prod] = new Guid("572DED96-9C60-4526-8F92-EE7D91D38C1A"),
[ClsidContext.Dev] = new Guid("1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96"),
},
},
[typeof(CreateCompositePackageCatalogOptions)] = new()
{
ProjectedClassType = typeof(CreateCompositePackageCatalogOptions),
InterfaceType = typeof(ICreateCompositePackageCatalogOptions),
Clsids = new Dictionary<ClsidContext, Guid>()
{
[ClsidContext.Prod] = new Guid("526534B8-7E46-47C8-8416-B1685C327D37"),
[ClsidContext.Dev] = new Guid("EE160901-B317-4EA7-9CC6-5355C6D7D8A7"),
},
},
[typeof(InstallOptions)] = new()
{
ProjectedClassType = typeof(InstallOptions),
InterfaceType = typeof(IInstallOptions),
Clsids = new Dictionary<ClsidContext, Guid>()
{
[ClsidContext.Prod] = new Guid("1095F097-EB96-453B-B4E6-1613637F3B14"),
[ClsidContext.Dev] = new Guid("44FE0580-62F7-44D4-9E91-AA9614AB3E86"),
},
},
[typeof(UninstallOptions)] = new()
{
ProjectedClassType = typeof(UninstallOptions),
InterfaceType = typeof(IUninstallOptions),
Clsids = new Dictionary<ClsidContext, Guid>()
{
[ClsidContext.Prod] = new Guid("E1D9A11E-9F85-4D87-9C17-2B93143ADB8D"),
[ClsidContext.Dev] = new Guid("AA2A5C04-1AD9-46C4-B74F-6B334AD7EB8C"),
},
},
[typeof(PackageMatchFilter)] = new()
{
ProjectedClassType = typeof(PackageMatchFilter),
InterfaceType = typeof(IPackageMatchFilter),
Clsids = new Dictionary<ClsidContext, Guid>()
{
[ClsidContext.Prod] = new Guid("D02C9DAF-99DC-429C-B503-4E504E4AB000"),
[ClsidContext.Dev] = new Guid("3F85B9F4-487A-4C48-9035-2903F8A6D9E8"),
},
},
};
/// <summary>
/// Get CLSID based on the provided context for the specified type
/// </summary>
/// <typeparam name="T">Projected class type</typeparam>
/// <param name="context">Context</param>
/// <returns>CLSID for the provided context and type, or throw an exception if not found.</returns>
/// <exception cref="InvalidOperationException">Throws an exception if type is not a project class.</exception>
public static Guid GetClsid<T>(ClsidContext context)
{
ValidateType<T>();
return Classes[typeof(T)].GetClsid(context);
}
/// <summary>
/// Get IID corresponding to the COM object
/// </summary>
/// <typeparam name="T">Projected class type</typeparam>
/// <returns>IID or throw an exception if not found.</returns>
/// <exception cref="InvalidOperationException">Throws an exception if type is not a project class.</exception>
public static Guid GetIid<T>()
{
ValidateType<T>();
return Classes[typeof(T)].GetIid();
}
/// <summary>
/// Validate that the provided type is defined.
/// </summary>
/// <param name="type">Projected class type</param>
/// <exception cref="InvalidOperationException">Throws an exception if type is not a project class.</exception>
private static void ValidateType<TType>()
{
if (!Classes.ContainsKey(typeof(TType)))
{
throw new InvalidOperationException($"{typeof(TType).Name} is not a projected class type.");
}
}
}

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 WindowsPackageManager.Interop;
public enum ClsidContext
{
// Production CLSID Guids
Prod,
// Development CLSID Guids
Dev,
}

View File

@@ -0,0 +1,59 @@
// 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.Management.Deployment;
namespace WindowsPackageManager.Interop;
/// <summary>
/// Factory class for creating WinGet COM objects.
/// Details about each method can be found in the source IDL:
/// https://github.com/microsoft/winget-cli/blob/master/src/Microsoft.Management.Deployment/PackageManager.idl
/// </summary>
public abstract class WindowsPackageManagerFactory
{
private readonly ClsidContext _clsidContext;
public WindowsPackageManagerFactory(ClsidContext clsidContext)
{
_clsidContext = clsidContext;
}
/// <summary>
/// Creates an instance of the class <typeparamref name="T"/>.
/// </summary>
/// <remarks>
/// Type <typeparamref name="T"/> must be one of the types defined in the winget COM API.
/// Implementations of this method can assume that <paramref name="clsid"/> and <paramref name="iid"/>
/// are the right GUIDs for the class in the given context.
/// </remarks>
protected abstract T CreateInstance<T>(Guid clsid, Guid iid);
public PackageManager CreatePackageManager() => CreateInstance<PackageManager>();
public FindPackagesOptions CreateFindPackagesOptions() => CreateInstance<FindPackagesOptions>();
public CreateCompositePackageCatalogOptions CreateCreateCompositePackageCatalogOptions() => CreateInstance<CreateCompositePackageCatalogOptions>();
public InstallOptions CreateInstallOptions() => CreateInstance<InstallOptions>();
public UninstallOptions CreateUninstallOptions() => CreateInstance<UninstallOptions>();
public PackageMatchFilter CreatePackageMatchFilter() => CreateInstance<PackageMatchFilter>();
/// <summary>
/// Creates an instance of the class <typeparamref name="T"/>.
/// </summary>
/// <remarks>
/// This is a helper for calling the derived class's <see cref="CreateInstance{T}(Guid, Guid)"/>
/// method with the appropriate GUIDs.
/// </remarks>
private T CreateInstance<T>()
{
var clsid = ClassesDefinition.GetClsid<T>(_clsidContext);
var iid = ClassesDefinition.GetIid<T>();
return CreateInstance<T>(clsid, iid);
}
}

View File

@@ -0,0 +1,40 @@
// 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 Windows.Win32;
using Windows.Win32.System.Com;
using WinRT;
namespace WindowsPackageManager.Interop;
public class WindowsPackageManagerStandardFactory : WindowsPackageManagerFactory
{
public WindowsPackageManagerStandardFactory(ClsidContext clsidContext = ClsidContext.Prod)
: base(clsidContext)
{
}
protected override T CreateInstance<T>(Guid clsid, Guid iid)
{
var pUnknown = IntPtr.Zero;
try
{
var hr = PInvoke.CoCreateInstance(clsid, null, CLSCTX.CLSCTX_ALL, iid, out var result);
Marshal.ThrowExceptionForHR(hr);
pUnknown = Marshal.GetIUnknownForObject(result);
return MarshalGeneric<T>.FromAbi(pUnknown);
}
finally
{
// CoCreateInstance and FromAbi both AddRef on the native object.
// Release once to prevent memory leak.
if (pUnknown != IntPtr.Zero)
{
Marshal.Release(pUnknown);
}
}
}
}

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>