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
|
After Width: | Height: | Size: 3.4 KiB |
@@ -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 |
|
After Width: | Height: | Size: 2.3 KiB |
@@ -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 |
|
After Width: | Height: | Size: 3.5 KiB |
@@ -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 |
|
After Width: | Height: | Size: 1.3 KiB |
@@ -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 |
@@ -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>
|
||||
@@ -0,0 +1,6 @@
|
||||
CoCreateInstance
|
||||
CoMarshalInterface
|
||||
CoUnmarshalInterface
|
||||
CreateStreamOnHGlobal
|
||||
MSHCTX
|
||||
MSHLFLAGS
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
351
src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WinGet/Properties/Resources.Designer.cs
generated
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||