From ba2ef2341497842a78c0b12244d58c00c5bdc8af Mon Sep 17 00:00:00 2001 From: Roy Date: Tue, 11 Aug 2020 00:53:43 +0200 Subject: [PATCH] [PowerToys Run] Add Support for Uris (#5160) * url handler plugin * updates * Add seperate interface classes rename to uri module * Update path * Update implementation to remove slow DNS lookup ( and let browser handle it) * tabs to spaces * - Update icon/assets - Finalize Project * Update wix project * Implement UpdateBrowserIconPath * Implemented Microsoft.CodeAnalysis.FxCopAnalyzers * Add Language component to installer * Update logic to determine icon * Update Translation File to "Open in browser" * Added test for typing http://test.com and which result to expect on each keystoke * Implement StyleCop * Added ipv6 tests * Fix Solution LineBreaks * Added Microsoft.Plugin.Uri as build Dependency * Use ArgumentNullException instead of InvalidOperationException * Fix wrong Directory in wix installer Co-authored-by: Roy --- PowerToys.sln | 15 ++ installer/PowerToysSetup/Product.wxs | 35 ++++ .../Microsoft.Plugin.Uri.UnitTests.csproj | 21 ++ .../UriHelper/ExtendedUriParserTests.cs | 53 ++++++ .../Microsoft.Plugin.Uri/Images/uri.dark.png | Bin 0 -> 2580 bytes .../Microsoft.Plugin.Uri/Images/uri.light.png | Bin 0 -> 2541 bytes .../Interfaces/IRegistryWrapper.cs | 11 ++ .../Interfaces/IUriParser.cs | 11 ++ .../Interfaces/IUrlResolver.cs | 11 ++ .../Microsoft.Plugin.Uri/Languages/de.xaml | 5 + .../Microsoft.Plugin.Uri/Languages/en.xaml | 5 + .../Microsoft.Plugin.Uri/Languages/ja.xaml | 5 + .../Microsoft.Plugin.Uri/Languages/pl.xaml | 5 + .../Microsoft.Plugin.Uri/Languages/tr.xaml | 5 + .../Microsoft.Plugin.Uri/Languages/zh-cn.xaml | 5 + .../Microsoft.Plugin.Uri/Languages/zh-tw.xaml | 5 + .../Plugins/Microsoft.Plugin.Uri/Main.cs | 179 ++++++++++++++++++ .../Microsoft.Plugin.Uri.csproj | 133 +++++++++++++ .../Microsoft.Plugin.Uri/NativeMethods.cs | 21 ++ .../Microsoft.Plugin.Uri/RegisteryWrapper.cs | 17 ++ .../UriHelper/ExtendedUriParser.cs | 43 +++++ .../UriHelper/UriResolver.cs | 16 ++ .../Microsoft.Plugin.Uri/UriSettings.cs | 11 ++ .../Plugins/Microsoft.Plugin.Uri/plugin.json | 12 ++ 24 files changed, 624 insertions(+) create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri.UnitTests/Microsoft.Plugin.Uri.UnitTests.csproj create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri.UnitTests/UriHelper/ExtendedUriParserTests.cs create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Images/uri.dark.png create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Images/uri.light.png create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IRegistryWrapper.cs create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IUriParser.cs create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IUrlResolver.cs create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/de.xaml create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/en.xaml create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/ja.xaml create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/pl.xaml create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/tr.xaml create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/zh-cn.xaml create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/zh-tw.xaml create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Main.cs create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Microsoft.Plugin.Uri.csproj create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri/NativeMethods.cs create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri/RegisteryWrapper.cs create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/ExtendedUriParser.cs create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/UriResolver.cs create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriSettings.cs create mode 100644 src/modules/launcher/Plugins/Microsoft.Plugin.Uri/plugin.json diff --git a/PowerToys.sln b/PowerToys.sln index ca9bb2e58c..db9b22afda 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -189,6 +189,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Launcher", "src\m EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerLauncher", "src\modules\launcher\PowerLauncher\PowerLauncher.csproj", "{F97E5003-F263-4D4A-A964-0F1F3C82DEF2}" ProjectSection(ProjectDependencies) = postProject + {03276A39-D4E9-417C-8FFD-200B0EE5E871} = {03276A39-D4E9-417C-8FFD-200B0EE5E871} {FDB3555B-58EF-4AE6-B5F1-904719637AB4} = {FDB3555B-58EF-4AE6-B5F1-904719637AB4} {59BD9891-3837-438A-958D-ADC7F91F6F7E} = {59BD9891-3837-438A-958D-ADC7F91F6F7E} {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} = {C21BFF9C-2C99-4B5F-B7C9-A5E6DDDB37B0} @@ -263,6 +264,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ColorPickerUI", "src\module EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "colorpicker", "colorpicker", "{1D78B84B-CA39-406C-98F4-71F7EC266CC0}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.Uri", "src\modules\launcher\Plugins\Microsoft.Plugin.Uri\Microsoft.Plugin.Uri.csproj", "{03276A39-D4E9-417C-8FFD-200B0EE5E871}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Plugin.Uri.UnitTests", "src\modules\launcher\Plugins\Microsoft.Plugin.Uri.UnitTests\Microsoft.Plugin.Uri.UnitTests.csproj", "{B81FB7B6-D30E-428F-908A-41422EFC1172}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -521,6 +526,14 @@ Global {BA58206B-1493-4C75-BFEA-A85768A1E156}.Debug|x64.Build.0 = Debug|x64 {BA58206B-1493-4C75-BFEA-A85768A1E156}.Release|x64.ActiveCfg = Release|x64 {BA58206B-1493-4C75-BFEA-A85768A1E156}.Release|x64.Build.0 = Release|x64 + {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Debug|x64.ActiveCfg = Debug|x64 + {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Debug|x64.Build.0 = Debug|x64 + {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Release|x64.ActiveCfg = Release|x64 + {03276A39-D4E9-417C-8FFD-200B0EE5E871}.Release|x64.Build.0 = Release|x64 + {B81FB7B6-D30E-428F-908A-41422EFC1172}.Debug|x64.ActiveCfg = Debug|x64 + {B81FB7B6-D30E-428F-908A-41422EFC1172}.Debug|x64.Build.0 = Debug|x64 + {B81FB7B6-D30E-428F-908A-41422EFC1172}.Release|x64.ActiveCfg = Release|x64 + {B81FB7B6-D30E-428F-908A-41422EFC1172}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -595,6 +608,8 @@ Global {655C9AF2-18D3-4DA6-80E4-85504A7722BA} = {1D78B84B-CA39-406C-98F4-71F7EC266CC0} {BA58206B-1493-4C75-BFEA-A85768A1E156} = {1D78B84B-CA39-406C-98F4-71F7EC266CC0} {1D78B84B-CA39-406C-98F4-71F7EC266CC0} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} + {03276A39-D4E9-417C-8FFD-200B0EE5E871} = {4AFC9975-2456-4C70-94A4-84073C1CED93} + {B81FB7B6-D30E-428F-908A-41422EFC1172} = {4AFC9975-2456-4C70-94A4-84073C1CED93} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C3A2F9D1-7930-4EF4-A6FC-7EE0A99821D0} diff --git a/installer/PowerToysSetup/Product.wxs b/installer/PowerToysSetup/Product.wxs index 60ae35fb10..8dcbca9bc9 100644 --- a/installer/PowerToysSetup/Product.wxs +++ b/installer/PowerToysSetup/Product.wxs @@ -275,6 +275,11 @@ + + + + + @@ -1013,6 +1018,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri.UnitTests/Microsoft.Plugin.Uri.UnitTests.csproj b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri.UnitTests/Microsoft.Plugin.Uri.UnitTests.csproj new file mode 100644 index 0000000000..288c310638 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri.UnitTests/Microsoft.Plugin.Uri.UnitTests.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp3.1 + + false + x64 + Microsoft.Plugin.Uri.UnitTests + + + + + + + + + + + + + diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri.UnitTests/UriHelper/ExtendedUriParserTests.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri.UnitTests/UriHelper/ExtendedUriParserTests.cs new file mode 100644 index 0000000000..eea81a4c39 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri.UnitTests/UriHelper/ExtendedUriParserTests.cs @@ -0,0 +1,53 @@ +using Microsoft.Plugin.Uri.UriHelper; +using Microsoft.VisualStudio.TestPlatform.ObjectModel; +using NUnit.Framework; + +namespace Microsoft.Plugin.Uri.UnitTests.UriHelper +{ + [TestFixture] + public class ExtendedUriParserTests + { + [TestCase("google.com", true, "http://google.com/")] + [TestCase("localhost", true, "http://localhost/")] + [TestCase("127.0.0.1", true, "http://127.0.0.1/")] + [TestCase("127.0.0.1:80", true, "http://127.0.0.1/")] + [TestCase("127", true, "http://0.0.0.127/")] + [TestCase("", false, null)] + [TestCase("https://google.com", true, "https://google.com/")] + [TestCase("ftps://google.com", true, "ftps://google.com/")] + [TestCase(null, false, null)] + [TestCase("bing.com/search?q=gmx", true, "http://bing.com/search?q=gmx")] + [TestCase("h", true, "http://h/")] + [TestCase("ht", true, "http://ht/")] + [TestCase("htt", true, "http://htt/")] + [TestCase("http", true, "http://http/")] + [TestCase("http:", false, null)] + [TestCase("http:/", false, null)] + [TestCase("http://", false, null)] + [TestCase("http://t", true, "http://t/")] + [TestCase("http://te", true, "http://te/")] + [TestCase("http://tes", true, "http://tes/")] + [TestCase("http://test", true, "http://test/")] + [TestCase("http://test.", false, null)] + [TestCase("http://test.c", true, "http://test.c/")] + [TestCase("http://test.co", true, "http://test.co/")] + [TestCase("http://test.com", true, "http://test.com/")] + [TestCase("http:3", true,"http://http:3/")] + [TestCase("[::]", true, "http://[::]")] + [TestCase("[2001:0DB8::1]", true, "http://[2001:0DB8::1]/")] + [TestCase("[2001:0DB8::1]:80",true, "http://[2001:0DB8::1]/")] + public void TryParse_CanParseHostName(string query, bool expectedSuccess, string expectedResult) + { + // Arrange + var parser = new ExtendedUriParser(); + + // Act + var success = parser.TryParse(query, out var result); + + // Assert + Assert.AreEqual(expectedResult, result?.ToString()); + Assert.AreEqual(expectedSuccess, success); + } + } +} + diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Images/uri.dark.png b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Images/uri.dark.png new file mode 100644 index 0000000000000000000000000000000000000000..6c34ed1582e7aaddffda88aeab26666ee9cdb22e GIT binary patch literal 2580 zcmcIm32+lt7~ZyW$PtQ&NEyauTZePAyE!)5wSktTr5#M^lt>EW2%Fuv$x4!qyIYc$ z0_73}JX*?S$HAGxAcH8NA|r=IJ1U9_qlkbwzzhXpC^r;2^lg$JARyz6nQV6Vz3+Yh z_x}HT@8z}PqA@+YWpqOjq-SA)s|4Oj{p#8o{(4hyEP%JiBLx#x1Ti$~SIV;Ch5-oD zB}ejc>d)q&;e0nt)- zG3t~RfYL@1=Sk9p+8856TP!p=0yU8)k|3=FWx^?%B`qvvLz5p2MpFclEpg>1W5G8E z7SOZ^OAyg$)EG4zWyMcW48stli7=UPh``nIu*Svku$tM*-~uYIND)nv!>G>4`Q$Rq zfkCB-DTE?zv|%-wCYUfH#zhFqNa|B+1-ji2hlWCJXjRLb2BReP-j`VQlt%zj0#vz7 z;X&Rs5Y{qVgGB9{U9-X-&8dM5Po3EK)X4>{Vh$%>*?G+XUv+`>~7E?6yQ zoMR|}n?;7Skrc_8eTi8@wsDFCR}L5K$f~OnAfv$9MczhRaFMhDT;wba&hb10UG9VG zXp_%ELRIZ?opMlC+_C_(Ywp-y=yVn%hgvdbG+LPFq$!4`ag&k4P=RMfSqX8_ zXi|vt10oXk>$Zn7puhr2g*7fuE~N3`mE==ULgVgaIbL5B4vg2`00b=AEZrx~k2awM zZ8QKN=|fzSfT^;mML7lJ`e8}_)rG`^#H-x2hf6nG>~Y(6g64Q(VV}%tnIisricqc(>HIN-Elp{dvuyakDkYP=mZhbPH|VL zIHbcd&^ij9E&5~Ip$1>tjztiLl~UlL*Y#4_3Is{5D0Jm|0<+JRo~y1S(`VG}`D@6) zmSJ57&U@lC+ME^bOL}J$eP1lueW>qB!!|=?SnT@T1EuM+-bYr&UbTHWW#WdT*FRqT zR(j1gf92bC4W%Peo4&%Pgp;?*tr?5Hd0{|}A;*BGUD$bWXIgQ}uZzCfeeApZC2Rip z>;#{uIG>}lyh|@+WLB-ooY=YX<PQR?J=2@_5aFtNX9yW%YUYo@rEgN#>&hV7zo1=D?9_(G6 zGI^x2lX~(*|KW8rry_<1Z}*{>#iDm6 z2{XgMGSR$seWV!gO%Pfi!Cq&oYUeeV2) z9{$hHgRr#e`PpPwPZpDI`)elXSZ*8d cX!jOm^2De3=1Omi{=cm-ugLYm=qYpl25cjcQUCw| literal 0 HcmV?d00001 diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Images/uri.light.png b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Images/uri.light.png new file mode 100644 index 0000000000000000000000000000000000000000..bea54d005c0bf182cf81c3f6663c6d8e46fc48b2 GIT binary patch literal 2541 zcmcIm4Qvxt9B(UO4A`6r5rRT4hsfvkK6<^jhn0;MSaBV5>)ezeyS{sE58AtWcVpcU z?f8&@sC;bE7+fZxM8anb#;7>_0L+w6rxE2#6x0A?f`Rw}^IhA0fPlorrq}oGz2E!& zfB%p7UYC{>PtEB+w7XrT?9w;%ZASyvGL=c0zfF)v4S>qFKJjKhSzA;SVSPG)gapMJsL9EhW zf_W4bVw{yC07WsF!)2v8mgT7N7(+1>N!dx7A!v@LSe|xZnGcSvsbYvP^-jsyLT_$7 zqU&*q3H%wGmwmUZ??8itCahV+NxTRH}71 zj+CZdh{d~UWi1mX6fiOWvp87_4J{r=lSW3g_urca)WtYq}wlvoSY#vxe>HKkew zaPnLz>m$01#UqNYXc6TW+PbH&Qz(LNgYjxLnhK8yNEn4N#H}GLozfTQJt`D*MGYuQ zv@201T^2D0;jqyak}N7U+89GoW&!jHI&|Yk)(DD2-vDjrDUN4s<0yuws5I1%>Od59 zp*Pq;Fs=a2@)XU}>~&y8ltQ(40`>cOpRDPE44}{J#*rwiB#Ast*(jHtrU?Pi6aigq z5cLE=*j+Zt5p)PnI~PppLhHQ>s5T;DtWTG%r~t ZjBvHm5*~fDmYwB5Z&a2q)(R zgv-t_HV)c34uUSW>8gZwju7q5YN!$sqbN8-z`?OZh;l$8B(N@|3b;^}2ZNy?#{^l5 z!?7N_9wn-%enmvlwe=qNc|0Ym5|W~5LMxp*3G+?%aI}l#2*yg|mCnHx7(yfFhn5mTnT}J98*aTN8nZ z^fpzJhG|MjuMt#O5JoBa*D54$C0-Ne-d?(mbvlC}h>{pWXM~etX(H%A5p=lhPL2yg z!4YzFx&FUM&x9331UU@R2}|Pt-I+Tp_kZT$rW^21^T6~z4bqElaE3Gv)^vrDHx^${ zl?Px#FA{2@s%4TnNeK6(wFj5N6bms_^zEDLd+&$Tj6sv0G2)mqohBlCD6TV=g7mru zx>ljP#prflYUri=T7)vnl!`9BiNBR}noO25pSK_osXzb9h1hpg-tK3|Ufg-E@BTig z`+n8$+velz_xCAyu#hh}oE@EB6+0v&Y7v*0n{BZAt*Amv{ z^ik&Feg3M^_nI|u59P=-?WH--uPvm@^0Ol+XRz;N&92H?V=Gt@V~c9YgXaOlxV_esD{}c2LzhN#0Sh%UW+&nbW@%_QU z)^o2`e%fFO#OuzQe;oF8g4{T#Ut;&(3zhK;ht9THrX7h)z-yOm-ZZm#%&yqlQF+bT zx#9li@O?#n1~>7I75mHAuKMY~A!*)U%PzmSeBj;hc03B+&TST7ijCchw^Ex&FI)T3 zaiRV62K(hF*S#?d%-Z#`8DPJ(4Y@ROam7WLJCCnE*g1Cds%zi%1E!XhMGcFV=PjF6 z^}^K!!{1mu>&p+pXTu+Ctavdi*!Iney4BAtzEW~x`_p1#*UY1DK4;D?TYu<@raHZK zxP1P-PtFWpSUx|0LwRxyyKx&iEWdQw+4?C>SN6P8*1G8ayVoQ~Mfqdm z7mw_o^7S+L>!E8)zW992$4!Udw}_a&ZA#Jk?hO}Ped}VLaVMvG#U)pV zTwQrB9DTQ;e&2wO=a&r2Wb#r$;cYOBvCqbe)2ezF!T~vIg@!ZDF qL5|KVf8bT}c*~hRs5$yUvgOpvzmHp;Y;QLHkohJTd$&xgTJSg0tbX(W literal 0 HcmV?d00001 diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IRegistryWrapper.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IRegistryWrapper.cs new file mode 100644 index 0000000000..4b1682df16 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IRegistryWrapper.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Plugin.Uri.Interfaces +{ + public interface IRegistryWrapper + { + string GetRegistryValue(string registryLocation, string valueName); + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IUriParser.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IUriParser.cs new file mode 100644 index 0000000000..aa0154bc35 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IUriParser.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Plugin.Uri.Interfaces +{ + public interface IUriParser + { + bool TryParse(string input, out System.Uri result); + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IUrlResolver.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IUrlResolver.cs new file mode 100644 index 0000000000..717b67bf68 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Interfaces/IUrlResolver.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Plugin.Uri.Interfaces +{ + public interface IUrlResolver + { + bool IsValidHost(System.Uri uri); + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/de.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/de.xaml new file mode 100644 index 0000000000..2f2a319d1e --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/de.xaml @@ -0,0 +1,5 @@ + + Open in browser + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/en.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/en.xaml new file mode 100644 index 0000000000..2f2a319d1e --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/en.xaml @@ -0,0 +1,5 @@ + + Open in browser + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/ja.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/ja.xaml new file mode 100644 index 0000000000..2f2a319d1e --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/ja.xaml @@ -0,0 +1,5 @@ + + Open in browser + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/pl.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/pl.xaml new file mode 100644 index 0000000000..2f2a319d1e --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/pl.xaml @@ -0,0 +1,5 @@ + + Open in browser + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/tr.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/tr.xaml new file mode 100644 index 0000000000..2f2a319d1e --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/tr.xaml @@ -0,0 +1,5 @@ + + Open in browser + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/zh-cn.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/zh-cn.xaml new file mode 100644 index 0000000000..2f2a319d1e --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/zh-cn.xaml @@ -0,0 +1,5 @@ + + Open in browser + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/zh-tw.xaml b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/zh-tw.xaml new file mode 100644 index 0000000000..2f2a319d1e --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Languages/zh-tw.xaml @@ -0,0 +1,5 @@ + + Open in browser + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Main.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Main.cs new file mode 100644 index 0000000000..0479b566d3 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Main.cs @@ -0,0 +1,179 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using Microsoft.Plugin.Uri.UriHelper; +using Wox.Infrastructure.Logger; +using Wox.Infrastructure.Storage; +using Wox.Plugin; + +namespace Microsoft.Plugin.Uri +{ + public class Main : IPlugin, IPluginI18n, IContextMenu, ISavable, IDisposable + { + private readonly ExtendedUriParser _uriParser; + private readonly UriResolver _uriResolver; + private readonly PluginJsonStorage _storage; + private bool _disposed; + private UriSettings _uriSettings; + private RegisteryWrapper _registeryWrapper; + + public Main() + { + _storage = new PluginJsonStorage(); + _uriSettings = _storage.Load(); + _uriParser = new ExtendedUriParser(); + _uriResolver = new UriResolver(); + _registeryWrapper = new RegisteryWrapper(); + } + + public string BrowserIconPath { get; set; } + + public string DefaultIconPath { get; set; } + + public PluginInitContext Context { get; protected set; } + + public List LoadContextMenus(Result selectedResult) + { + return new List(0); + } + + public List Query(Query query) + { + var results = new List(); + + if (!string.IsNullOrEmpty(query?.Search) + && _uriParser.TryParse(query.Search, out var uriResult) + && _uriResolver.IsValidHost(uriResult)) + { + var uriResultString = uriResult.ToString(); + + results.Add(new Result + { + Title = uriResultString, + SubTitle = Context.API.GetTranslation("Microsoft_plugin_uri_website"), + IcoPath = _uriSettings.ShowBrowserIcon + ? BrowserIconPath + : DefaultIconPath, + Action = action => + { + Process.Start(new ProcessStartInfo(uriResultString) + { + UseShellExecute = true, + }); + return true; + }, + }); + } + + return results; + } + + public void Init(PluginInitContext context) + { + Context = context ?? throw new ArgumentNullException(nameof(context)); + Context.API.ThemeChanged += OnThemeChanged; + UpdateIconPath(Context.API.GetCurrentTheme()); + UpdateBrowserIconPath(Context.API.GetCurrentTheme()); + } + + public string GetTranslatedPluginTitle() + { + return "Url Handler"; + } + + public string GetTranslatedPluginDescription() + { + return "Handles urls"; + } + + public void Save() + { + _storage.Save(); + } + + private void OnThemeChanged(Theme oldtheme, Theme newTheme) + { + UpdateIconPath(newTheme); + UpdateBrowserIconPath(newTheme); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1031:Do not catch general exception types", Justification = "We want to keep the process alive but will log the exception")] + private void UpdateBrowserIconPath(Theme newTheme) + { + try + { + var progId = _registeryWrapper.GetRegistryValue("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice", "ProgId"); + var programLocation = + + // Resolve App Icon (UWP) + _registeryWrapper.GetRegistryValue("HKEY_CLASSES_ROOT\\" + progId + "\\Application", "ApplicationIcon") + + // Resolves default file association icon (UWP + Normal) + ?? _registeryWrapper.GetRegistryValue("HKEY_CLASSES_ROOT\\" + progId + "\\DefaultIcon", null); + + // "Handles 'Indirect Strings' (UWP programs)" + if (programLocation.StartsWith("@", StringComparison.Ordinal)) + { + var directProgramLocationStringBuilder = new StringBuilder(128); + if (NativeMethods.SHLoadIndirectString(programLocation, directProgramLocationStringBuilder, (uint)directProgramLocationStringBuilder.Capacity, IntPtr.Zero) == + NativeMethods.Hresult.Ok) + { + // Check if there's a postfix with contract-white/contrast-black icon is available and use that instead + var directProgramLocation = directProgramLocationStringBuilder.ToString(); + var themeIcon = newTheme == Theme.Light || newTheme == Theme.HighContrastWhite ? "contrast-white" : "contrast-black"; + var extension = Path.GetExtension(directProgramLocation); + var themedProgLocation = $"{directProgramLocation.Substring(0, directProgramLocation.Length - extension.Length)}_{themeIcon}{extension}"; + BrowserIconPath = File.Exists(themedProgLocation) + ? themedProgLocation + : directProgramLocation; + } + } + else + { + var indexOfComma = programLocation.IndexOf(',', StringComparison.Ordinal); + BrowserIconPath = indexOfComma > 0 + ? programLocation.Substring(0, indexOfComma) + : programLocation; + } + } + catch (Exception e) + { + BrowserIconPath = DefaultIconPath; + Log.Exception("Exception when retreiving icon", e); + } + } + + private void UpdateIconPath(Theme theme) + { + if (theme == Theme.Light || theme == Theme.HighContrastWhite) + { + DefaultIconPath = "Images/uri.light.png"; + } + else + { + DefaultIconPath = "Images/uri.dark.png"; + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed && disposing) + { + Context.API.ThemeChanged -= OnThemeChanged; + _disposed = true; + } + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Microsoft.Plugin.Uri.csproj b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Microsoft.Plugin.Uri.csproj new file mode 100644 index 0000000000..966f89b04c --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/Microsoft.Plugin.Uri.csproj @@ -0,0 +1,133 @@ + + + + netcoreapp3.1 + {03276a39-d4e9-417c-8ffd-200b0ee5e871} + Properties + Microsoft.Plugin.Uri + Microsoft.Plugin.Uri + true + false + false + x64 + + + + true + ..\..\..\..\..\x64\Debug\modules\launcher\Plugins\Microsoft.Plugin.Uri\ + DEBUG;TRACE + full + x64 + 7.3 + prompt + MinimumRecommendedRules.ruleset + 4 + false + true + + + + ..\..\..\..\..\x64\Release\modules\launcher\Plugins\Microsoft.Plugin.Uri\ + TRACE + true + pdbonly + x64 + 7.3 + prompt + MinimumRecommendedRules.ruleset + 4 + true + + + + + GlobalSuppressions.cs + + + StyleCop.json + + + + + + + + + + + + + + + + PreserveNewest + Designer + MSBuild:Compile + + + PreserveNewest + Designer + MSBuild:Compile + + + PreserveNewest + Designer + MSBuild:Compile + + + PreserveNewest + Designer + MSBuild:Compile + + + PreserveNewest + Designer + MSBuild:Compile + + + PreserveNewest + Designer + MSBuild:Compile + + + PreserveNewest + Designer + MSBuild:Compile + + + + + + + + + + + PreserveNewest + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + PreserveNewest + + + PreserveNewest + + + + \ No newline at end of file diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/NativeMethods.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/NativeMethods.cs new file mode 100644 index 0000000000..c64881736b --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/NativeMethods.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Microsoft.Plugin.Uri +{ + internal static class NativeMethods + { + internal enum Hresult : uint + { + Ok = 0x0000, + } + + [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)] + internal static extern Hresult SHLoadIndirectString(string pszSource, StringBuilder pszOutBuf, uint cchOutBuf, IntPtr ppvReserved); + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/RegisteryWrapper.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/RegisteryWrapper.cs new file mode 100644 index 0000000000..86993799ae --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/RegisteryWrapper.cs @@ -0,0 +1,17 @@ +// 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.Plugin.Uri.Interfaces; +using Microsoft.Win32; + +namespace Microsoft.Plugin.Uri +{ + public class RegisteryWrapper : IRegistryWrapper + { + public string GetRegistryValue(string registryLocation, string valueName) + { + return Registry.GetValue(registryLocation, valueName, null) as string; + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/ExtendedUriParser.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/ExtendedUriParser.cs new file mode 100644 index 0000000000..0d6e202e82 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/ExtendedUriParser.cs @@ -0,0 +1,43 @@ +// 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.Plugin.Uri.Interfaces; + +namespace Microsoft.Plugin.Uri.UriHelper +{ + public class ExtendedUriParser : IUriParser + { + public bool TryParse(string input, out System.Uri result) + { + if (string.IsNullOrEmpty(input)) + { + result = default; + return false; + } + + // Handle common cases UriBuilder does not handle + if (input.EndsWith(":", StringComparison.Ordinal) + || input.EndsWith(".", StringComparison.Ordinal) + || input.EndsWith(":/", StringComparison.Ordinal)) + { + result = default; + return false; + } + + try + { + var urlBuilder = new UriBuilder(input); + + result = urlBuilder.Uri; + return true; + } + catch (System.UriFormatException) + { + result = default; + return false; + } + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/UriResolver.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/UriResolver.cs new file mode 100644 index 0000000000..b22c693395 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriHelper/UriResolver.cs @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Plugin.Uri.Interfaces; + +namespace Microsoft.Plugin.Uri.UriHelper +{ + public class UriResolver : IUrlResolver + { + public bool IsValidHost(System.Uri uri) + { + return true; + } + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriSettings.cs b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriSettings.cs new file mode 100644 index 0000000000..7f91676425 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/UriSettings.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Plugin.Uri +{ + public class UriSettings + { + public bool ShowBrowserIcon { get; set; } = true; + } +} diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/plugin.json b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/plugin.json new file mode 100644 index 0000000000..38347f46b0 --- /dev/null +++ b/src/modules/launcher/Plugins/Microsoft.Plugin.Uri/plugin.json @@ -0,0 +1,12 @@ +{ + "ID": "03276A39D4E9417C8FFD200B0EE5E871", + "ActionKeyword": "*", + "Name": "Windows Uri Handler", + "Description": "Handles urls", + "Author": "Microsoft", + "Version": "1.0.0", + "Language": "csharp", + "Website": "http://aka.ms/PowerToys", + "ExecuteFileName": "Microsoft.Plugin.Uri.dll", + "IcoPath": "Images\\uri.dark.png" +}