diff --git a/.github/actions/spell-check/advice.md b/.github/actions/spell-check/advice.md index 1004eeaa60..84eb9218e2 100644 --- a/.github/actions/spell-check/advice.md +++ b/.github/actions/spell-check/advice.md @@ -14,7 +14,9 @@ https://www.regexplanet.com/advanced/perl/) yours before committing to verify it * well-formed pattern. - If you can write a [pattern](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns) that would match it, + If you can write a [pattern]( +https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns +) that would match it, try adding it to the `patterns.txt` file. Patterns are Perl 5 Regular Expressions - you can [test]( diff --git a/.github/actions/spell-check/allow/allow.txt b/.github/actions/spell-check/allow/allow.txt index 2774060a60..20a05a495d 100644 --- a/.github/actions/spell-check/allow/allow.txt +++ b/.github/actions/spell-check/allow/allow.txt @@ -9,4 +9,5 @@ sdl ssh ubuntu unuing +workarounds wil diff --git a/.github/actions/spell-check/allow/temporary.txt b/.github/actions/spell-check/allow/temporary.txt new file mode 100644 index 0000000000..05cd229158 --- /dev/null +++ b/.github/actions/spell-check/allow/temporary.txt @@ -0,0 +1,11 @@ +etw +filetime +flyouts +lnks +reparented +screenshots +SIDs +subkeys +TApp +websites +wmi diff --git a/.github/actions/spell-check/candidate.patterns b/.github/actions/spell-check/candidate.patterns index 4b40e728ee..4760fcdd18 100644 --- a/.github/actions/spell-check/candidate.patterns +++ b/.github/actions/spell-check/candidate.patterns @@ -1,23 +1,36 @@ # marker to ignore all code on line ^.*/\* #no-spell-check-line \*/.*$ -# marker for ignoring a comment to the end of the line -// #no-spell-check.*$ +# marker to ignore all code on line +^.*\bno-spell-check(?:-line|)(?:\s.*|)$ + +# https://cspell.org/configuration/document-settings/ +# cspell inline +^.*\b[Cc][Ss][Pp][Ee][Ll]{2}:\s*[Dd][Ii][Ss][Aa][Bb][Ll][Ee]-[Ll][Ii][Nn][Ee]\b # patch hunk comments ^\@\@ -\d+(?:,\d+|) \+\d+(?:,\d+|) \@\@ .* # git index header -index [0-9a-z]{7,40}\.\.[0-9a-z]{7,40} +index (?:[0-9a-z]{7,40},|)[0-9a-z]{7,40}\.\.[0-9a-z]{7,40} + +# file permissions +['"`\s][-bcdLlpsw](?:[-r][-w][-Ssx]){2}[-r][-w][-SsTtx]\+?['"`\s] + +# css url wrappings +\burl\([^)]+\) # cid urls (['"])cid:.*?\g{-1} # data url in parens -\(data:[^)]*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})[^)]*\) +\(data:(?:[^) ][^)]*?|)(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})[^)]*\) # data url in quotes -([`'"])data:.*?(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,}).*\g{-1} +([`'"])data:(?:[^ `'"].*?|)(?:[A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,}).*\g{-1} # data url data:[-a-zA-Z=;:/0-9+]*,\S* +# https/http/file urls +#(?:\b(?:https?|ftp|file)://)[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|] + # mailto urls mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,} @@ -35,6 +48,9 @@ magnet:[?=:\w]+ # asciinema \basciinema\.org/a/[0-9a-zA-Z]+ +# asciinema v2 +^\[\d+\.\d+, "[io]", ".*"\]$ + # apple \bdeveloper\.apple\.com/[-\w?=/]+ # Apple music @@ -89,7 +105,7 @@ vpc-\w+ # Google Drive \bdrive\.google\.com/(?:file/d/|open)[-0-9a-zA-Z_?=]* # Google Groups -\bgroups\.google\.com/(?:(?:forum/#!|d/)(?:msg|topics?|searchin)|a)/[^/\s"]+/[-a-zA-Z0-9$]+(?:/[-a-zA-Z0-9]+)* +\bgroups\.google\.com(?:/[a-z]+/(?:#!|)[^/\s"]+)* # Google Maps \bmaps\.google\.com/maps\?[\w&;=]* # Google themes @@ -117,6 +133,8 @@ themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+. (?:\[`?[0-9a-f]+`?\]\(https:/|)/(?:www\.|)github\.com(?:/[^/\s"]+){2,}(?:/[^/\s")]+)(?:[0-9a-f]+(?:[-0-9a-zA-Z/#.]*|)\b|) # GitHub SHAs \bgithub\.com(?:/[^/\s"]+){2}[@#][0-9a-f]+\b +# GitHub SHA refs +\[([0-9a-f]+)\]\(https://(?:www\.|)github.com/[-\w]+/[-\w]+/commit/\g{-1}[0-9a-f]* # GitHub wiki \bgithub\.com/(?:[^/]+/){2}wiki/(?:(?:[^/]+/|)_history|[^/]+(?:/_compare|)/[0-9a-f.]{40,})\b # githubusercontent @@ -128,9 +146,9 @@ themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+. # git.io \bgit\.io/[0-9a-zA-Z]+ # GitHub JSON -"node_id": "[-a-zA-Z=;:/0-9+]*" +"node_id": "[-a-zA-Z=;:/0-9+_]*" # Contributor -\[[^\]]+\]\(https://github\.com/[^/\s"]+\) +\[[^\]]+\]\(https://github\.com/[^/\s"]+/?\) # GHSA GHSA(?:-[0-9a-z]{4}){3} @@ -143,8 +161,8 @@ GHSA(?:-[0-9a-z]{4}){3} # GitLab commits \bgitlab\.[^/\s"]*/(?:[^/\s"]+/){2}commits?/[0-9a-f]+\b -# binanace -accounts.binance.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]* +# binance +accounts\.binance\.com/[a-z/]*oauth/authorize\?[-0-9a-zA-Z&%]* # bitbucket diff \bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}diff(?:stat|)(?:/[^/\s"]+){2}:[0-9a-f]+ @@ -280,9 +298,9 @@ slack://[a-zA-Z0-9?&=]+ \bdropbox\.com/sh?/[^/\s"]+/[-0-9A-Za-z_.%?=&;]+ # ipfs protocol -ipfs://[0-9a-z]* +ipfs://[0-9a-zA-Z]{3,} # ipfs url -/ipfs/[0-9a-z]* +/ipfs/[0-9a-zA-Z]{3,} # w3 \bw3\.org/[-0-9a-zA-Z/#.]+ @@ -359,14 +377,22 @@ ipfs://[0-9a-z]* # tinyurl \btinyurl\.com/\w+ +# codepen +\bcodepen\.io/[\w/]+ + +# registry.npmjs.org +\bregistry\.npmjs\.org/(?:@[^/"']+/|)[^/"']+/-/[-\w@.]+ + # getopts \bgetopts\s+(?:"[^"]+"|'[^']+') # ANSI color codes -(?:\\(?:u00|x)1b|\x1b)\[\d+(?:;\d+|)m +(?:\\(?:u00|x)1[Bb]|\x1b|\\u\{1[Bb]\})\[\d+(?:;\d+|)m # URL escaped characters \%[0-9A-F][A-F] +# lower URL escaped characters +\%[0-9a-f][a-f](?=[a-z]{2,}) # IPv6 \b(?:[0-9a-fA-F]{0,4}:){3,7}[0-9a-fA-F]{0,4}\b # c99 hex digits (not the full format, just one I've seen) @@ -376,7 +402,7 @@ ipfs://[0-9a-z]* # sha sha\d+:[0-9]*[a-f]{3,}[0-9a-f]* # sha-... -- uses a fancy capture -(['"]|")[0-9a-f]{40,}\g{-1} +(\\?['"]|")[0-9a-f]{40,}\g{-1} # hex runs \b[0-9a-fA-F]{16,}\b # hex in url queries @@ -391,18 +417,21 @@ sha\d+:[0-9]*[a-f]{3,}[0-9a-f]* # Well known gpg keys .well-known/openpgpkey/[\w./]+ +# pki +-----BEGIN.*-----END + # uuid: \b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b # hex digits including css/html color classes: -(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|u\d+)\b +(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23)[0-9_a-fA-FgGrR]*?[a-fA-FgGrR]{2,}[0-9_a-fA-FgGrR]*(?:[uUlL]{0,3}|[iu]\d+)\b # integrity -integrity="sha\d+-[-a-zA-Z=;:/0-9+]{40,}" +integrity=(['"])(?:\s*sha\d+-[-a-zA-Z=;:/0-9+]{40,})+\g{-1} # https://www.gnu.org/software/groff/manual/groff.html # man troff content \\f[BCIPR] -# ' -\\\(aq +# '/" +\\\([ad]q # .desktop mime types ^MimeTypes?=.*$ @@ -411,21 +440,33 @@ integrity="sha\d+-[-a-zA-Z=;:/0-9+]{40,}" # Localized .desktop content Name\[[^\]]+\]=.* -# IServiceProvider -\bI(?=(?:[A-Z][a-z]{2,})+\b) +# IServiceProvider / isAThing +\b(?:I|isA)(?=(?:[A-Z][a-z]{2,})+\b) # crypt -"\$2[ayb]\$.{56}" +(['"])\$2[ayb]\$.{56}\g{-1} # scrypt / argon \$(?:scrypt|argon\d+[di]*)\$\S+ -# Input to GitHub JSON -content: "[-a-zA-Z=;:/0-9+]*=" +# go.sum +\bh1:\S+ -# Python stringprefix / binaryprefix +# scala modules +#("[^"]+"\s*%%?\s*){2,3}"[^"]+" + +# Input to GitHub JSON +content: (['"])[-a-zA-Z=;:/0-9+]*=\g{-1} + +# This does not cover multiline strings, if your repository has them, +# you'll want to remove the `(?=.*?")` suffix. +# The `(?=.*?")` suffix should limit the false positives rate +# printf +#%(?:(?:(?:hh?|ll?|[jzt])?[diuoxn]|l?[cs]|L?[fega]|p)(?=[a-z]{2,})|(?:X|L?[FEGA]|p)(?=[a-zA-Z]{2,}))(?=[_a-zA-Z]+\b)(?!%)(?=.*?['"]) + +# Python string prefix / binary prefix # Note that there's a high false positive rate, remove the `?=` and search for the regex to see if the matches seem like reasonable strings -(?|m([|!/@#,;']).*?\g{-1}) + +# perl qr regex +(?|\(.*?\)|([|!/@#,;']).*?\g{-1}) # Go regular expressions regexp?\.MustCompile\(`[^`]*`\) +# regex choice +\(\?:[^)]+\|[^)]+\) + +# proto +^\s*(\w+)\s\g{-1} = + # sed regular expressions sed 's/(?:[^/]*?[a-zA-Z]{3,}[^/]*?/){2} +# node packages +(["'])\@[^/'" ]+/[^/'" ]+\g{-1} + # go install go install(?:\s+[a-z]+\.[-@\w/.]+)+ +# jetbrains schema https://youtrack.jetbrains.com/issue/RSRP-489571 +urn:shemas-jetbrains-com + # kubernetes pod status lists # https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase \w+(?:-\w+)+\s+\d+/\d+\s+(?:Running|Pending|Succeeded|Failed|Unknown)\s+ @@ -462,19 +522,47 @@ go install(?:\s+[a-z]+\.[-@\w/.]+)+ -[0-9a-f]{10}-\w{5}\s # posthog secrets -posthog\.init\((['"])phc_[^"',]+\g{-1}, +([`'"])phc_[^"',]+\g{-1} # xcode # xcodeproject scenes -(?:Controller|ID|id)="\w{3}-\w{2}-\w{3}" +(?:Controller|destination|ID|id)="\w{3}-\w{2}-\w{3}" # xcode api botches customObjectInstantitationMethod +# configure flags +.* \| --\w{2,}.*?(?=\w+\s\w+) + # font awesome classes \.fa-[-a-z0-9]+ +# bearer auth +(['"])Bear[e][r] .*?\g{-1} + +# basic auth +(['"])Basic [-a-zA-Z=;:/0-9+]{3,}\g{-1} + +# base64 encoded content +([`'"])[-a-zA-Z=;:/0-9+]+=\g{-1} +# base64 encoded content in xml/sgml +>[-a-zA-Z=;:/0-9+]+== 0.0.22) +\\\w{2,}\{ + +# eslint +"varsIgnorePattern": ".+" + +# Windows short paths +[/\\][^/\\]{5,6}~\d{1,2}[/\\] + +# in check-spelling@v0.0.22+, printf markers aren't automatically consumed +# printf markers +#(?v# (?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_])) -# Compiler flags (Scala) -(?:^|[\t ,>"'`=(])-J-[DPWXY](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) -# Compiler flags -#(?:^|[\t ,"'`=(])-[DPWXYLlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) + +# Compiler flags (Unix, Java/Scala) +# Use if you have things like `-Pdocker` and want to treat them as `docker` +#(?:^|[\t ,>"'`=(])-(?:(?:J-|)[DPWXY]|[Llf])(?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) + +# Compiler flags (Windows / PowerShell) +# This is a subset of the more general compiler flags pattern. +# It avoids matching `-Path` to prevent it from being treated as `ath` +#(?:^|[\t ,"'`=(])-(?:[DPL](?=[A-Z]{2,})|[WXYlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,})) # Compiler flags (linker) ,-B + # curl arguments \b(?:\\n|)curl(?:\s+-[a-zA-Z]{1,2}\b)*(?:\s+-[a-zA-Z]{3,})(?:\s+-[a-zA-Z]+)* # set arguments diff --git a/.github/actions/spell-check/excludes.txt b/.github/actions/spell-check/excludes.txt index ab49292d30..143cedf602 100644 --- a/.github/actions/spell-check/excludes.txt +++ b/.github/actions/spell-check/excludes.txt @@ -10,68 +10,87 @@ (?:^|/)FilePreviewCommon/Assets/Monaco/monacoSpecialLanguages.js (?:^|/)monacoSRC/ (?:^|/)package(?:-lock|)\.json$ +(?:^|/)Pipfile$ +(?:^|/)power-rename-ui-flags$ +(?:^|/)pyproject.toml +(?:^|/)requirements(?:-dev|-doc|-test|)\.txt$ (?:^|/)timezones\.json$ (?:^|/)vendor/ (?:^|/)WindowsSettings\.json$ -/package(?:-lock|)\.json$ -/pinyindb/ -/settings-html/ -[/.][a-z]{2}(?:-[a-zA-Z]{2}|)\. +/images/launcher/[^/]+$ +/TestFiles/ +[^/]\.gcode$ +[^/]\.rgs$ \.a$ \.ai$ +\.all-contributorsrc$ \.avi$ \.bmp$ \.bz2$ +\.cer$ \.class$ +\.coveragerc$ +\.crl$ \.crt$ -\.dat$ +\.csr$ \.dll$ \.docx?$ \.drawio$ \.DS_Store$ \.eot$ +\.eps$ \.exe$ \.filters$ -\.gcode$ \.gif$ +\.git-blame-ignore-revs$ \.gitattributes$ -\.gitignore$ +\.gitkeep$ \.graffle$ \.gz$ \.icns$ \.ico$ +\.ipynb$ \.jar$ \.jks$ \.jpe?g$ \.key$ -\.lcl$ \.lib$ \.lock$ \.map$ \.min\.. +\.mo$ \.mod$ \.mp[34]$ \.o$ \.ocf$ \.otf$ +\.p12$ +\.parquet$ \.pdf$ \.pem$ -\.PNG$ +\.pfx$ \.png$ \.psd$ \.pyc$ +\.pylintrc$ +\.qm$ \.s$ -\.stl$ -\.svg$ +\.sig$ +\.so$ \.svgz?$ +\.sys$ \.tar$ +\.tgz$ \.tiff?$ \.ttf$ \.wav$ \.webm$ \.webp$ \.woff2?$ +\.xcf$ \.xlsx?$ +\.xpm$ +\.xz$ \.zip$ ^\.github/actions/spell-check/ ^\.gitmodules$ @@ -79,38 +98,28 @@ ^\Q.pipelines/ESRPSigning_core.json\E$ ^\Qinstaller/PowerToysSetup/Settings.wxs\E$ ^\Qsrc/common/ManagedCommon/ColorFormatHelper.cs\E$ +^\Q.pipelines/sdl.gdnbaselines\E$ +^\Qsrc/common/FilePreviewCommon/Assets/Monaco/monaco_languages.json\E$ +^\Qsrc/common/notifications/BackgroundActivatorDLL/cpp.hint\E$ +^\Qsrc/modules/colorPicker/ColorPickerUI/Assets/ColorPicker/colorPicker.cur\E$ ^\Qsrc/modules/colorPicker/ColorPickerUI/Shaders/GridShader.cso\E$ ^\Qsrc/modules/MouseUtils/MouseJumpUI/MainForm.resx\E$ ^\Qsrc/modules/MouseWithoutBorders/App/Form/frmAbout.cs\E$ -^\Qsrc/modules/MouseWithoutBorders/App/Properties/AssemblyInfo.cs\E$ ^\Qsrc/modules/MouseWithoutBorders/ModuleInterface/generateSecurityDescriptor.h\E$ -^\Qsrc/settings-ui/Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/CorruptJson/Microsoft/PowerToys/settings.json\E$ -^\Qsrc/settings-ui/Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.18.2/Microsoft/PowerToys/PowerRename/power-rename-ui-flags\E$ -^\Qsrc/settings-ui/Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.19.2/Microsoft/PowerToys/PowerRename/power-rename-ui-flags\E$ -^\Qsrc/settings-ui/Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.20.1/Microsoft/PowerToys/PowerRename/power-rename-ui-flags\E$ -^\Qsrc/settings-ui/Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/FancyZones/settings.json\E$ -^\Qsrc/settings-ui/Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/V0.21.1/Microsoft/PowerToys/PowerRename/power-rename-ui-flags\E$ -^\Qsrc/settings-ui/Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/FancyZones/settings.json\E$ -^\Qsrc/settings-ui/Settings.UI.UnitTests/BackwardsCompatibility/TestFiles/v0.22.0/Microsoft/PowerToys/PowerRename/power-rename-ui-flags\E$ +^\Qsrc/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/WindowsAndMessaging/User32.SYSTEM_METRICS_INDEX.cs\E$ +^\Qsrc/modules/MouseWithoutBorders/App/Form/frmInputCallback.resx\E$ +^\Qsrc/modules/MouseWithoutBorders/App/Form/frmLogon.resx\E$ +^\Qsrc/modules/MouseWithoutBorders/App/Form/frmMatrix.resx\E$ +^\Qsrc/modules/MouseWithoutBorders/App/Form/frmScreen.resx\E$ +^\Qsrc/modules/peek/Peek.Common/NativeMethods.txt\E$ +^\Qsrc/modules/previewpane/SvgPreviewHandler/SvgHTMLPreviewGenerator.cs\E$ +^\Qsrc/modules/previewpane/UnitTests-StlThumbnailProvider/HelperFiles/sample.stl\E$ ^\Qtools/project_template/ModuleTemplate/resource.h\E$ ^doc/devdocs/akaLinks\.md$ -^installer/PowerToysSetup/WebView2/MicrosoftEdgeWebview2Setup.exe$ -^src/common/logger/logger\.vcxproj\.filters$ -^src/common/notifications/BackgroundActivatorDLL/BackgroundActivator\.vcxproj\.filters$ -^src/common/notifications/BackgroundActivatorDLL/cpp\.hint$ -^src/modules/colorPicker/ColorPickerUI/Assets/ColorPicker/colorPicker\.cur$ -^src/modules/fancyzones/lib/FancyZonesWinHookEventIDs\.h$ -^src/modules/imageresizer/dll/ContextMenuHandler\.rgs$ -^src/modules/imageresizer/dll/ImageResizerExt\.rgs$ -^src/modules/MouseUtils/MouseJumpUI/NativeMethods/User32/UI/WindowsAndMessaging/User32.SYSTEM_METRICS_INDEX.cs$ ^src/modules/MouseWithoutBorders/App/Form/.*\.resx$ ^src/modules/MouseWithoutBorders/App/Form/.*\.Designer\.cs$ ^src/modules/MouseWithoutBorders/App/Helper/.*\.resx$ ^src/modules/MouseWithoutBorders/App/.*/NativeMethods\.cs$ -^src/modules/peek/Peek\.Common/NativeMethods\.txt$ -^src/modules/powerrename/testapp/PowerRenameTest\.vcxproj\.filters$ -^src/modules/previewpane/PreviewPaneUnitTests/HelperFiles/MarkdownWithHTMLImageTag\.txt$ ^src/modules/previewpane/UnitTests-MarkdownPreviewHandler/HelperFiles/MarkdownWithHTMLImageTag.txt$ -^tools/CleanUp_tool/CleanUp_tool\.vcxproj\.filters$ ^tools/Verification scripts/Check preview handler registration\.ps1$ ignore$ diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 14d58e86b9..2965a4f3d8 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -21,6 +21,7 @@ activationaction ADDUNDORECORD ADifferent adio +adipiscing administra ADMINS adml @@ -34,6 +35,7 @@ AGGREGATABLE AHybrid ALarger alekhyareddy +aliquip ALLAPPS ALLINPUT ALLOWUNDO @@ -97,6 +99,7 @@ ASingle ASSOCCHANGED ASYNCWINDOWPLACEMENT ASYNCWINDOWPOS +ative atl atlbase atlcom @@ -150,7 +153,6 @@ BLURREGION bmi bms BNumber -Bokm BOKMAL bootstrapper BOOTSTRAPPERINSTALLFOLDER @@ -172,6 +174,7 @@ BTNFACE Bto buf bugreport +BUILDARCH BUILDNUMBER buildtask buildtransitive @@ -274,6 +277,7 @@ cominterop commandline COMMANDTITLE commctrl +commodo compmgmt COMPOSITIONFULL comsupp @@ -284,6 +288,8 @@ CONFIGW CONFLICTINGMODIFIERKEY CONFLICTINGMODIFIERSHORTCUT CONOUT +consectetur +consequat Consolas constexpr consts @@ -326,6 +332,7 @@ CTRLALTDEL Ctrls Ctx CUI +cupidatat currentculture CURRENTDIR CURSORINFO @@ -372,6 +379,7 @@ dcomp DComposition dcr dcs +ddd DDEIf DDevice ddf @@ -380,6 +388,7 @@ Deact debian debugbreak DECLAR +declatory declspec decryptor Dedup @@ -398,12 +407,12 @@ DELETEDKEYIMAGE DELETESCANS deletethis Delimarsky -dend DENORMAL Deondre depersist deprioritized deref +deserunt DESKTOPABSOLUTEEDITING DESKTOPABSOLUTEPARSING desktopshorcutinstalled @@ -484,6 +493,7 @@ dxgidebug dxgiformat dxguid ecount +ecyclebin EData Edid EDITKEYBOARD @@ -491,8 +501,13 @@ editkeyboardwindow EDITSHORTCUTS editshortcutswindow EFile +egistry +egistrypreview eip ekus +elease +elemetry +elit emmintrin Emoji ENABLEDELAYEDEXPANSION @@ -503,6 +518,7 @@ encryptor endpointvolume endregion ENDSESSION +enim ENTERSIZEMOVE ENU EOAC @@ -517,10 +533,16 @@ ERRORLEVEL ERRORTITLE ESettings esize +esource esrp +estapp +estart +ests +esult etl etstat -etw +ETW +etwork EUQ eurochange eventlog @@ -538,6 +560,7 @@ exabyte examplehandler examplepowertoy EXAND +Excepteur EXCLUDEFROMCAPTURE exdisp executionpolicy @@ -548,6 +571,8 @@ exlist EXPCMDFLAGS EXPCMDSTATE explr +exppowertoys +exptas exsb EXSEL exstyle @@ -575,7 +600,7 @@ FILEOP FILEOS FILESUBTYPE FILESYSPATH -filetime +Filetime FILEVERSION Filtergraph Filterkeyboard @@ -907,6 +932,8 @@ killrunner Knownfolders KSPROPERTY Kybd +laboris +laborum LAlt Lambson langword @@ -1028,6 +1055,7 @@ majortype MAJORVERSION makecab MAKEINTRESOURCE +MAKEINTRESOURCEA MAKEINTRESOURCEW makepri manifestdependency @@ -1110,6 +1138,7 @@ mockapi MODECHANGE modernwpf MODESPRUNED +mollit MONITORENUMPROC MONITORINFO MONITORINFOEX @@ -1128,6 +1157,8 @@ MOUSEWHEEL MOVESIZEEND MOVESIZESTART mozilla +MOZILLAPL +MOZPL mpmc MRM MRT @@ -1154,10 +1185,12 @@ msrc msstore mst msvc +msvcp MTND Mul MULTIPLEUSE multizone +muxc mvvm mwb MWBEx @@ -1182,6 +1215,7 @@ NCMBUTTONUP NCMOUSELEAVE NCMOUSEMOVE NCol +nconsectetur ncpa NCPAINT NCRBUTTONDBLCLK @@ -1197,6 +1231,7 @@ netcore netcoreapp netcpl netframework +Netscape netsetup netsh Neue @@ -1205,8 +1240,8 @@ newdev newitem newpath newrow +newsgroups Newtonsoft -niels nielslaute NIF nint @@ -1223,6 +1258,8 @@ NOCOPYBITS nodeca nodiscard nodoc +NODRAWCAPTION +NODRAWICON NOINHERITLAYOUT NOINTERFACE NOLINKINFO @@ -1249,6 +1286,7 @@ NORMALUSER NOSEARCH NOSENDCHANGING NOSIZE +nostrud notfound NOTIFICATIONSDLL NOTIFYICONDATAW @@ -1264,6 +1302,7 @@ NOZORDER NPH NResize nrw +nsunt NTAPI ntdll ntfs @@ -1280,12 +1319,15 @@ Objbase OBJID objidl oblitum +obmikh +occaecat ocr Ocrsettings odbc odbccp Oem officehubintl +officia ofs oid oldcolor @@ -1302,6 +1344,7 @@ onenote onstd oobe OOBEPT +ools opencode opensource openxmlformats @@ -1315,6 +1358,8 @@ ostr OSVERSIONINFOEX OSVERSIONINFOEXW osvi +otating +otifications OUTOFCONTEXT OUTOFMEMORY outpin @@ -1461,6 +1506,7 @@ PRODUCTVERSION Progman programdata PROGRAMFILES +proident projectname PROPBAG PROPERTYKEY @@ -1541,6 +1587,7 @@ RECTL rectp rects RECTSOURCE +recyclebin redirectedfrom Redist redistributable @@ -1612,6 +1659,7 @@ RIGHTDOWN RIGHTSCROLLBAR RIGHTUP riid +ringbuffer RKey RLO RMENU @@ -1677,7 +1725,6 @@ SDKDDK sdns searchterm secpol -Secur Segoe Sekan SENDCHANGE @@ -1698,6 +1745,7 @@ SETTINGCHANGE SETTINGSCHANGED settingsheader settingshotkeycontrol +setvariable SETWORKAREA setzero sfgao @@ -1853,6 +1901,7 @@ stylecop Subdir subfolders subkey +subkeys SUBLANG subquery subresource @@ -1888,7 +1937,7 @@ SYSTEMTIME sysvol Tadele talynone -TApp +tapp TApplication TApplied targ @@ -1945,6 +1994,7 @@ tlbimp TMPVAR TNP toggleswitch +tonos toolkitcontrols toolkitconverters Toolset @@ -1965,6 +2015,7 @@ Tsd TServer TStr TValue +tweakme TWF tymed typedef @@ -1987,6 +2038,7 @@ UIEx uipi UIs ULARGE +ullamco ULONGLONG ums unapply @@ -2008,6 +2060,7 @@ unknwn UNLEN Unmap unmute +unner UNORM unregistering unremapped @@ -2048,6 +2101,7 @@ VCINSTALLDIR vcm Vcpkg VCRT +vcruntime vcvars VDesktop vdi @@ -2090,6 +2144,7 @@ vsconfig VSCROLL vsetq VSM +vso vsonline vstemplate VSTHRD @@ -2186,7 +2241,7 @@ WKSG Wlkr wmain Wman -wmi +WMI WMICIM wmimgmt WMKEYDOWN @@ -2229,6 +2284,8 @@ wsl wss wstr wsz +WTA +WTNCA wtoi WTS wtsapi diff --git a/.github/actions/spell-check/line_forbidden.patterns b/.github/actions/spell-check/line_forbidden.patterns index e8c5179a47..8ddb666cd6 100644 --- a/.github/actions/spell-check/line_forbidden.patterns +++ b/.github/actions/spell-check/line_forbidden.patterns @@ -1,4 +1,6 @@ -# reject `m_data` as there's a certain OS which has evil defines that break things if it's used elsewhere +# reject `m_data` as VxWorks defined it and that breaks things if it's used elsewhere +# see [fprime](https://github.com/nasa/fprime/commit/d589f0a25c59ea9a800d851ea84c2f5df02fb529) +# and [Qt](https://github.com/qtproject/qt-solutions/blame/fb7bc42bfcc578ff3fa3b9ca21a41e96eb37c1c7/qtscriptclassic/src/qscriptbuffer_p.h#L46) # \bm_data\b # If you have a framework that uses `it()` for testing and `fit()` for debugging a specific test, @@ -6,40 +8,72 @@ # to use this: #\bfit\( +# s.b. anymore +\bany more[,.] + # s.b. GitHub -\bGithub\b +(?v# +(?:(?<=[A-Z]{2})V|(?<=[a-z]{2}|[A-Z]{2})v)\d+(?:\b|(?=[a-zA-Z_])) + +# hit-count: 209 file-count: 97 # w3 \bw3\.org/[-0-9a-zA-Z/#.]+ -# hit-count: 47 file-count: 3 +# hit-count: 137 file-count: 38 +# alternate markers if you run into latex and friends +(?]|\b)[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{10,12}(?:\g{-1}|[<})>]) -# c99 hex digits (not the full format, just one I've seen) -0x[0-9a-fA-F](?:\.[0-9a-fA-F]*|)[pP] - -# URL escaped characters -\%[0-9A-F]{2} - -# wregex -std::wregex\(L"[^"]*"\) - -# hash -Hash="[0-9A-F]{40}" -# SHA256 hash -'[0-9A-F]{64}' - -# hex digits including css/html color classes: -(?:[\\0][xX]|\\u|[uU]\+|#x?|\%23|L")[0-9a-fA-FgGrR_]{2,}(?:[uU]?[lL]{0,2}|u\d+)\b - (?:L"[abAB]+", ){3}L"[abAB]+" -"Lorem[^"]+?\." -TestCase\("[^"]+" -# Test line with hexadecimal colors -\[DataRow\("[0-9A-F]{6}", \d{3}, \d{3}, \d{3}\)\] -\[DataRow\("[0-9A-F]{6}", \d{3}.\d{1}, \d{3}.\d{1}, \d{3}.\d{1}\)\] -\[DataRow\("[0-9A-F]{6}", "[BCGMRY]\d\d?", \d{3}, \d{3}\)\] - -# version suffix v# -[Vv]\d+(?:\b|(?=[a-zA-Z_])) - -# Windows paths -\\native -\\netcoreapp -\\netstandard -\\network -\\notifications -\\recyclebin -\\Registry -\\registry -\\reinstall -\\release -\\Resize -\\resource -\\Resources -\\restart -\\restore -\\result -\\robmikh -\\rotating -\\runner -\\runtimes -\\Telemetry -\\telemetry -\\testapp -\\tests -\\tools - -# plugin.json -^ "ID": "[0-9A-F]{32}",$ +# hit-count: 1 file-count: 1 +# marker to ignore all code on line +^.*/\* #no-spell-check-line \*/.*$ # UnitTests \[DataRow\(.*\)\] -# Id info inside markdown file (registry.md) -^\|\s+ID\s+\|\s*\`[0-9A-F]{32}\` - -# TestCase strings intentionally have non dictionary items -\[TestCase\(new string.*\] - # D2D D?2D -# marker for ignoring a comment to the end of the line -^.*/\* #no-spell-check-line \*/.*$ -// #no-spell-check.*$ +# hit-count: 1 file-count: 1 +# GHSA +GHSA(?:-[0-9a-z]{4}){3} -http://tes/ +# hit-count: 1 file-count: 1 +# medium +\bmedium\.com/\@?[^/\s"]+/[-\w]+ +# hit-count: 1 file-count: 1 +# kubectl.kubernetes.io/last-applied-configuration +"kubectl.kubernetes.io/last-applied-configuration": ".*" + +# hit-count: 1 file-count: 1 # tar arguments -\b(?:\\n|)tar(?:\s+-[a-zA-Z]+|\s[a-z]+)+ +\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+ + +\bSecur32 -# fabricbot.json -"id": "\S+" -"commentPattern": ".*" # Questionably acceptable forms of `in to` # Personally, I prefer `log into`, but people object # https://www.tprteaching.com/log-into-log-in-to-login/ -\b[Ll]og in to\b +\b(?:[Ll]og|[Ss]ign) in to\b + +# to opt in +\bto opt in\b # acceptable duplicates # ls directory listings -# /bin/ls -l output -[-bcdlpsw](?:[-r][-w][-sx]){3}\s+\d+\s+(\S+)\s+\g{-1}\s+\d+\s+ +[-bcdlpsw](?:[-r][-w][-Ssx]){3}\s+\d+\s+\S+\s+\S+\s+\d+\s+ +# mount +\bmount\s+-t\s+(\w+)\s+\g{-1}\b # C types and repeated CSS values -\s(center|div|inherit|long|LONG|none|normal|solid|thin|transparent|very)(?: \g{-1})+\s +\s(auto|center|div|inherit|long|LONG|none|normal|solid|thin|transparent|very)(?: \g{-1})+\s +# C struct +\bstruct\s+(\w+)\s+\g{-1}\b # go templates -\s(\w+)\s+\g{-1}\s+\`(?:graphql|json|yaml): -# javadoc / .net -(?:[\\@](?:groupname|param)|(?:public|private)(?:\s+static|\s+readonly)*)\s+(\w+)\s+\g{-1}\s +\s(\w+)\s+\g{-1}\s+\`(?:graphql|inject|json|yaml): +# doxygen / javadoc / .net +(?:[\\@](?:brief|groupname|t?param|return|retval)|(?:public|private|\[Parameter(?:\(.+\)|)\])(?:\s+static|\s+override|\s+readonly)*)(?:\s+\{\w+\}|)\s+(\w+)\s+\g{-1}\s # Commit message -- Signed-off-by and friends ^\s*(?:(?:Based-on-patch|Co-authored|Helped|Mentored|Reported|Reviewed|Signed-off)-by|Thanks-to): (?:[^<]*<[^>]*>|[^<]*)\s*$ diff --git a/.github/actions/spell-check/reject.txt b/.github/actions/spell-check/reject.txt index b5a6d36809..e5e4c3eef8 100644 --- a/.github/actions/spell-check/reject.txt +++ b/.github/actions/spell-check/reject.txt @@ -1,4 +1,5 @@ ^attache$ +^bellow$ benefitting occurences? ^dependan.* diff --git a/.github/policies/resourceManagement.yml b/.github/policies/resourceManagement.yml index 0a9e925b94..f41817bbf6 100644 --- a/.github/policies/resourceManagement.yml +++ b/.github/policies/resourceManagement.yml @@ -233,5 +233,14 @@ configuration: - addReply: reply: Hi! Thanks for making us aware of the problem. We raised the issue with our internal localization team. This issue should be fixed hopefully in the next version of PowerToys. description: + - if: + - payloadType: Issue_Comment + - commentContains: + pattern: 'I would [like|love] [to help|helping|to contribute|contributing|to implement|implementing|to fix|fixing]' + isRegex: True + then: + - addReply: + reply: Hi! Your last comment indicates to our system, that you might want to contribute to this feature/fix this bug. Thank you! Please make us aware on our ["Would you like to contribute to PowerToys?" thread](https://github.com/microsoft/PowerToys/issues/28769), as we don't see all the comments.

_I'm a bot (beep!) so please excuse any mistakes I may make_ + description: onFailure: onSuccess: diff --git a/.github/workflows/spelling2.yml b/.github/workflows/spelling2.yml index 0cbb2ce08c..d58828a50e 100644 --- a/.github/workflows/spelling2.yml +++ b/.github/workflows/spelling2.yml @@ -5,7 +5,7 @@ name: Spell checking # https://github.com/check-spelling/check-spelling/wiki/Feature%3A-Restricted-Permissions # # `jobs.comment-push` runs when a push is made to a repository and the `jobs.spelling` job needs to make a comment -# (in odd cases, it might actually run just to collapse a commment, but that's fairly rare) +# (in odd cases, it might actually run just to collapse a comment, but that's fairly rare) # it needs `contents: write` in order to add a comment. # # `jobs.comment-pr` runs when a pull_request is made to a repository and the `jobs.spelling` job needs to make a comment @@ -34,6 +34,29 @@ name: Spell checking # # For background, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Update-with-deploy-key +# Sarif reporting +# +# Access to Sarif reports is generally restricted (by GitHub) to members of the repository. +# +# Requires enabling `security-events: write` +# and configuring the action with `use_sarif: 1` +# +# For information on the feature, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Sarif-output + +# Minimal workflow structure: +# +# on: +# push: +# ... +# pull_request_target: +# ... +# jobs: +# # you only want the spelling job, all others should be omitted +# spelling: +# # remove `security-events: write` and `use_sarif: 1` +# # remove `experimental_apply_changes_via_bot: 1` +# ... otherwise adjust the `with:` as you wish + on: push: branches: @@ -43,12 +66,13 @@ on: pull_request_target: branches: - "**" - tags-ignore: - - "**" types: - 'opened' - 'reopened' - 'synchronize' + issue_comment: + types: + - 'created' jobs: spelling: @@ -57,10 +81,11 @@ jobs: contents: read pull-requests: read actions: read + security-events: write outputs: followup: ${{ steps.spelling.outputs.followup }} runs-on: ubuntu-latest - if: "contains(github.event_name, 'pull_request') || github.event_name == 'push'" + if: ${{ contains(github.event_name, 'pull_request') || github.event_name == 'push' }} concurrency: group: spelling-${{ github.event.pull_request.number || github.ref }} # note: If you use only_check_changed_files, you do not want cancel-in-progress @@ -68,24 +93,45 @@ jobs: steps: - name: check-spelling id: spelling - uses: check-spelling/check-spelling@v0.0.21 + uses: check-spelling/check-spelling@v0.0.22 with: config: .github/actions/spell-check - suppress_push_for_open_pull_request: 1 + suppress_push_for_open_pull_request: ${{ github.actor != 'dependabot[bot]' && 1 }} checkout: true check_file_names: 1 - spell_check_this: check-spelling/spell-check-this@prerelease + spell_check_this: microsoft/PowerToys@main post_comment: 0 use_magic_file: 1 - extra_dictionary_limit: 10 + warnings: bad-regex,binary-file,deprecated-feature,ignored-expect-variant,large-file,limited-references,no-newline-at-eof,noisy-file,non-alpha-in-dictionary,token-is-substring,unexpected-line-ending,whitespace-in-dictionary,minified-file,unsupported-configuration,no-files-to-check + experimental_apply_changes_via_bot: ${{ github.repository_owner != 'microsoft' && 1 }} + use_sarif: ${{ (!github.event.pull_request || (github.event.pull_request.head.repo.full_name == github.repository)) && 1 }} + extra_dictionary_limit: 20 extra_dictionaries: - cspell:software-terms/src/software-terms.txt + cspell:software-terms/dict/softwareTerms.txt cspell:cpp/src/stdlib-cpp.txt cspell:filetypes/filetypes.txt cspell:cpp/src/stdlib-c.txt - cspell:fullstack/fullstack.txt - cspell:html/html.txt - cspell:css/css.txt + cspell:python/src/python/python-lib.txt + cspell:lorem-ipsum/dictionary.txt + cspell:php/dict/php.txt + cspell:typescript/dict/typescript.txt + cspell:swift/src/swift.txt + cspell:fullstack/dict/fullstack.txt + cspell:node/dict/node.txt + cspell:dotnet/dict/dotnet.txt + cspell:django/dict/django.txt + cspell:python/src/python/python.txt + cspell:csharp/csharp.txt + cspell:python/src/common/extra.txt + cspell:cpp/src/compiler-msvc.txt + cspell:aws/aws.txt + cspell:golang/dict/go.txt + cspell:java/src/java.txt + cspell:html/dict/html.txt + cspell:css/dict/css.txt + cspell:k8s/dict/k8s.txt + cspell:java/src/java-terms.txt + cspell:powershell/dict/powershell.txt comment-push: name: Report (Push) @@ -97,11 +143,11 @@ jobs: if: (success() || failure()) && needs.spelling.outputs.followup && github.event_name == 'push' steps: - name: comment - uses: check-spelling/check-spelling@v0.0.21 + uses: check-spelling/check-spelling@v0.0.22 with: config: .github/actions/spell-check checkout: true - spell_check_this: check-spelling/spell-check-this@prerelease + spell_check_this: microsoft/PowerToys@main task: ${{ needs.spelling.outputs.followup }} comment-pr: @@ -110,13 +156,39 @@ jobs: runs-on: ubuntu-latest needs: spelling permissions: + contents: read pull-requests: write if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request') steps: - name: comment - uses: check-spelling/check-spelling@v0.0.21 + uses: check-spelling/check-spelling@v0.0.22 with: config: .github/actions/spell-check checkout: true spell_check_this: check-spelling/spell-check-this@prerelease task: ${{ needs.spelling.outputs.followup }} + experimental_apply_changes_via_bot: ${{ github.repository_owner != 'microsoft' && 1 }} + + update: + name: Update PR + permissions: + contents: write + pull-requests: write + actions: read + runs-on: ubuntu-latest + if: ${{ + github.repository_owner != 'microsoft' && + github.event_name == 'issue_comment' && + github.event.issue.pull_request && + contains(github.event.comment.body, '@check-spelling-bot apply') + }} + concurrency: + group: spelling-update-${{ github.event.issue.number }} + cancel-in-progress: false + steps: + - name: apply spelling updates + uses: check-spelling/check-spelling@v0.0.22 + with: + experimental_apply_changes_via_bot: ${{ github.repository_owner != 'microsoft' && 1 }} + checkout: true + ssh_key: "${{ secrets.CHECK_SPELLING }}" diff --git a/.pipelines/ESRPSigning_core.json b/.pipelines/ESRPSigning_core.json index d217b2dd1e..73bc45ed03 100644 --- a/.pipelines/ESRPSigning_core.json +++ b/.pipelines/ESRPSigning_core.json @@ -73,6 +73,12 @@ "PowerToys.PdfThumbnailProviderCpp.dll", "PowerToys.powerpreview.dll", "PowerToys.PreviewHandlerCommon.dll", + "PowerToys.QoiPreviewHandler.dll", + "PowerToys.QoiPreviewHandler.exe", + "PowerToys.QoiPreviewHandlerCpp.dll", + "PowerToys.QoiThumbnailProvider.dll", + "PowerToys.QoiThumbnailProvider.exe", + "PowerToys.QoiThumbnailProviderCpp.dll", "PowerToys.StlThumbnailProvider.dll", "PowerToys.StlThumbnailProvider.exe", "PowerToys.StlThumbnailProviderCpp.dll", @@ -289,6 +295,7 @@ "ColorCode.Core.dll", "ColorCode.UWP.dll", "UnitsNet.dll", + "UtfUnknown.dll", "Wpf.Ui.dll" ], "SigningInfo": { diff --git a/.pipelines/ci/ci.yml b/.pipelines/ci/ci.yml index cab3c045fa..982487c435 100644 --- a/.pipelines/ci/ci.yml +++ b/.pipelines/ci/ci.yml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/azure-pipelines-vscode/main/service-schema.json trigger: batch: true branches: @@ -9,21 +10,27 @@ trigger: - doc/* - temp/* - tools/* + - '**.md' pr: branches: include: - main - stable - + paths: + exclude: + - '**.md' + - doc + # 0.0.yyMM.dd## # 0.0.1904.0900 name: 0.0.$(Date:yyMM).$(Date:dd)$(Rev:rr) jobs: + - template: ./templates/build-powertoys-precheck.yml - template: ./templates/build-powertoys-ci.yml parameters: - platform: x64 + platform: x64 - template: ./templates/build-powertoys-ci.yml parameters: - platform: arm64 + platform: arm64 \ No newline at end of file diff --git a/.pipelines/ci/templates/build-powertoys-ci.yml b/.pipelines/ci/templates/build-powertoys-ci.yml index c21ecd2712..c5f4882cd8 100644 --- a/.pipelines/ci/templates/build-powertoys-ci.yml +++ b/.pipelines/ci/templates/build-powertoys-ci.yml @@ -6,6 +6,8 @@ parameters: jobs: - job: Build${{ parameters.platform }}${{ parameters.configuration }} displayName: Build ${{ parameters.platform }} ${{ parameters.configuration }} + dependsOn: Precheck + condition: and(succeeded(),ne(dependencies.Precheck.outputs['verifyBuildRequest.skipBuild'], 'Yes')) variables: BuildConfiguration: ${{ parameters.configuration }} BuildPlatform: ${{ parameters.platform }} @@ -29,4 +31,4 @@ jobs: # when we renamed our main branch. - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 displayName: 'Component Detection' - condition: and(succeededOrFailed(), not(eq(variables['Build.Reason'], 'PullRequest'))) + condition: and(succeededOrFailed(), not(eq(variables['Build.Reason'], 'PullRequest'))) \ No newline at end of file diff --git a/.pipelines/ci/templates/build-powertoys-precheck.yml b/.pipelines/ci/templates/build-powertoys-precheck.yml new file mode 100644 index 0000000000..14f1b01b3b --- /dev/null +++ b/.pipelines/ci/templates/build-powertoys-precheck.yml @@ -0,0 +1,38 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/azure-pipelines-vscode/master/service-schema.json +jobs: +- job: Precheck + pool: + demands: ImageOverride -equals SHINE-VS17-Latest + ${{ if eq(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}: + name: SHINE-OSS-L + ${{ if ne(variables['System.CollectionUri'], 'https://dev.azure.com/ms/') }}: + name: SHINE-INT-L + steps: + - checkout: none + + - task: PowerShell@2 + displayName: Verify Build Request + inputs: + targetType: 'inline' + script: | + try { + # Try based on pull request first + $pullRequestNumber = "$(system.pullRequest.pullRequestNumber)"; + $gitHubPullRequest = Invoke-RestMethod -Method Get "https://api.github.com/repos/microsoft/PowerToys/pulls/$pullRequestNumber/files" + # If there are no files updated in the commit that are .md, set skipBuild variable + if(([array]($gitHubPullRequest.filename) -notmatch ".md|.txt").Length -eq 0) { + Write-Host '##vso[task.setvariable variable=skipBuild;isOutput=true]Yes' + Write-Host 'Skipping Build' + } + } + catch { + # Fall back to the latest commit otherwise. + $commit = "$(build.sourceVersion)"; + $gitHubCommit = Invoke-RestMethod -Method Get "https://api.github.com/repos/microsoft/PowerToys/commits/$commit" + if(([array]($githubCommit.files.filename) -notmatch ".md|.txt").Length -eq 0) { + Write-Host '##vso[task.setvariable variable=skipBuild;isOutput=true]Yes' + Write-Host 'Skipping Build' + } + } + pwsh: true + name: verifyBuildRequest diff --git a/.pipelines/ci/templates/build-powertoys-steps.yml b/.pipelines/ci/templates/build-powertoys-steps.yml index ba7aec6f36..23cd60f191 100644 --- a/.pipelines/ci/templates/build-powertoys-steps.yml +++ b/.pipelines/ci/templates/build-powertoys-steps.yml @@ -195,8 +195,10 @@ steps: **\UnitTests-GcodeThumbnailProvider.dll **\UnitTests-StlThumbnailProvider.dll **\UnitTests-PdfThumbnailProvider.dll + **\UnitTests-QoiThumbnailProvider.dll **\Settings.UI.UnitTests.dll **\UnitTests-GcodePreviewHandler.dll + **\UnitTests-QoiPreviewHandler.dll **\UnitTests-FancyZonesEditor.dll **\UnitTests-PdfPreviewHandler.dll **\UnitTests-PreviewHandlerCommon.dll diff --git a/.pipelines/versionAndSignCheck.ps1 b/.pipelines/versionAndSignCheck.ps1 index 8cc31f7567..42b66ed8bc 100644 --- a/.pipelines/versionAndSignCheck.ps1 +++ b/.pipelines/versionAndSignCheck.ps1 @@ -25,7 +25,11 @@ $nullVersionExceptions = @( "codicon.ttf", "e_sqlite3.dll", "vcamp140_app.dll", + "vcruntime140_app.dll", + "vcruntime140_1_app.dll", + "msvcp140_app.dll", "marshal.dll", + "Microsoft.Toolkit.Win32.UI.XamlHost.dll", "Microsoft.UI.Composition.OSSupport.dll", "Microsoft.UI.Windowing.dll", "Microsoft.UI.Xaml.Internal.dll", diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 01d1400264..43eb58e7e0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,7 +31,7 @@ Upvote the original issue by clicking its [+😊] button and hitting 👍 (+1) i ## Contributing fixes / features -Please comment on an issue to let us know you're interested in working on something before you start the work. Not only does this avoid multiple people unexpectedly working on the same thing at the same time but it enables us to make sure everyone is clear on what should be done to implement any new functionality. It's less work for everyone, in the long run, to establish this up front. +Please comment on [our "Would you like to contribute to PowerToys?" thread](https://github.com/microsoft/PowerToys/issues/28769) to let us know you're interested in working on something before you start the work. Not only does this avoid multiple people unexpectedly working on the same thing at the same time but it enables us to make sure everyone is clear on what should be done to implement any new functionality. It's less work for everyone, in the long run, to establish this up front. ### Localization issues diff --git a/Directory.Build.props b/Directory.Build.props index 3481bdaba7..9f8159a6d9 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -12,6 +12,7 @@ true Recommended <_SkipUpgradeNetAnalyzersNuGetWarning>true + false $(Platform) diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000000..66a8733ce6 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props index 046dabaff8..93130d8eb7 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -34,11 +34,11 @@ - + - + @@ -67,16 +67,18 @@ + + - + - + \ No newline at end of file diff --git a/NOTICE.md b/NOTICE.md index fae2e827ed..0c1780b2ba 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -8,6 +8,7 @@ This software incorporates material from third parties. - PowerToys Run - Installer/Runner - Measure tool +- Peek ## Utility: Color Picker @@ -95,6 +96,484 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +### UTF Unknown + +We use the UTF.Unknown NuGet package for detecting encoding in text/code files. + +**Source**: https://github.com/CharsetDetector/UTF-unknown + +``` + MOZILLA PUBLIC LICENSE + Version 1.1 + + --------------- + +1. Definitions. + + 1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. + + 1.1. "Contributor" means each entity that creates or contributes to + the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications + made by that particular Contributor. + + 1.3. "Covered Code" means the Original Code or Modifications or the + combination of the Original Code and Modifications, in each case + including portions thereof. + + 1.4. "Electronic Distribution Mechanism" means a mechanism generally + accepted in the software development community for the electronic + transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source + Code. + + 1.6. "Initial Developer" means the individual or entity identified + as the Initial Developer in the Source Code notice required by Exhibit + A. + + 1.7. "Larger Work" means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + + 1.8. "License" means this document. + + 1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + A. Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. + + B. Any new file that contains any part of the Original Code or + previous Modifications. + + 1.10. "Original Code" means Source Code of computer software code + which is described in the Source Code notice required by Exhibit A as + Original Code, and which, at the time of its release under this + License is not already Covered Code governed by this License. + + 1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, + and apparatus claims, in any patent Licensable by grantor. + + 1.11. "Source Code" means the preferred form of the Covered Code for + making modifications to it, including all modules it contains, plus + any associated interface definition files, scripts used to control + compilation and installation of an Executable, or source code + differential comparisons against either the Original Code or another + well known, available Covered Code of the Contributor's choice. The + Source Code can be in a compressed or archival form, provided the + appropriate decompression or de-archiving software is widely available + for no charge. + + 1.12. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License or a future version of this License issued under Section 6.1. + For legal entities, "You" includes any entity which controls, is + controlled by, or is under common control with You. For purposes of + this definition, "control" means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty percent + (50%) of the outstanding shares or beneficial ownership of such + entity. + +2. Source Code License. + + 2.1. The Initial Developer Grant. + The Initial Developer hereby grants You a world-wide, royalty-free, + non-exclusive license, subject to third party intellectual property + claims: + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original + Code (or portions thereof) with or without Modifications, and/or + as part of a Larger Work; and + + (b) under Patents Claims infringed by the making, using or + selling of Original Code, to make, have made, use, practice, + sell, and offer for sale, and/or otherwise dispose of the + Original Code (or portions thereof). + + (c) the licenses granted in this Section 2.1(a) and (b) are + effective on the date Initial Developer first distributes + Original Code under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) + separate from the Original Code; or 3) for infringements caused + by: i) the modification of the Original Code or ii) the + combination of the Original Code with other software or devices. + + 2.2. Contributor Grant. + Subject to third party intellectual property claims, each Contributor + hereby grants You a world-wide, royalty-free, non-exclusive license + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor, to use, reproduce, modify, + display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an + unmodified basis, with other Modifications, as Covered Code + and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either alone + and/or in combination with its Contributor Version (or portions + of such combination), to make, use, sell, offer for sale, have + made, and/or otherwise dispose of: 1) Modifications made by that + Contributor (or portions thereof); and 2) the combination of + Modifications made by that Contributor with its Contributor + Version (or portions of such combination). + + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of + the Covered Code. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: 1) for any code that Contributor has deleted from the + Contributor Version; 2) separate from the Contributor Version; + 3) for infringements caused by: i) third party modifications of + Contributor Version or ii) the combination of Modifications made + by that Contributor with other software (except as part of the + Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by + that Contributor. + +3. Distribution Obligations. + + 3.1. Application of License. + The Modifications which You create or to which You contribute are + governed by the terms of this License, including without limitation + Section 2.2. The Source Code version of Covered Code may be + distributed only under the terms of this License or a future version + of this License released under Section 6.1, and You must include a + copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code + version that alters or restricts the applicable version of this + License or the recipients' rights hereunder. However, You may include + an additional document offering the additional rights described in + Section 3.5. + + 3.2. Availability of Source Code. + Any Modification which You create or to which You contribute must be + made available in Source Code form under the terms of this License + either on the same media as an Executable version or via an accepted + Electronic Distribution Mechanism to anyone to whom you made an + Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) + months after the date it initially became available, or at least six + (6) months after a subsequent version of that particular Modification + has been made available to such recipients. You are responsible for + ensuring that the Source Code version remains available even if the + Electronic Distribution Mechanism is maintained by a third party. + + 3.3. Description of Modifications. + You must cause all Covered Code to which You contribute to contain a + file documenting the changes You made to create that Covered Code and + the date of any change. You must include a prominent statement that + the Modification is derived, directly or indirectly, from Original + Code provided by the Initial Developer and including the name of the + Initial Developer in (a) the Source Code, and (b) in any notice in an + Executable version or related documentation in which You describe the + origin or ownership of the Covered Code. + + 3.4. Intellectual Property Matters + (a) Third Party Claims. + If Contributor has knowledge that a license under a third party's + intellectual property rights is required to exercise the rights + granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code + distribution titled "LEGAL" which describes the claim and the + party making the claim in sufficient detail that a recipient will + know whom to contact. If Contributor obtains such knowledge after + the Modification is made available as described in Section 3.2, + Contributor shall promptly modify the LEGAL file in all copies + Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) + reasonably calculated to inform those who received the Covered + Code that new knowledge has been obtained. + + (b) Contributor APIs. + If Contributor's Modifications include an application programming + interface and Contributor has knowledge of patent licenses which + are reasonably necessary to implement that API, Contributor must + also include this information in the LEGAL file. + + (c) Representations. + Contributor represents that, except as disclosed pursuant to + Section 3.4(a) above, Contributor believes that Contributor's + Modifications are Contributor's original creation(s) and/or + Contributor has sufficient rights to grant the rights conveyed by + this License. + + 3.5. Required Notices. + You must duplicate the notice in Exhibit A in each file of the Source + Code. If it is not possible to put such notice in a particular Source + Code file due to its structure, then You must include such notice in a + location (such as a relevant directory) where a user would be likely + to look for such a notice. If You created one or more Modification(s) + You may add your name as a Contributor to the notice described in + Exhibit A. You must also duplicate this License in any documentation + for the Source Code where You describe recipients' rights or ownership + rights relating to Covered Code. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear than + any such warranty, support, indemnity or liability obligation is + offered by You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of warranty, + support, indemnity or liability terms You offer. + + 3.6. Distribution of Executable Versions. + You may distribute Covered Code in Executable form only if the + requirements of Section 3.1-3.5 have been met for that Covered Code, + and if You include a notice stating that the Source Code version of + the Covered Code is available under the terms of this License, + including a description of how and where You have fulfilled the + obligations of Section 3.2. The notice must be conspicuously included + in any notice in an Executable version, related documentation or + collateral in which You describe recipients' rights relating to the + Covered Code. You may distribute the Executable version of Covered + Code or ownership rights under a license of Your choice, which may + contain terms different from this License, provided that You are in + compliance with the terms of this License and that the license for the + Executable version does not attempt to limit or alter the recipient's + rights in the Source Code version from the rights set forth in this + License. If You distribute the Executable version under a different + license You must make it absolutely clear that any terms which differ + from this License are offered by You alone, not by the Initial + Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such + terms You offer. + + 3.7. Larger Works. + You may create a Larger Work by combining Covered Code with other code + not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Code. + +4. Inability to Comply Due to Statute or Regulation. + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description + must be included in the LEGAL file described in Section 3.4 and must + be included with all distributions of the Source Code. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Application of this License. + + This License applies to code to which the Initial Developer has + attached the notice in Exhibit A and to related Covered Code. + +6. Versions of the License. + + 6.1. New Versions. + Netscape Communications Corporation ("Netscape") may publish revised + and/or new versions of the License from time to time. Each version + will be given a distinguishing version number. + + 6.2. Effect of New Versions. + Once Covered Code has been published under a particular version of the + License, You may always continue to use it under the terms of that + version. You may also choose to use such Covered Code under the terms + of any subsequent version of the License published by Netscape. No one + other than Netscape has the right to modify the terms applicable to + Covered Code created under this License. + + 6.3. Derivative Works. + If You create or use a modified version of this License (which you may + only do in order to apply it to code which is not already Covered Code + governed by this License), You must (a) rename Your license so that + the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", + "MPL", "NPL" or any confusingly similar phrase do not appear in your + license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license + contains terms which differ from the Mozilla Public License and + Netscape Public License. (Filling in the name of the Initial + Developer, Original Code or Contributor in the notice described in + Exhibit A shall not of themselves be deemed to be modifications of + this License.) + +7. DISCLAIMER OF WARRANTY. + + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE + IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, + YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE + COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER + OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +8. TERMINATION. + + 8.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall + survive any termination of this License. Provisions which, by their + nature, must remain in effect beyond the termination of this License + shall survive. + + 8.2. If You initiate litigation by asserting a patent infringement + claim (excluding declatory judgment actions) against Initial Developer + or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + + (a) such Participant's Contributor Version directly or indirectly + infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License + shall, upon 60 days notice from Participant terminate prospectively, + unless if within 60 days after receipt of notice You either: (i) + agree in writing to pay Participant a mutually agreeable reasonable + royalty for Your past and future use of Modifications made by such + Participant, or (ii) withdraw Your litigation claim with respect to + the Contributor Version against such Participant. If within 60 days + of notice, a reasonable royalty and payment arrangement are not + mutually agreed upon in writing by the parties or the litigation claim + is not withdrawn, the rights granted by Participant to You under + Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + + (b) any software, hardware, or device, other than such Participant's + Contributor Version, directly or indirectly infringes any patent, then + any rights granted to You by such Participant under Sections 2.1(b) + and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that + Participant. + + 8.3. If You assert a patent infringement claim against Participant + alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as + by license or settlement) prior to the initiation of patent + infringement litigation, then the reasonable value of the licenses + granted by such Participant under Sections 2.1 or 2.2 shall be taken + into account in determining the amount or value of any payment or + license. + + 8.4. In the event of termination under Sections 8.1 or 8.2 above, + all end user license agreements (excluding distributors and resellers) + which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination. + +9. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, + OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR + ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY + CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, + WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE + EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO + THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +10. U.S. GOVERNMENT END USERS. + + The Covered Code is a "commercial item," as that term is defined in + 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer + software" and "commercial computer software documentation," as such + terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 + C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), + all U.S. Government End Users acquire Covered Code with only those + rights set forth herein. + +11. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed by + California law provisions (except to the extent applicable law, if + any, provides otherwise), excluding its conflict-of-law provisions. + With respect to disputes in which at least one party is a citizen of, + or an entity chartered or registered to do business in the United + States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern + District of California, with venue lying in Santa Clara County, + California, with the losing party responsible for costs, including + without limitation, court costs and reasonable attorneys' fees and + expenses. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. + Any law or regulation which provides that the language of a contract + shall be construed against the drafter shall not apply to this + License. + +12. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, + out of its utilization of rights under this License and You agree to + work with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + +13. MULTIPLE-LICENSED CODE. + + Initial Developer may designate portions of the Covered Code as + "Multiple-Licensed". "Multiple-Licensed" means that the Initial + Developer permits you to utilize portions of the Covered Code under + Your choice of the NPL or the alternative licenses, if any, specified + by the Initial Developer in the file described in Exhibit A. + +EXHIBIT A -Mozilla Public License. + + The contents of this file are subject to the Mozilla Public License + Version 1.1 (the "License"); you may not use this file except in + compliance with the License. You may obtain a copy of the License at + http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + License for the specific language governing rights and limitations + under the License. + + The Original Code is ______________________________________. + + The Initial Developer of the Original Code is ________________________. + Portions created by ______________________ are Copyright (C) ______ + _______________________. All Rights Reserved. + + Contributor(s): ______________________________________. + + Alternatively, the contents of this file may be used under the terms + of the _____ license (the "[___] License"), in which case the + provisions of [______] License are applicable instead of those + above. If you wish to allow use of your version of this file only + under the terms of the [____] License and not to allow others to use + your version of this file under the MPL, indicate your decision by + deleting the provisions above and replace them with the notice and + other provisions required by the [___] License. If you do not delete + the provisions above, a recipient may use your version of this file + under either the MPL or the [___] License." + + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.] +``` + ## Utility: ImageResizer ### Brice Lams's Image Resizer License @@ -307,6 +786,486 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +## Utility: Peek + +### UTF Unknown + +We use the UTF.Unknown NuGet package for detecting encoding in text/code files. + +**Source**: https://github.com/CharsetDetector/UTF-unknown + +``` + MOZILLA PUBLIC LICENSE + Version 1.1 + + --------------- + +1. Definitions. + + 1.0.1. "Commercial Use" means distribution or otherwise making the + Covered Code available to a third party. + + 1.1. "Contributor" means each entity that creates or contributes to + the creation of Modifications. + + 1.2. "Contributor Version" means the combination of the Original + Code, prior Modifications used by a Contributor, and the Modifications + made by that particular Contributor. + + 1.3. "Covered Code" means the Original Code or Modifications or the + combination of the Original Code and Modifications, in each case + including portions thereof. + + 1.4. "Electronic Distribution Mechanism" means a mechanism generally + accepted in the software development community for the electronic + transfer of data. + + 1.5. "Executable" means Covered Code in any form other than Source + Code. + + 1.6. "Initial Developer" means the individual or entity identified + as the Initial Developer in the Source Code notice required by Exhibit + A. + + 1.7. "Larger Work" means a work which combines Covered Code or + portions thereof with code not governed by the terms of this License. + + 1.8. "License" means this document. + + 1.8.1. "Licensable" means having the right to grant, to the maximum + extent possible, whether at the time of the initial grant or + subsequently acquired, any and all of the rights conveyed herein. + + 1.9. "Modifications" means any addition to or deletion from the + substance or structure of either the Original Code or any previous + Modifications. When Covered Code is released as a series of files, a + Modification is: + A. Any addition to or deletion from the contents of a file + containing Original Code or previous Modifications. + + B. Any new file that contains any part of the Original Code or + previous Modifications. + + 1.10. "Original Code" means Source Code of computer software code + which is described in the Source Code notice required by Exhibit A as + Original Code, and which, at the time of its release under this + License is not already Covered Code governed by this License. + + 1.10.1. "Patent Claims" means any patent claim(s), now owned or + hereafter acquired, including without limitation, method, process, + and apparatus claims, in any patent Licensable by grantor. + + 1.11. "Source Code" means the preferred form of the Covered Code for + making modifications to it, including all modules it contains, plus + any associated interface definition files, scripts used to control + compilation and installation of an Executable, or source code + differential comparisons against either the Original Code or another + well known, available Covered Code of the Contributor's choice. The + Source Code can be in a compressed or archival form, provided the + appropriate decompression or de-archiving software is widely available + for no charge. + + 1.12. "You" (or "Your") means an individual or a legal entity + exercising rights under, and complying with all of the terms of, this + License or a future version of this License issued under Section 6.1. + For legal entities, "You" includes any entity which controls, is + controlled by, or is under common control with You. For purposes of + this definition, "control" means (a) the power, direct or indirect, + to cause the direction or management of such entity, whether by + contract or otherwise, or (b) ownership of more than fifty percent + (50%) of the outstanding shares or beneficial ownership of such + entity. + +2. Source Code License. + + 2.1. The Initial Developer Grant. + The Initial Developer hereby grants You a world-wide, royalty-free, + non-exclusive license, subject to third party intellectual property + claims: + (a) under intellectual property rights (other than patent or + trademark) Licensable by Initial Developer to use, reproduce, + modify, display, perform, sublicense and distribute the Original + Code (or portions thereof) with or without Modifications, and/or + as part of a Larger Work; and + + (b) under Patents Claims infringed by the making, using or + selling of Original Code, to make, have made, use, practice, + sell, and offer for sale, and/or otherwise dispose of the + Original Code (or portions thereof). + + (c) the licenses granted in this Section 2.1(a) and (b) are + effective on the date Initial Developer first distributes + Original Code under the terms of this License. + + (d) Notwithstanding Section 2.1(b) above, no patent license is + granted: 1) for code that You delete from the Original Code; 2) + separate from the Original Code; or 3) for infringements caused + by: i) the modification of the Original Code or ii) the + combination of the Original Code with other software or devices. + + 2.2. Contributor Grant. + Subject to third party intellectual property claims, each Contributor + hereby grants You a world-wide, royalty-free, non-exclusive license + + (a) under intellectual property rights (other than patent or + trademark) Licensable by Contributor, to use, reproduce, modify, + display, perform, sublicense and distribute the Modifications + created by such Contributor (or portions thereof) either on an + unmodified basis, with other Modifications, as Covered Code + and/or as part of a Larger Work; and + + (b) under Patent Claims infringed by the making, using, or + selling of Modifications made by that Contributor either alone + and/or in combination with its Contributor Version (or portions + of such combination), to make, use, sell, offer for sale, have + made, and/or otherwise dispose of: 1) Modifications made by that + Contributor (or portions thereof); and 2) the combination of + Modifications made by that Contributor with its Contributor + Version (or portions of such combination). + + (c) the licenses granted in Sections 2.2(a) and 2.2(b) are + effective on the date Contributor first makes Commercial Use of + the Covered Code. + + (d) Notwithstanding Section 2.2(b) above, no patent license is + granted: 1) for any code that Contributor has deleted from the + Contributor Version; 2) separate from the Contributor Version; + 3) for infringements caused by: i) third party modifications of + Contributor Version or ii) the combination of Modifications made + by that Contributor with other software (except as part of the + Contributor Version) or other devices; or 4) under Patent Claims + infringed by Covered Code in the absence of Modifications made by + that Contributor. + +3. Distribution Obligations. + + 3.1. Application of License. + The Modifications which You create or to which You contribute are + governed by the terms of this License, including without limitation + Section 2.2. The Source Code version of Covered Code may be + distributed only under the terms of this License or a future version + of this License released under Section 6.1, and You must include a + copy of this License with every copy of the Source Code You + distribute. You may not offer or impose any terms on any Source Code + version that alters or restricts the applicable version of this + License or the recipients' rights hereunder. However, You may include + an additional document offering the additional rights described in + Section 3.5. + + 3.2. Availability of Source Code. + Any Modification which You create or to which You contribute must be + made available in Source Code form under the terms of this License + either on the same media as an Executable version or via an accepted + Electronic Distribution Mechanism to anyone to whom you made an + Executable version available; and if made available via Electronic + Distribution Mechanism, must remain available for at least twelve (12) + months after the date it initially became available, or at least six + (6) months after a subsequent version of that particular Modification + has been made available to such recipients. You are responsible for + ensuring that the Source Code version remains available even if the + Electronic Distribution Mechanism is maintained by a third party. + + 3.3. Description of Modifications. + You must cause all Covered Code to which You contribute to contain a + file documenting the changes You made to create that Covered Code and + the date of any change. You must include a prominent statement that + the Modification is derived, directly or indirectly, from Original + Code provided by the Initial Developer and including the name of the + Initial Developer in (a) the Source Code, and (b) in any notice in an + Executable version or related documentation in which You describe the + origin or ownership of the Covered Code. + + 3.4. Intellectual Property Matters + (a) Third Party Claims. + If Contributor has knowledge that a license under a third party's + intellectual property rights is required to exercise the rights + granted by such Contributor under Sections 2.1 or 2.2, + Contributor must include a text file with the Source Code + distribution titled "LEGAL" which describes the claim and the + party making the claim in sufficient detail that a recipient will + know whom to contact. If Contributor obtains such knowledge after + the Modification is made available as described in Section 3.2, + Contributor shall promptly modify the LEGAL file in all copies + Contributor makes available thereafter and shall take other steps + (such as notifying appropriate mailing lists or newsgroups) + reasonably calculated to inform those who received the Covered + Code that new knowledge has been obtained. + + (b) Contributor APIs. + If Contributor's Modifications include an application programming + interface and Contributor has knowledge of patent licenses which + are reasonably necessary to implement that API, Contributor must + also include this information in the LEGAL file. + + (c) Representations. + Contributor represents that, except as disclosed pursuant to + Section 3.4(a) above, Contributor believes that Contributor's + Modifications are Contributor's original creation(s) and/or + Contributor has sufficient rights to grant the rights conveyed by + this License. + + 3.5. Required Notices. + You must duplicate the notice in Exhibit A in each file of the Source + Code. If it is not possible to put such notice in a particular Source + Code file due to its structure, then You must include such notice in a + location (such as a relevant directory) where a user would be likely + to look for such a notice. If You created one or more Modification(s) + You may add your name as a Contributor to the notice described in + Exhibit A. You must also duplicate this License in any documentation + for the Source Code where You describe recipients' rights or ownership + rights relating to Covered Code. You may choose to offer, and to + charge a fee for, warranty, support, indemnity or liability + obligations to one or more recipients of Covered Code. However, You + may do so only on Your own behalf, and not on behalf of the Initial + Developer or any Contributor. You must make it absolutely clear than + any such warranty, support, indemnity or liability obligation is + offered by You alone, and You hereby agree to indemnify the Initial + Developer and every Contributor for any liability incurred by the + Initial Developer or such Contributor as a result of warranty, + support, indemnity or liability terms You offer. + + 3.6. Distribution of Executable Versions. + You may distribute Covered Code in Executable form only if the + requirements of Section 3.1-3.5 have been met for that Covered Code, + and if You include a notice stating that the Source Code version of + the Covered Code is available under the terms of this License, + including a description of how and where You have fulfilled the + obligations of Section 3.2. The notice must be conspicuously included + in any notice in an Executable version, related documentation or + collateral in which You describe recipients' rights relating to the + Covered Code. You may distribute the Executable version of Covered + Code or ownership rights under a license of Your choice, which may + contain terms different from this License, provided that You are in + compliance with the terms of this License and that the license for the + Executable version does not attempt to limit or alter the recipient's + rights in the Source Code version from the rights set forth in this + License. If You distribute the Executable version under a different + license You must make it absolutely clear that any terms which differ + from this License are offered by You alone, not by the Initial + Developer or any Contributor. You hereby agree to indemnify the + Initial Developer and every Contributor for any liability incurred by + the Initial Developer or such Contributor as a result of any such + terms You offer. + + 3.7. Larger Works. + You may create a Larger Work by combining Covered Code with other code + not governed by the terms of this License and distribute the Larger + Work as a single product. In such a case, You must make sure the + requirements of this License are fulfilled for the Covered Code. + +4. Inability to Comply Due to Statute or Regulation. + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Code due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description + must be included in the LEGAL file described in Section 3.4 and must + be included with all distributions of the Source Code. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Application of this License. + + This License applies to code to which the Initial Developer has + attached the notice in Exhibit A and to related Covered Code. + +6. Versions of the License. + + 6.1. New Versions. + Netscape Communications Corporation ("Netscape") may publish revised + and/or new versions of the License from time to time. Each version + will be given a distinguishing version number. + + 6.2. Effect of New Versions. + Once Covered Code has been published under a particular version of the + License, You may always continue to use it under the terms of that + version. You may also choose to use such Covered Code under the terms + of any subsequent version of the License published by Netscape. No one + other than Netscape has the right to modify the terms applicable to + Covered Code created under this License. + + 6.3. Derivative Works. + If You create or use a modified version of this License (which you may + only do in order to apply it to code which is not already Covered Code + governed by this License), You must (a) rename Your license so that + the phrases "Mozilla", "MOZILLAPL", "MOZPL", "Netscape", + "MPL", "NPL" or any confusingly similar phrase do not appear in your + license (except to note that your license differs from this License) + and (b) otherwise make it clear that Your version of the license + contains terms which differ from the Mozilla Public License and + Netscape Public License. (Filling in the name of the Initial + Developer, Original Code or Contributor in the notice described in + Exhibit A shall not of themselves be deemed to be modifications of + this License.) + +7. DISCLAIMER OF WARRANTY. + + COVERED CODE IS PROVIDED UNDER THIS LICENSE ON AN "AS IS" BASIS, + WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, + WITHOUT LIMITATION, WARRANTIES THAT THE COVERED CODE IS FREE OF + DEFECTS, MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE OR NON-INFRINGING. + THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE COVERED CODE + IS WITH YOU. SHOULD ANY COVERED CODE PROVE DEFECTIVE IN ANY RESPECT, + YOU (NOT THE INITIAL DEVELOPER OR ANY OTHER CONTRIBUTOR) ASSUME THE + COST OF ANY NECESSARY SERVICING, REPAIR OR CORRECTION. THIS DISCLAIMER + OF WARRANTY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. NO USE OF + ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT UNDER THIS DISCLAIMER. + +8. TERMINATION. + + 8.1. This License and the rights granted hereunder will terminate + automatically if You fail to comply with terms herein and fail to cure + such breach within 30 days of becoming aware of the breach. All + sublicenses to the Covered Code which are properly granted shall + survive any termination of this License. Provisions which, by their + nature, must remain in effect beyond the termination of this License + shall survive. + + 8.2. If You initiate litigation by asserting a patent infringement + claim (excluding declatory judgment actions) against Initial Developer + or a Contributor (the Initial Developer or Contributor against whom + You file such action is referred to as "Participant") alleging that: + + (a) such Participant's Contributor Version directly or indirectly + infringes any patent, then any and all rights granted by such + Participant to You under Sections 2.1 and/or 2.2 of this License + shall, upon 60 days notice from Participant terminate prospectively, + unless if within 60 days after receipt of notice You either: (i) + agree in writing to pay Participant a mutually agreeable reasonable + royalty for Your past and future use of Modifications made by such + Participant, or (ii) withdraw Your litigation claim with respect to + the Contributor Version against such Participant. If within 60 days + of notice, a reasonable royalty and payment arrangement are not + mutually agreed upon in writing by the parties or the litigation claim + is not withdrawn, the rights granted by Participant to You under + Sections 2.1 and/or 2.2 automatically terminate at the expiration of + the 60 day notice period specified above. + + (b) any software, hardware, or device, other than such Participant's + Contributor Version, directly or indirectly infringes any patent, then + any rights granted to You by such Participant under Sections 2.1(b) + and 2.2(b) are revoked effective as of the date You first made, used, + sold, distributed, or had made, Modifications made by that + Participant. + + 8.3. If You assert a patent infringement claim against Participant + alleging that such Participant's Contributor Version directly or + indirectly infringes any patent where such claim is resolved (such as + by license or settlement) prior to the initiation of patent + infringement litigation, then the reasonable value of the licenses + granted by such Participant under Sections 2.1 or 2.2 shall be taken + into account in determining the amount or value of any payment or + license. + + 8.4. In the event of termination under Sections 8.1 or 8.2 above, + all end user license agreements (excluding distributors and resellers) + which have been validly granted by You or any distributor hereunder + prior to termination shall survive termination. + +9. LIMITATION OF LIABILITY. + + UNDER NO CIRCUMSTANCES AND UNDER NO LEGAL THEORY, WHETHER TORT + (INCLUDING NEGLIGENCE), CONTRACT, OR OTHERWISE, SHALL YOU, THE INITIAL + DEVELOPER, ANY OTHER CONTRIBUTOR, OR ANY DISTRIBUTOR OF COVERED CODE, + OR ANY SUPPLIER OF ANY OF SUCH PARTIES, BE LIABLE TO ANY PERSON FOR + ANY INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY + CHARACTER INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, + WORK STOPPAGE, COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER + COMMERCIAL DAMAGES OR LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN + INFORMED OF THE POSSIBILITY OF SUCH DAMAGES. THIS LIMITATION OF + LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR PERSONAL INJURY + RESULTING FROM SUCH PARTY'S NEGLIGENCE TO THE EXTENT APPLICABLE LAW + PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE + EXCLUSION OR LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO + THIS EXCLUSION AND LIMITATION MAY NOT APPLY TO YOU. + +10. U.S. GOVERNMENT END USERS. + + The Covered Code is a "commercial item," as that term is defined in + 48 C.F.R. 2.101 (Oct. 1995), consisting of "commercial computer + software" and "commercial computer software documentation," as such + terms are used in 48 C.F.R. 12.212 (Sept. 1995). Consistent with 48 + C.F.R. 12.212 and 48 C.F.R. 227.7202-1 through 227.7202-4 (June 1995), + all U.S. Government End Users acquire Covered Code with only those + rights set forth herein. + +11. MISCELLANEOUS. + + This License represents the complete agreement concerning subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. This License shall be governed by + California law provisions (except to the extent applicable law, if + any, provides otherwise), excluding its conflict-of-law provisions. + With respect to disputes in which at least one party is a citizen of, + or an entity chartered or registered to do business in the United + States of America, any litigation relating to this License shall be + subject to the jurisdiction of the Federal Courts of the Northern + District of California, with venue lying in Santa Clara County, + California, with the losing party responsible for costs, including + without limitation, court costs and reasonable attorneys' fees and + expenses. The application of the United Nations Convention on + Contracts for the International Sale of Goods is expressly excluded. + Any law or regulation which provides that the language of a contract + shall be construed against the drafter shall not apply to this + License. + +12. RESPONSIBILITY FOR CLAIMS. + + As between Initial Developer and the Contributors, each party is + responsible for claims and damages arising, directly or indirectly, + out of its utilization of rights under this License and You agree to + work with Initial Developer and Contributors to distribute such + responsibility on an equitable basis. Nothing herein is intended or + shall be deemed to constitute any admission of liability. + +13. MULTIPLE-LICENSED CODE. + + Initial Developer may designate portions of the Covered Code as + "Multiple-Licensed". "Multiple-Licensed" means that the Initial + Developer permits you to utilize portions of the Covered Code under + Your choice of the NPL or the alternative licenses, if any, specified + by the Initial Developer in the file described in Exhibit A. + +EXHIBIT A -Mozilla Public License. + + The contents of this file are subject to the Mozilla Public License + Version 1.1 (the "License"); you may not use this file except in + compliance with the License. You may obtain a copy of the License at + http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + License for the specific language governing rights and limitations + under the License. + + The Original Code is ______________________________________. + + The Initial Developer of the Original Code is ________________________. + Portions created by ______________________ are Copyright (C) ______ + _______________________. All Rights Reserved. + + Contributor(s): ______________________________________. + + Alternatively, the contents of this file may be used under the terms + of the _____ license (the "[___] License"), in which case the + provisions of [______] License are applicable instead of those + above. If you wish to allow use of your version of this file only + under the terms of the [____] License and not to allow others to use + your version of this file under the MPL, indicate your decision by + deleting the provisions above and replace them with the notice and + other provisions required by the [___] License. If you do not delete + the provisions above, a recipient may use your version of this file + under either the MPL or the [___] License." + + [NOTE: The text of this Exhibit A may differ slightly from the text of + the notices in the Source Code files of the Original Code. You should + use the text of this Exhibit A rather than the text found in the + Original Code Source Code for Your Modifications.] +``` + ## NuGet Packages used by PowerToys - Appium.WebDriver 4.4.5 @@ -337,10 +1296,10 @@ SOFTWARE. - Microsoft.Extensions.Logging 7.0.0 - Microsoft.NET.Test.Sdk 17.6.3 - Microsoft.Toolkit.Uwp.Notifications 7.1.2 -- Microsoft.Web.WebView2 1.0.1722.45 +- Microsoft.Web.WebView2 1.0.2088.41 - Microsoft.Windows.Compatibility 7.0.3 - Microsoft.Windows.CsWin32 0.2.46-beta -- Microsoft.Windows.CsWinRT 2.0.3 +- Microsoft.Windows.CsWinRT 2.0.4 - Microsoft.Windows.SDK.BuildTools 10.0.22621.756 - Microsoft.Windows.SDK.Contracts 10.0.19041.1 - Microsoft.WindowsAppSDK 1.4.230913002 @@ -367,10 +1326,12 @@ SOFTWARE. - System.Reactive 6.0.0-preview.9 - System.Runtime.Caching 7.0.0 - System.ServiceProcess.ServiceController 7.0.1 +- System.Text.Encoding.CodePages 7.0.0 - UnicodeInformation 2.6.0 - UnitsNet 4.145.0 +- UTF.Unknown 2.5.1 - Vanara.PInvoke.Shell32 3.4.11 - Vanara.PInvoke.User32 3.4.11 - WinUIEx 2.2.0 -- WPF-UI 3.0.0-preview.4 +- WPF-UI 3.0.0-preview.9 diff --git a/PowerToys.sln b/PowerToys.sln index 6918ee68a4..9d11bcd4e0 100644 --- a/PowerToys.sln +++ b/PowerToys.sln @@ -177,6 +177,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution src\.editorconfig = src\.editorconfig .vsconfig = .vsconfig Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets Directory.Packages.props = Directory.Packages.props Solution.props = Solution.props EndProjectSection @@ -537,7 +538,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CropAndLock", "src\modules\ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CropAndLockModuleInterface", "src\modules\CropAndLock\CropAndLockModuleInterface\CropAndLockModuleInterface.vcxproj", "{3157FA75-86CF-4EE2-8F62-C43F776493C6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTests-FancyZonesEditor", "src\modules\fancyzones\UnitTests-FancyZonesEditor\UnitTests-FancyZonesEditor.csproj", "{FC8EB78F-F061-4BD9-A3F6-507BEA965E2B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests-FancyZonesEditor", "src\modules\fancyzones\UnitTests-FancyZonesEditor\UnitTests-FancyZonesEditor.csproj", "{FC8EB78F-F061-4BD9-A3F6-507BEA965E2B}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "EnvironmentVariables", "EnvironmentVariables", "{538ED0BB-B863-4B20-98CC-BCDF7FA0B68A}" EndProject @@ -545,6 +546,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EnvironmentVariables", "src EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EnvironmentVariablesModuleInterface", "src\modules\EnvironmentVariables\EnvironmentVariablesModuleInterface\EnvironmentVariablesModuleInterface.vcxproj", "{B9420661-B0E4-4241-ABD4-4A27A1F64250}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "QoiThumbnailProviderCpp", "src\modules\previewpane\QoiThumbnailProviderCpp\QoiThumbnailProviderCpp.vcxproj", "{CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QoiThumbnailProvider", "src\modules\previewpane\QoiThumbnailProvider\QoiThumbnailProvider.csproj", "{D949EC7D-48A9-4279-95D5-078E7FD1F048}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "QoiPreviewHandlerCpp", "src\modules\previewpane\QoiPreviewHandlerCpp\QoiPreviewHandlerCpp.vcxproj", "{3BAF9C81-A194-4925-A035-5E24A5D1E542}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QoiPreviewHandler", "src\modules\previewpane\QoiPreviewHandler\QoiPreviewHandler.csproj", "{6B04803D-B418-4833-A67E-B0FC966636A5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests-QoiPreviewHandler", "src\modules\previewpane\UnitTests-QoiPreviewHandler\UnitTests-QoiPreviewHandler.csproj", "{3940AD4D-F748-4BE4-9083-85769CD553EF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests-QoiThumbnailProvider", "src\modules\previewpane\UnitTests-QoiThumbnailProvider\UnitTests-QoiThumbnailProvider.csproj", "{F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UITests-FancyZones", "src\modules\fancyzones\UITests-FancyZones\UITests-FancyZones.csproj", "{FE38FC07-1C05-4B57-ADA3-2FE2F53C6A52}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UITests-FancyZonesEditor", "src\modules\fancyzones\UITests-FancyZonesEditor\UITests-FancyZonesEditor.csproj", "{3A9A791E-94A9-49F8-8401-C11CE288D5FB}" @@ -2355,6 +2368,78 @@ Global {B9420661-B0E4-4241-ABD4-4A27A1F64250}.Release|x64.Build.0 = Release|x64 {B9420661-B0E4-4241-ABD4-4A27A1F64250}.Release|x86.ActiveCfg = Release|x64 {B9420661-B0E4-4241-ABD4-4A27A1F64250}.Release|x86.Build.0 = Release|x64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Debug|ARM64.Build.0 = Debug|ARM64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Debug|x64.ActiveCfg = Debug|x64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Debug|x64.Build.0 = Debug|x64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Debug|x86.ActiveCfg = Debug|x64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Debug|x86.Build.0 = Debug|x64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Release|ARM64.ActiveCfg = Release|ARM64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Release|ARM64.Build.0 = Release|ARM64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Release|x64.ActiveCfg = Release|x64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Release|x64.Build.0 = Release|x64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Release|x86.ActiveCfg = Release|x64 + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7}.Release|x86.Build.0 = Release|x64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Debug|ARM64.Build.0 = Debug|ARM64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Debug|x64.ActiveCfg = Debug|x64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Debug|x64.Build.0 = Debug|x64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Debug|x86.ActiveCfg = Debug|x64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Debug|x86.Build.0 = Debug|x64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Release|ARM64.ActiveCfg = Release|ARM64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Release|ARM64.Build.0 = Release|ARM64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Release|x64.ActiveCfg = Release|x64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Release|x64.Build.0 = Release|x64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Release|x86.ActiveCfg = Release|x64 + {D949EC7D-48A9-4279-95D5-078E7FD1F048}.Release|x86.Build.0 = Release|x64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Debug|ARM64.Build.0 = Debug|ARM64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Debug|x64.ActiveCfg = Debug|x64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Debug|x64.Build.0 = Debug|x64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Debug|x86.ActiveCfg = Debug|x64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Debug|x86.Build.0 = Debug|x64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Release|ARM64.ActiveCfg = Release|ARM64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Release|ARM64.Build.0 = Release|ARM64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Release|x64.ActiveCfg = Release|x64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Release|x64.Build.0 = Release|x64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Release|x86.ActiveCfg = Release|x64 + {3BAF9C81-A194-4925-A035-5E24A5D1E542}.Release|x86.Build.0 = Release|x64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Debug|ARM64.Build.0 = Debug|ARM64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Debug|x64.ActiveCfg = Debug|x64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Debug|x64.Build.0 = Debug|x64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Debug|x86.ActiveCfg = Debug|x64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Debug|x86.Build.0 = Debug|x64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Release|ARM64.ActiveCfg = Release|ARM64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Release|ARM64.Build.0 = Release|ARM64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Release|x64.ActiveCfg = Release|x64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Release|x64.Build.0 = Release|x64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Release|x86.ActiveCfg = Release|x64 + {6B04803D-B418-4833-A67E-B0FC966636A5}.Release|x86.Build.0 = Release|x64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Debug|ARM64.Build.0 = Debug|ARM64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Debug|x64.ActiveCfg = Debug|x64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Debug|x64.Build.0 = Debug|x64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Debug|x86.ActiveCfg = Debug|x64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Debug|x86.Build.0 = Debug|x64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Release|ARM64.ActiveCfg = Release|ARM64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Release|ARM64.Build.0 = Release|ARM64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Release|x64.ActiveCfg = Release|x64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Release|x64.Build.0 = Release|x64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Release|x86.ActiveCfg = Release|x64 + {3940AD4D-F748-4BE4-9083-85769CD553EF}.Release|x86.Build.0 = Release|x64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Debug|ARM64.Build.0 = Debug|ARM64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Debug|x64.ActiveCfg = Debug|x64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Debug|x64.Build.0 = Debug|x64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Debug|x86.ActiveCfg = Debug|x64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Debug|x86.Build.0 = Debug|x64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Release|ARM64.ActiveCfg = Release|ARM64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Release|ARM64.Build.0 = Release|ARM64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Release|x64.ActiveCfg = Release|x64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Release|x64.Build.0 = Release|x64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Release|x86.ActiveCfg = Release|x64 + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38}.Release|x86.Build.0 = Release|x64 {FE38FC07-1C05-4B57-ADA3-2FE2F53C6A52}.Debug|ARM64.ActiveCfg = Debug|ARM64 {FE38FC07-1C05-4B57-ADA3-2FE2F53C6A52}.Debug|ARM64.Build.0 = Debug|ARM64 {FE38FC07-1C05-4B57-ADA3-2FE2F53C6A52}.Debug|x64.ActiveCfg = Debug|x64 @@ -2577,6 +2662,12 @@ Global {538ED0BB-B863-4B20-98CC-BCDF7FA0B68A} = {4574FDD0-F61D-4376-98BF-E5A1262C11EC} {51465DA1-C18B-4B99-93E1-ECF8E0FA0CBA} = {538ED0BB-B863-4B20-98CC-BCDF7FA0B68A} {B9420661-B0E4-4241-ABD4-4A27A1F64250} = {538ED0BB-B863-4B20-98CC-BCDF7FA0B68A} + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7} = {2F305555-C296-497E-AC20-5FA1B237996A} + {D949EC7D-48A9-4279-95D5-078E7FD1F048} = {2F305555-C296-497E-AC20-5FA1B237996A} + {3BAF9C81-A194-4925-A035-5E24A5D1E542} = {2F305555-C296-497E-AC20-5FA1B237996A} + {6B04803D-B418-4833-A67E-B0FC966636A5} = {2F305555-C296-497E-AC20-5FA1B237996A} + {3940AD4D-F748-4BE4-9083-85769CD553EF} = {2F305555-C296-497E-AC20-5FA1B237996A} + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38} = {2F305555-C296-497E-AC20-5FA1B237996A} {FE38FC07-1C05-4B57-ADA3-2FE2F53C6A52} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} {3A9A791E-94A9-49F8-8401-C11CE288D5FB} = {D1D6BC88-09AE-4FB4-AD24-5DED46A791DD} EndGlobalSection diff --git a/README.md b/README.md index 84ad899111..3717194ad6 100644 --- a/README.md +++ b/README.md @@ -39,19 +39,19 @@ Microsoft PowerToys is a set of utilities for power users to tune and streamline Go to the [Microsoft PowerToys GitHub releases page][github-release-link] and click on `Assets` at the bottom to show the files available in the release. Please use the appropriate PowerToys installer that matches your machine's architecture and install scope. For most, it is `x64` and per-user. -[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=project%3Amicrosoft%2FPowerToys%2F48 -[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=project%3Amicrosoft%2FPowerToys%2F47 -[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.74.1/PowerToysUserSetup-0.74.1-x64.exe -[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.74.1/PowerToysUserSetup-0.74.1-arm64.exe -[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.74.1/PowerToysSetup-0.74.1-x64.exe -[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.74.1/PowerToysSetup-0.74.1-arm64.exe +[github-next-release-work]: https://github.com/microsoft/PowerToys/issues?q=project%3Amicrosoft%2FPowerToys%2F49 +[github-current-release-work]: https://github.com/microsoft/PowerToys/issues?q=project%3Amicrosoft%2FPowerToys%2F48 +[ptUserX64]: https://github.com/microsoft/PowerToys/releases/download/v0.75.1/PowerToysUserSetup-0.75.1-x64.exe +[ptUserArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.75.1/PowerToysUserSetup-0.75.1-arm64.exe +[ptMachineX64]: https://github.com/microsoft/PowerToys/releases/download/v0.75.1/PowerToysSetup-0.75.1-x64.exe +[ptMachineArm64]: https://github.com/microsoft/PowerToys/releases/download/v0.75.1/PowerToysSetup-0.75.1-arm64.exe | Description | Filename | sha256 hash | |----------------|----------|-------------| -| Per user - x64 | [PowerToysUserSetup-0.74.1-x64.exe][ptUserX64] | 748BF7BA33913237D36D6F48E3839D0C8035967305137A17DEFF39D775735C81 | -| Per user - ARM64 | [PowerToysUserSetup-0.74.1-arm64.exe][ptUserArm64] | F5DAA89A9CF3A2805E121085AFD056A890F241A170FAB5007AA58E2755C88C54 | -| Machine wide - x64 | [PowerToysSetup-0.74.1-x64.exe][ptMachineX64] | 298C6F4E4391BDC06E128BED86A303C3300A68EAF754B4630AF7542C78C0944A | -| Machine wide - ARM64 | [PowerToysSetup-0.74.1-arm64.exe][ptMachineArm64] | A65F3C300A48F9F81312B7FC7B306382CB87F591612D0CEC7E5C0E47E868904B | +| Per user - x64 | [PowerToysUserSetup-0.75.1-x64.exe][ptUserX64] | CFDAE52607689A695F4E4DDD7C1FE68400359AEF0D2B23C86122835E9D32A20F | +| Per user - ARM64 | [PowerToysUserSetup-0.75.1-arm64.exe][ptUserArm64] | 9BAD3EF71DEDE70445416AC7369D115FAE095152722BC4F23EE393D8A10F45CA | +| Machine wide - x64 | [PowerToysSetup-0.75.1-x64.exe][ptMachineX64] | 18FEB9377B0BA45189FFF4F89627B152DD794CCC15F005592B34A40A3EA62EA8 | +| Machine wide - ARM64 | [PowerToysSetup-0.75.1-arm64.exe][ptMachineArm64] | F5CDF5A35876A0B581F446BF728B7AC52B6B701C0850D9CEA9A1874523745CFD | This is our preferred method. @@ -97,147 +97,139 @@ For guidance on developing for PowerToys, please read the [developer docs](/doc/ Our [prioritized roadmap][roadmap] of features and utilities that the core team is focusing on. -### 0.74 - September 2023 Update +### 0.75 - October 2023 Update -In this release, we focused on stability and improvements. +In this release, we focused on new features, stability and improvements. **Highlights** - - Upgraded to Windows App SDK 1.4.1, increasing stability of WinUI3 utilities. Thanks [@dongle-the-gadget](https://github.com/dongle-the-gadget) for starting the upgrade! - - Text Extractor was upgraded to its version 2.0, with a new overlay, table mode and more Quality of Life improvements. Thanks [@TheJoeFin](https://github.com/TheJoeFin)! - - Improved FancyZones stability, fixing some layout resets and improving handling of newly created windows on Windows 11. - - Fixed many silent crashes that were reported to Watson and the user's event viewer. + - New utility: An environment variables editor with the functionality to configure profiles that can be enabled/disabled. Thanks [@niels9001](https://github.com/niels9001) for the design and UI work that made this possible! + - Settings has a new Dashboard home page, with quick access for enabling modules, short descriptions and activation methods. Thanks [@niels9001](https://github.com/niels9001) for the design and UI work that made this possible! + - Added a previewer to Peek that hosts File Explorer previewers to support every file type that a machine is currently able to preview. For example, this means that if Microsoft Office handlers are installed, Peek can preview Office files. Thanks [@dillydylann](https://github.com/dillydylann)! ### General - - Turning animations off in Windows Settings will now also turn them off in PowerToys. - - Upgraded the Windows App SDK dependency to 1.4.1. Thanks [@dongle-the-gadget](https://github.com/dongle-the-gadget) for the original 1.4.0 upgrade! - - Show in the thumbnail label and application titles when running as administrator. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! - - Upgraded the Win UI Community Toolkit dependency to 8.0. Thanks [@niels9001](https://github.com/niels9001)! - -### Awake - - - Added down-sampled variants to the application's icon. Thanks [@morriscurtis](https://github.com/morriscurtis)! + - Many typo fixes through the projects and documentation. Thanks [@brianteeman](https://github.com/brianteeman)! + - Refactored and improved the logic across utilities for bringing a window to the foreground after activation. ### Color Picker - - After adding a new color in the editor, the history will scroll the new color into view. Thanks [@peerpalo](https://github.com/peerpalo)! + - After activating Color Picker, it's now possible to cancel the session by clicking the right mouse button. Thanks [@fredso90](https://github.com/fredso90)! -### Crop and Lock - - Fixed a Crop and Lock crash that would occur when trying to reparent a window crashes the target application. An error message is shown instead. +### Environment Variables + - Added a new utility: An environment variables editor that has the functionality to configure profiles that can be enabled/disabled. Thanks [@niels9001](https://github.com/niels9001) for the design and UI work that made this possible! + - Shows in the title bar if it's running as an administrator. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! ### FancyZones - - Set the process and main thread priority to normal. - - Fixed handling newly created windows on Windows 11. - - Fixed scenarios where opening the FancyZones Editor would reset the layouts. + - Fixed an issue causing context menu pop-ups from some apps to automatically snap to a zone. (This was a hotfix for 0.74) + - Applied the fix for the context menu pop-ups to the logic that decides which windows can be snapped. + - Reworked the "Keep windows in their zones" option to include the work area and turn it on by default, fixing an incompatibility with the Copilot flyout. + - Fixed an issue causing windows to be snapped while moving to a different virtual desktop. ### File Explorer add-ons - - Optimized CPU usage for generating SVG thumbnails. - - Improved handling of Gcode Thumbnails, including JPG and QOI formats. Thanks [@pedrolamas](https://github.com/pedrolamas)! - - Better handled errors when sending telemetry, which were causing reported crashes. - - Fixed some thumbnails not being shown centered like before the optimization. + - Fixed an issue blocking some SVG files from being previewed correctly. (This was a hotfix for 0.74) + - Fixed crashes on invalid files in the STL Thumbnail generator. -### File Locksmith +### GPO - - Shows files opened by processes with PID greater than 65535. Thanks [@poke30744](https://github.com/poke30744)! - - Fixed a GDI object leak in the context menu which would crash Explorer. - -### Find My Mouse + - Added a global GPO rule that applies for all utilities unless it's overridden. Thanks [@htcfreek](https://github.com/htcfreek)! + - Added GPO rules to control which PowerToys Run plugins should be enabled/disabled by policy. Thanks [@htcfreek](https://github.com/htcfreek)! + * All plugins have to provide its plugin ID as static property in its Main method. - - Added new activation methods, including by hotkey. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! +### Image Resizer -### Hosts File Editor - - - Ignore the default ACME sample entries in the hosts file. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! - - Improved save error handling and added better error messages. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! - - Corrected a check for an error when signaling the application to start as administrator. - - Refactored the context menu. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! - - Fixed dialogs overlapping the title bar after the upgrade to Windows App SDK 1.4. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Fixed wrong .bmp file association in the registry. Thanks [@meitinger](https://github.com/meitinger)! ### Keyboard Manager - - Distinguish between the regular minus key and the numpad minus key. + - Visually distinguish between the Numpad and regular period characters in the UI. + - This utility is now disabled by default on new installations, since it requires user configuration to affect keyboard behavior. + - Fixed a typo in the Numpad Subtract key in the editor. -### Mouse Without Borders +### Mouse Highlighter - - Fixed a crash when trying to restart the application. + - Removed the lower limit of fade delay and duration, to allow better signaling of doing a double click. Thanks [@fredso90](https://github.com/fredso90)! + +### Mouse Jump + + - The process now runs in the background, for a faster activation time. Thanks [@mikeclayton](https://github.com/mikeclayton)! ### Peek - - Using Peek on HTML files will show a white background by default, similar to a browser's default behavior. - - Fix a white flash on Dark theme when switching file and improved the development file preview detection and adjustments. + - Reported file sizes will now more closely match what's reported by File Explorer. Thanks [@Deepak-Sangle](https://github.com/Deepak-Sangle)! + - Added a previewer that hosts File Explorer previewers to support every file type that a machine is currently able to preview. Thanks [@dillydylann](https://github.com/dillydylann)! + - Fixed an issue causing the preview of the first file to be stuck loading. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Fixed showing the previously previewed video file when invoking Peek with a new file. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Added the wrap and file formatting options to the Monaco previewer. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! ### PowerRename - - Fixed a crash caused by big counter values on the new enumeration method. + - Save data from the last run in a different file to avoid conflicting with changing settings in the Settings application. ### PowerToys Run - - It's now possible to select which shell is used by the Shell plugin. - - A combobox option type was added to the plugin options. - - Fixed a bug in the Calculator plugin that was causing decimal numbers to be misinterpreted on locales where the dot (`.`) character isn't used as a decimal or digit separator. - - Improved the Program plugin stability when it fails to load a program's thumbnail at startup. - - The use of Pinyin for querying some plugins can now be turned on in Settings. Thanks [@ChaseKnowlden](https://github.com/ChaseKnowlden)! - - Refactored option types for plugin and added number, string and composite types to be used in the future. Thanks [@htcfreek](https://github.com/htcfreek)! - - Fixed the entry for searching for Windows updates in the Settings plugin. Thanks [@htcfreek](https://github.com/htcfreek)! + - Fixed a case where the query wasn't being cleared after invoking a result action through the keyboard. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Improved the shell selection option for Windows Terminal in the Shell plugin and improved the backend code for adding combo box options to plugins. Thanks [@htcfreek](https://github.com/htcfreek)! + * The implementation of the combo box items has changed and isn't backward compatible. (Old plugins won't crash, but the combo box setting isn't shown in settings ui anymore.) + - Added Unix time in milliseconds, fixed negative unix time input and improved error messages in the TimeDate plugin. Thanks [@htcfreek](https://github.com/htcfreek)! + - The PowerToys plugin allows calling the new Environment Variables utility. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Refactored and added support to VSCodium Stable, VSCodium Insider and Remote Tunnels workspaces. Thanks [@eternalphane](https://github.com/eternalphane)! ### Quick Accent - - The "All languages" character set is now calculated by programmatically querying the characters for every available language. Thanks [@dannysummerlin](https://github.com/dannysummerlin)! - - Added é to the Norwegian and Swedish languages. Thanks [@Aaron-Junker](https://github.com/Aaron-Junker)! - - Added a runtime cache to the "All languages" character set, to only calculate accents once per key. + - Fixed characters that were removed from "All languages" because they were not in any single language. (This was a hotfix for 0.74) + - Added Asturian characters to the Spanish character set. Thanks [@blakestack](https://github.com/blakestack)! + - Added Greek characters with tonos. Thanks [@PesBandi](https://github.com/PesBandi)! ### Registry Preview - - Fixed focusing issues at startup. - - Improved the data visualization to show data in a similar way to the Windows Registry Editor. Thanks [@dillydylann](https://github.com/dillydylann)! + - Fixed a parsing error that crashed the Application. (This was a hotfix for 0.74) + - Fixed opening file names with non-ASCII characters. Thanks [@randyrants](https://github.com/randyrants)! + - Fixed wrong parsing when the file contained an assignment with spaces around the equals sign. Thanks [@randyrants](https://github.com/randyrants)! + - Fixed key transversal issues when a key was a substring of a parent key. Thanks [@randyrants](https://github.com/randyrants)! ### Runner - - Fixed hanging when a bug report was generated from the flyout. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Fixed the update notification toast to show a Unicode arrow. Thanks [@TheJoeFin](https://github.com/TheJoeFin)! ### Settings - - Improved the way the OOBE window reacts to Windows theme change. - - Fixed an issue that made it impossible to change the "Switch between windows in the current zone" "Next window" shortcut for FancyZones. - - Fixed a crash when entering a duplicate name for a color in the Color Picker page and improved clean up when cancelling a color edit. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Added a new Dashboard home page, with quick access for enabling modules, short descriptions and activation methods. Thanks [@niels9001](https://github.com/niels9001) for the design and UI work that made this possible! + - Fixed a typo in the Hosts File Editor page. Thanks [@Deepak-Sangle](https://github.com/Deepak-Sangle)! + - Added a lock icon to the flyout listing of all modules when its enabled state is controlled by policy. + - The "All apps" list in the flyout will now list all apps even if their enabled state is controlled by policy. -### Text Extractor +### Video Conference Mute - - Text Extractor 2.0, with a new overlay, table mode and more Quality of Life improvements. Thanks [@TheJoeFin](https://github.com/TheJoeFin)! + - Added an option to allow for the toolbar to hide after some time passed. Thanks [@quyenvsp](https://github.com/quyenvsp)! + - Added an option to select to mute or unmute at startup. Thanks [@quyenvsp](https://github.com/quyenvsp)! + - Fixed an issue causing a cascade of mute/unmute triggers. ### Documentation - - SECURITY.md was updated from 0.0.2 to 0.0.9. Thanks [@Aaron-Junker](https://github.com/Aaron-Junker)! - - Improved the README and main development document for clarity and completeness. Thanks [@codeofdusk](https://github.com/codeofdusk) and [@aprilbbrockhoeft](https://github.com/aprilbbrockhoeft)! + - Updated the Group Policy documentation on learn.microsoft.com, removed the Group Policy documentation from the repository and linked to the published documentation on learn.microsoft.com instead. ### Development - - Fixed PowerToys Run DateTime plugin tests that were failing depending on locale, so that they can be run correctly on all dev machines. - - Fixed PowerToys Run System plugin tests that were failing for certain network interfaces, so that they can be run correctly on all dev machines. Thanks [@snickler](https://github.com/snickler)! - - Fixed a markdown bug on the GitHub /helped command. - - Switched build pipelines to a new agent pool. Thanks [@DHowett](https://github.com/DHowett)! - - New .cs files created in Visual Studio get the header added automatically. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Added project dependencies to the version project and headers to avoid building errors. Thanks [@johnterickson](https://github.com/johnterickson)! + - Enabled Control Flow Guard in the C++ projects. Thanks [@DHowett](https://github.com/DHowett)! + - Switched the release pipeline to the 1ES governed template. Thanks [@DHowett](https://github.com/DHowett)! + - Styled XAML files and added a XAML Style checker to the solution, with a CI action to check if code being contributed is compliant. Thanks [@davidegiacometti](https://github.com/davidegiacometti)! + - Suppressed redundant midl file warnings in PowerRename. + - Add unit tests to FancyZones Editor. Thanks [@garv5014](https://github.com/garv5014), [@andrewbengordon](https://github.com/andrewbengordon) and [@Cwighty](https://github.com/Cwighty)! + - Improved the Default Layouts internal structure in FancyZones Editor. Thanks [@garv5014](https://github.com/garv5014)! + - Fixed code issues to allow building in Visual Studio 17.8 Preview 4. -#### What is being planned for version 0.75 +#### What is being planned for version 0.76 -For [v0.75][github-next-release-work], we'll work on the items below: +For [v0.76][github-next-release-work], we'll work on the items below: - Language selection - .NET 8 upgrade - - Policy support for managing PowerToys Run plugins. -*Attention*: A breaking change is planned (for 0.75), in which each plugin has to declare its identifier programmatically so that it can be controlled through GPO. For third-party plugin developers, please check https://github.com/microsoft/PowerToys/pull/27468 for more details. - - - New utility: Environment Variables Editor. Here's a Work in Progress preview: - -![Environment Variables Editor WIP](https://github.com/microsoft/PowerToys/assets/26118718/f99532a8-5aae-481b-a662-19a95f4aa03d) - - - New Settings homepage. Here's a Work in Progress preview: - -![PowerToys Settings Dashboard WIP](https://github.com/microsoft/PowerToys/assets/26118718/938a5715-0a9b-4fe9-9e15-adfec92da694) - + - Allowing Keyboard Manager to output arbitrary Unicode sequences + - Automated UI testing through WinAppDriver - Modernize and refresh the UX of PowerToys based on WPF. Here's Work in Progress previews for the modules "PowerToys Run" and "Color Picker": ![PowerToys Run UI refresh WIP](https://github.com/microsoft/PowerToys/assets/9866362/16903bcb-c18e-49fb-93ca-738b81957055) diff --git a/doc/devdocs/modules/keyboardmanager/keyboardmanagerui.md b/doc/devdocs/modules/keyboardmanager/keyboardmanagerui.md index 1f55198b4b..a94c9f0b36 100644 --- a/doc/devdocs/modules/keyboardmanager/keyboardmanagerui.md +++ b/doc/devdocs/modules/keyboardmanager/keyboardmanagerui.md @@ -1,10 +1,7 @@ # Keyboard Manager UI ## Table of Contents: -1. [C++ XAML Islands](#c-xaml-islands) - 1. [Debugging exceptions in XAML Islands](#debugging-exceptions-in-xaml-islands) - 2. [Build times](#build-times) - 3. [Setting custom backgrounds for Xaml Controls using brushes](#setting-custom-backgrounds-for-xaml-controls-using-brushes) +1. [XAML Implementation](#xaml-implementation) 2. [UI Structure](#ui-structure) 3. [EditKeyboardWindow / EditShortcutsWindow](#editkeyboardwindow--editshortcutswindow) 1. [OK and Cancel button](#ok-and-cancel-button) @@ -17,28 +14,18 @@ 2. [Single Key ComboBox Selection Handler](#single-key-combobox-selection-handler) 3. [Shortcut ComboBox Selection Handler](#shortcut-combobox-selection-handler) -## C++ XAML Islands -The KBM UI is implemented as a C++ XAML Island, but all the controls are implemented in code behind rather than .xaml and .xaml.cs files. This was done as per a XAML Island Code sample and it didn't require a separate UWP project, which could be limited in terms of using hooks. There is a [tech debt item](https://github.com/microsoft/PowerToys/issues/2027) for moving this to XAML. The reason it wasn't implemented in the C# Settings was because it required communication with the low level hook thread, which could be too slow if IPC is used, since the UI needs to update on every key event. +## XAML Implementation +The KBM UI was originally implemented as a XAML Island, but in order to easily support Mica, which is [still an issue](https://github.com/microsoft/microsoft-ui-xaml/issues/5319), the [`XamlBridge`](https://github.com/microsoft/PowerToys/blob/b3f27057d43445abc59aa04405f7c24bb895a61c/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/XamlBridge2.cpp) was rewritten to use a [`FrameworkView`](https://github.com/microsoft/PowerToys/blob/b3f27057d43445abc59aa04405f7c24bb895a61c/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/XamlBridge2.cpp#L47C11-L49) object which is how a traditional UWP app behaves: +1. A `CoreWindow` is created by [calling a function inside of `Windows.UI.dll` with an ordinal number of `1500`](https://github.com/microsoft/PowerToys/blob/b3f27057d43445abc59aa04405f7c24bb895a61c/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/XamlBridge2.cpp#L35-L42). +2. A [stubbed implementation of `CoreApplicationView`](https://github.com/microsoft/PowerToys/blob/b3f27057d43445abc59aa04405f7c24bb895a61c/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/XamlBridge2.cpp#L10-L18) was created. +3. Then [both objects are passed on to the `FrameworkView`](https://github.com/microsoft/PowerToys/blob/b3f27057d43445abc59aa04405f7c24bb895a61c/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/XamlBridge2.cpp#L47-L49) to initialize the XAML framework. +4. Lastly, the `CoreWindow` is [attached to the editor window](https://github.com/microsoft/PowerToys/blob/b3f27057d43445abc59aa04405f7c24bb895a61c/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/XamlBridge2.cpp#L54-L61) and its `HWND` is used in-place of `DesktopWindowXamlSource`'s. -**Note:** For functions which take a XAML component as argument, pass it by value and not by reference. This is because `winrt` WinUI classes store their own internal references, so they are supposed to be passed by value (and internally ref counts are incremented). Passing by reference can lead to weird behavior where the object is `null`. +Mica is then achieved by calling [`BackdropMaterial::SetApplyToRootOrPageBackground()`](https://github.com/microsoft/PowerToys/blob/b3f27057d43445abc59aa04405f7c24bb895a61c/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp#L388-L400) in both of the editor windows, or falls back to the `ApplicationPageBackgroundThemeBrush` background if Mica isn't available. -The windows are [created as C++ windows](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L128-L140) and the window sizes are set to default by [scaling them as per DPI](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L120-L126) using the `DPIAware::Convert` API from common lib. Since the UI is launched on a new thread, the window may not be in the foreground, so [we call `SetForegroundWindow`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L146-L150). +The UI was also updated to use WinUI 2.8 to match the look and feel of the Fluent design language of Windows 11 and the rest of PowerToys. There has been talk about [migrating the implementation to XAML files instead of code-behind](https://github.com/microsoft/PowerToys/issues/2027) and [utilizing WinUI 3 going forward](https://github.com/microsoft/PowerToys/issues/15870). More about the update can be read in [here](https://github.com/microsoft/PowerToys/pull/28473). -`DesktopWindowXamlSource` has to be declared and [it is initialized](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L159-L162) using the [`XamlBridge`](https://github.com/microsoft/PowerToys/blob/main/src/modules/keyboardmanager/ui/XamlBridge.cpp), and [a second window handle](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L161-L162) is generated for the internal Xaml Island window. Most of the code was based on the [Xaml Island Sample](https://github.com/microsoft/Xaml-Islands-Samples/blob/master/Samples/Win32/SampleCppApp/XamlBridge.cpp). The `XamlBridge` class contains code which handles initializing the Xaml Island containers as well as handling special messages like keyboard navigation, and focus between islands and between the C++ window and the island. It also has methods for clearing the xaml islands and closing the window. - -Once the UI controls are created, the parent container is set as the content for the `DesktopWindowXamlSource` and the `XamlBridge.MessageLoop` is executed. Messages are processed by the C++ window handler like [`EditKeyboardWindowProc`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/EditKeyboardWindow.cpp#L364-L404). The general structure we use for this is, for any `WM_PAINT` or `WM_SIZE` message we resize the Xaml Island window. For `WM_GETMINMAXINFO` we set minimum widths so that the window cannot be resized beyond a minimum height and width. This is done to prevent the WinUI elements from overlapping and getting cropped. If it is neither of these cases we send the message to the [`XamlBridge.MessageHandler`](https://github.com/microsoft/PowerToys/blob/b80578b1b9a4b24c9945bddac33c771204280107/src/modules/keyboardmanager/ui/XamlBridge.cpp#L291-L301) which handles Destroy, Activation and Focus. If `WM_NCDESTROY` is received when the `XamlBridge` is `nullptr`, the window thread is terminated. - -**Note:** `ContentDialog` in Xaml Islands requires manually settings a `XamlRoot`. This can generally be done by passing the XamlRoot from a component in the main window, such as the button used to open the dialog ([`sender.as diff --git a/src/modules/PowerOCR/PowerOCR/OCROverlay.xaml.cs b/src/modules/PowerOCR/PowerOCR/OCROverlay.xaml.cs index e79d0d3ea4..60ec8b3a3a 100644 --- a/src/modules/PowerOCR/PowerOCR/OCROverlay.xaml.cs +++ b/src/modules/PowerOCR/PowerOCR/OCROverlay.xaml.cs @@ -49,7 +49,7 @@ public partial class OCROverlay : Window Top = screenRectangle.Top >= 0 ? screenRectangle.Top : screenRectangle.Top + (screenRectangle.Height / 2); InitializeComponent(); - + Wpf.Ui.Appearance.SystemThemeWatcher.Watch(this, Wpf.Ui.Controls.WindowBackdropType.None); PopulateLanguageMenu(); } diff --git a/src/modules/PowerOCR/PowerOCR/PowerOCR.csproj b/src/modules/PowerOCR/PowerOCR/PowerOCR.csproj index 5b59ab9ef8..4116b0889c 100644 --- a/src/modules/PowerOCR/PowerOCR/PowerOCR.csproj +++ b/src/modules/PowerOCR/PowerOCR/PowerOCR.csproj @@ -61,4 +61,20 @@ + + + + True + True + Resources.resx + + + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + Designer + + diff --git a/src/modules/PowerOCR/PowerOCR/Properties/Resources.Designer.cs b/src/modules/PowerOCR/PowerOCR/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..f50fd9ff68 --- /dev/null +++ b/src/modules/PowerOCR/PowerOCR/Properties/Resources.Designer.cs @@ -0,0 +1,135 @@ +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +namespace PowerOCR.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // 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() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [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("PowerOCR.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Cancel. + /// + public static string Cancel { + get { + return ResourceManager.GetString("Cancel", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cancel (Esc). + /// + public static string CancelShortcut { + get { + return ResourceManager.GetString("CancelShortcut", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Format result as a single line. + /// + public static string ResultTextSingleLine { + get { + return ResourceManager.GetString("ResultTextSingleLine", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Format result as a single line (S). + /// + public static string ResultTextSingleLineShortcut { + get { + return ResourceManager.GetString("ResultTextSingleLineShortcut", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Format result as a table. + /// + public static string ResultTextTable { + get { + return ResourceManager.GetString("ResultTextTable", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Format result as a table (T). + /// + public static string ResultTextTableShortcut { + get { + return ResourceManager.GetString("ResultTextTableShortcut", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Selected language. + /// + public static string SelectedLang { + get { + return ResourceManager.GetString("SelectedLang", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Settings. + /// + public static string Settings { + get { + return ResourceManager.GetString("Settings", resourceCulture); + } + } + } +} diff --git a/src/modules/PowerOCR/PowerOCR/Properties/Resources.resx b/src/modules/PowerOCR/PowerOCR/Properties/Resources.resx new file mode 100644 index 0000000000..df6947f018 --- /dev/null +++ b/src/modules/PowerOCR/PowerOCR/Properties/Resources.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Cancel + + + Cancel (Esc) + (Esc) indicates the keyboard shortcut + + + Format result as a single line + + + Format result as a single line (S) + (S) indicates the keyboard shortcut + + + Format result as a table + + + Format result as a table (T) + (T) indicates the keyboard shortcut + + + Selected language + + + Settings + + \ No newline at end of file diff --git a/src/modules/PowerOCR/PowerOCR/Styles/ButtonStyles.xaml b/src/modules/PowerOCR/PowerOCR/Styles/ButtonStyles.xaml deleted file mode 100644 index fab21fb3ee..0000000000 --- a/src/modules/PowerOCR/PowerOCR/Styles/ButtonStyles.xaml +++ /dev/null @@ -1,695 +0,0 @@ - - - - - - - - - - - - - - M 0,0 L 3.5,4 L 7,0 Z - M 0,4 L 3.5,0 L 7,4 Z - M 0,0 L 4,3.5 L 0,7 Z - F1 M 10.0,1.2 L 4.7,9.1 L 4.5,9.1 L 0,5.2 L 1.3,3.5 L 4.3,6.1L 8.3,0 L 10.0,1.2 Z - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/PowerOCR/PowerOCR/Styles/Colors.xaml b/src/modules/PowerOCR/PowerOCR/Styles/Colors.xaml deleted file mode 100644 index 97fa005753..0000000000 --- a/src/modules/PowerOCR/PowerOCR/Styles/Colors.xaml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/modules/PowerOCR/PowerOCRModuleInterface/dllmain.cpp b/src/modules/PowerOCR/PowerOCRModuleInterface/dllmain.cpp index f89dde5ce1..1eebf010ee 100644 --- a/src/modules/PowerOCR/PowerOCRModuleInterface/dllmain.cpp +++ b/src/modules/PowerOCR/PowerOCRModuleInterface/dllmain.cpp @@ -12,6 +12,7 @@ #include #include #include +#include BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD ul_reason_for_call, @@ -293,6 +294,13 @@ public: { return m_enabled; } + + // Returns whether the PowerToys should be enabled by default + virtual bool is_enabled_by_default() const override + { + // disabled by default for Windows 11 and enabled by default on Windows 10 + return !package::IsWin11OrGreater(); + } }; extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create() diff --git a/src/modules/awake/Awake/Awake.csproj b/src/modules/awake/Awake/Awake.csproj index f31c22e62f..58576db33c 100644 --- a/src/modules/awake/Awake/Awake.csproj +++ b/src/modules/awake/Awake/Awake.csproj @@ -79,6 +79,11 @@ Never + + True + True + Resources.resx + @@ -87,11 +92,17 @@ + + + ResXFileCodeGenerator + Resources.Designer.cs + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/src/modules/awake/Awake/Core/Manager.cs b/src/modules/awake/Awake/Core/Manager.cs index d46843226a..6016a4551e 100644 --- a/src/modules/awake/Awake/Core/Manager.cs +++ b/src/modules/awake/Awake/Core/Manager.cs @@ -6,12 +6,14 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Globalization; using System.IO; using System.Reactive.Linq; using System.Text; using System.Threading; using Awake.Core.Models; using Awake.Core.Native; +using Awake.Properties; using ManagedCommon; using Microsoft.PowerToys.Telemetry; using Microsoft.Win32; @@ -274,9 +276,9 @@ namespace Awake.Core { Dictionary optionsList = new Dictionary { - { "30 minutes", 1800 }, - { "1 hour", 3600 }, - { "2 hours", 7200 }, + { string.Format(CultureInfo.InvariantCulture, Resources.AWAKE_MINUTES, 30), 1800 }, + { Resources.AWAKE_1_HOUR, 3600 }, + { string.Format(CultureInfo.InvariantCulture, Resources.AWAKE_HOURS, 2), 7200 }, }; return optionsList; } diff --git a/src/modules/awake/Awake/Core/TrayHelper.cs b/src/modules/awake/Awake/Core/TrayHelper.cs index 110d623d75..ef6794ecfb 100644 --- a/src/modules/awake/Awake/Core/TrayHelper.cs +++ b/src/modules/awake/Awake/Core/TrayHelper.cs @@ -6,11 +6,13 @@ using System; using System.Collections.Generic; using System.Drawing; using System.Linq; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using Awake.Core.Models; using Awake.Core.Native; +using Awake.Properties; using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Library; @@ -115,11 +117,11 @@ namespace Awake.Core if (!startedFromPowerToys) { // If Awake is started from PowerToys, the correct way to exit it is disabling it from Settings. - Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING, (uint)TrayCommands.TC_EXIT, "Exit"); + Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING, (uint)TrayCommands.TC_EXIT, Resources.AWAKE_EXIT); Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_SEPARATOR, 0, string.Empty); } - Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING | (keepDisplayOn ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED) | (mode == AwakeMode.PASSIVE ? Native.Constants.MF_DISABLED : Native.Constants.MF_ENABLED), (uint)TrayCommands.TC_DISPLAY_SETTING, "Keep screen on"); + Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING | (keepDisplayOn ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED) | (mode == AwakeMode.PASSIVE ? Native.Constants.MF_DISABLED : Native.Constants.MF_ENABLED), (uint)TrayCommands.TC_DISPLAY_SETTING, Resources.AWAKE_KEEP_SCREEN_ON); } // In case there are no tray shortcuts defined for the application default to a @@ -137,10 +139,10 @@ namespace Awake.Core Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_SEPARATOR, 0, string.Empty); - Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING | (mode == AwakeMode.PASSIVE ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_PASSIVE, "Off (keep using the selected power plan)"); - Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING | (mode == AwakeMode.INDEFINITE ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_INDEFINITE, "Keep awake indefinitely"); - Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_POPUP | (mode == AwakeMode.TIMED ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)awakeTimeMenu, "Keep awake on interval"); - Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING | Native.Constants.MF_DISABLED | (mode == AwakeMode.EXPIRABLE ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_EXPIRABLE, "Keep awake until expiration date and time"); + Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING | (mode == AwakeMode.PASSIVE ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_PASSIVE, Resources.AWAKE_OFF); + Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING | (mode == AwakeMode.INDEFINITE ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_INDEFINITE, Resources.AWAKE_KEEP_INDEFINITELY); + Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_POPUP | (mode == AwakeMode.TIMED ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)awakeTimeMenu, Resources.AWAKE_KEEP_ON_INTERVAL); + Bridge.InsertMenu(TrayMenu, 0, Native.Constants.MF_BYPOSITION | Native.Constants.MF_STRING | Native.Constants.MF_DISABLED | (mode == AwakeMode.EXPIRABLE ? Native.Constants.MF_CHECKED : Native.Constants.MF_UNCHECKED), (uint)TrayCommands.TC_MODE_EXPIRABLE, Resources.AWAKE_KEEP_UNTIL_EXPIRATION); TrayIcon.Text = text; } @@ -157,7 +159,7 @@ namespace Awake.Core public override AccessibleRole Role => AccessibleRole.CheckButton; - public override string Name => _menuItem.Text + ", " + Role + ", " + (_menuItem.Checked ? "Checked" : "Unchecked"); + public override string Name => _menuItem.Text + ", " + Role + ", " + (_menuItem.Checked ? Resources.AWAKE_CHECKED : Resources.AWAKE_UNCHECKED); } private sealed class CheckButtonToolStripMenuItem : ToolStripMenuItem diff --git a/src/modules/awake/Awake/Properties/Resources.Designer.cs b/src/modules/awake/Awake/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..dab74f79fa --- /dev/null +++ b/src/modules/awake/Awake/Properties/Resources.Designer.cs @@ -0,0 +1,171 @@ +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +namespace Awake.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // 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()] + internal 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() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Awake.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to 1 hour. + /// + internal static string AWAKE_1_HOUR { + get { + return ResourceManager.GetString("AWAKE_1_HOUR", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 1 minute. + /// + internal static string AWAKE_1_MINUTE { + get { + return ResourceManager.GetString("AWAKE_1_MINUTE", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Checked. + /// + internal static string AWAKE_CHECKED { + get { + return ResourceManager.GetString("AWAKE_CHECKED", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Exit. + /// + internal static string AWAKE_EXIT { + get { + return ResourceManager.GetString("AWAKE_EXIT", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} hours. + /// + internal static string AWAKE_HOURS { + get { + return ResourceManager.GetString("AWAKE_HOURS", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Keep awake indefinitely. + /// + internal static string AWAKE_KEEP_INDEFINITELY { + get { + return ResourceManager.GetString("AWAKE_KEEP_INDEFINITELY", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Keep awake on interval. + /// + internal static string AWAKE_KEEP_ON_INTERVAL { + get { + return ResourceManager.GetString("AWAKE_KEEP_ON_INTERVAL", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Keep screen on. + /// + internal static string AWAKE_KEEP_SCREEN_ON { + get { + return ResourceManager.GetString("AWAKE_KEEP_SCREEN_ON", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Keep awake until expiration date and time. + /// + internal static string AWAKE_KEEP_UNTIL_EXPIRATION { + get { + return ResourceManager.GetString("AWAKE_KEEP_UNTIL_EXPIRATION", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} minutes. + /// + internal static string AWAKE_MINUTES { + get { + return ResourceManager.GetString("AWAKE_MINUTES", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Off (keep using the selected power plan). + /// + internal static string AWAKE_OFF { + get { + return ResourceManager.GetString("AWAKE_OFF", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unchecked. + /// + internal static string AWAKE_UNCHECKED { + get { + return ResourceManager.GetString("AWAKE_UNCHECKED", resourceCulture); + } + } + } +} diff --git a/src/modules/awake/Awake/Properties/Resources.resx b/src/modules/awake/Awake/Properties/Resources.resx new file mode 100644 index 0000000000..7ce0a1652d --- /dev/null +++ b/src/modules/awake/Awake/Properties/Resources.resx @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Checked + + + Exit + + + 1 hour + + + {0} hours + {0} shouldn't be removed. It will be replaced by a number greater than 1 at runtime by the application. Used for defining a period to keep the PC awake. + + + Keep awake indefinitely + Keep the system awake forever + + + Keep awake on interval + Keep the system awake for a given time + + + Keep screen on + + + Keep awake until expiration date and time + Keep the system awake until expiration date and time + + + 1 minute + + + {0} minutes + {0} shouldn't be removed. It will be replaced by a number greater than 1 at runtime by the application. Used for defining a period to keep the PC awake. + + + Off (keep using the selected power plan) + Don't keep the system awake, use the selected system power plan + + + Unchecked + + \ No newline at end of file diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp index 960f797f72..f708a2d4f8 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZones.cpp @@ -394,6 +394,15 @@ void FancyZones::WindowCreated(HWND window) noexcept return; } + // Hotfix + // Avoid automatically moving popup windows, as they can be just popup menus. + bool isPopup = FancyZonesWindowUtils::IsPopupWindow(window); + bool hasThickFrame = FancyZonesWindowUtils::HasThickFrame(window); + if (isPopup && !hasThickFrame) + { + return; + } + // Avoid already stamped (zoned) windows const bool isZoned = !FancyZonesWindowProperties::RetrieveZoneIndexProperty(window).empty(); if (isZoned) diff --git a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProcessing.cpp b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProcessing.cpp index 5a0c2eda53..102392ba91 100644 --- a/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProcessing.cpp +++ b/src/modules/fancyzones/FancyZonesLib/FancyZonesWindowProcessing.cpp @@ -27,9 +27,8 @@ bool FancyZonesWindowProcessing::IsProcessable(HWND window) noexcept // popup could be the window we don't want to snap: start menu, notification popup, tray window, etc. // also, popup could be the windows we want to snap disregarding the "allowSnapPopupWindows" setting, e.g. Telegram - bool isPopup = FancyZonesWindowUtils::IsPopupWindow(window); - bool hasThickFrame = FancyZonesWindowUtils::HasThickFrame(window); - if (isPopup && (!hasThickFrame || !FancyZonesSettings::settings().allowSnapPopupWindows)) + bool isPopup = FancyZonesWindowUtils::IsPopupWindow(window) && !FancyZonesWindowUtils::HasThickFrameAndMinimizeMaximizeButtons(window); + if (isPopup && !FancyZonesSettings::settings().allowSnapPopupWindows) { return false; } diff --git a/src/modules/fancyzones/FancyZonesLib/Settings.cpp b/src/modules/fancyzones/FancyZonesLib/Settings.cpp index c794bc116e..51d9b52846 100644 --- a/src/modules/fancyzones/FancyZonesLib/Settings.cpp +++ b/src/modules/fancyzones/FancyZonesLib/Settings.cpp @@ -238,7 +238,7 @@ void FancyZonesSettings::LoadSettings() if (auto val = values.get_int_value(NonLocalizable::OverlappingZonesAlgorithmID)) { // Avoid undefined behavior - if (*val >= 0 || *val < static_cast(OverlappingZonesAlgorithm::EnumElements)) + if (*val >= 0 && *val < static_cast(OverlappingZonesAlgorithm::EnumElements)) { auto algorithm = (OverlappingZonesAlgorithm)*val; if (m_settings.overlappingZonesAlgorithm != algorithm) diff --git a/src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp b/src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp index ec246180fd..8d83a7cab7 100644 --- a/src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp +++ b/src/modules/fancyzones/FancyZonesLib/WindowUtils.cpp @@ -306,26 +306,23 @@ void FancyZonesWindowUtils::SizeWindowToRect(HWND window, RECT rect) noexcept ::GetWindowPlacement(window, &placement); } - if (!IsWindowVisible(window)) - { - placement.showCmd = SW_HIDE; - } - else + if (IsWindowVisible(window)) { // Do not restore minimized windows. We change their placement though so they restore to the correct zone. if ((placement.showCmd != SW_SHOWMINIMIZED) && (placement.showCmd != SW_MINIMIZE)) { - placement.showCmd = SW_RESTORE; - } + // Remove maximized show command to make sure window is moved to the correct zone. + if (placement.showCmd == SW_SHOWMAXIMIZED) + placement.flags &= ~WPF_RESTORETOMAXIMIZED; - // Remove maximized show command to make sure window is moved to the correct zone. - if (placement.showCmd == SW_SHOWMAXIMIZED) - { placement.showCmd = SW_RESTORE; - placement.flags &= ~WPF_RESTORETOMAXIMIZED; } } + else + { + placement.showCmd = SW_HIDE; + } ScreenToWorkAreaCoords(window, rect); diff --git a/src/modules/imageresizer/ui/App.xaml b/src/modules/imageresizer/ui/App.xaml index b11e65520b..b43baa5b60 100644 --- a/src/modules/imageresizer/ui/App.xaml +++ b/src/modules/imageresizer/ui/App.xaml @@ -38,4 +38,4 @@ - + \ No newline at end of file diff --git a/src/modules/imageresizer/ui/ViewModels/MainViewModel.cs b/src/modules/imageresizer/ui/ViewModels/MainViewModel.cs index 8f335e4b1d..64a321ba6a 100644 --- a/src/modules/imageresizer/ui/ViewModels/MainViewModel.cs +++ b/src/modules/imageresizer/ui/ViewModels/MainViewModel.cs @@ -44,7 +44,7 @@ namespace ImageResizer.ViewModels { if (_batch.Files.Count == 0) { - _batch.Files.AddRange(view?.OpenPictureFiles()); + _batch.Files.AddRange(view.OpenPictureFiles()); } CurrentPage = new InputViewModel(_settings, this, view, _batch); diff --git a/src/modules/imageresizer/ui/Views/MainWindow.xaml b/src/modules/imageresizer/ui/Views/MainWindow.xaml index 3627e68d4a..72a5120710 100644 --- a/src/modules/imageresizer/ui/Views/MainWindow.xaml +++ b/src/modules/imageresizer/ui/Views/MainWindow.xaml @@ -16,7 +16,6 @@ ExtendsContentIntoTitleBar="True" Icon="/PowerToys.ImageResizer;component/Resources/ImageResizer.ico" ResizeMode="NoResize" - WindowBackdropType="Mica" WindowCornerPreference="Default" WindowStartupLocation="CenterScreen"> diff --git a/src/modules/imageresizer/ui/Views/MainWindow.xaml.cs b/src/modules/imageresizer/ui/Views/MainWindow.xaml.cs index 0c96ed12a7..9a6dfc15b1 100644 --- a/src/modules/imageresizer/ui/Views/MainWindow.xaml.cs +++ b/src/modules/imageresizer/ui/Views/MainWindow.xaml.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Common.UI; using ImageResizer.ViewModels; using Microsoft.Win32; using Wpf.Ui.Controls; @@ -17,7 +18,17 @@ namespace ImageResizer.Views public MainWindow(MainViewModel viewModel) { DataContext = viewModel; - Wpf.Ui.Appearance.Watcher.Watch(this); + Wpf.Ui.Appearance.SystemThemeWatcher.Watch(this); + + if (OSVersionHelper.IsWindows11()) + { + WindowBackdropType = WindowBackdropType.Mica; + } + else + { + WindowBackdropType = WindowBackdropType.None; + } + InitializeComponent(); } diff --git a/src/modules/keyboardmanager/Directory.Build.targets b/src/modules/keyboardmanager/Directory.Build.targets index d37c24b655..dfcf63e016 100644 --- a/src/modules/keyboardmanager/Directory.Build.targets +++ b/src/modules/keyboardmanager/Directory.Build.targets @@ -1,4 +1,6 @@ + + diff --git a/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.vcxproj b/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.vcxproj index 3af18021f0..5f513f8831 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.vcxproj +++ b/src/modules/keyboardmanager/KeyboardManagerEditor/KeyboardManagerEditor.vcxproj @@ -1,6 +1,8 @@ + + 81010002 @@ -98,7 +100,7 @@ ./;$(SolutionDir)src\modules\;$(SolutionDir)src\modules\KeyboardManager\KeyboardManagerEditorLibrary\;$(SolutionDir)src\common\Display;$(SolutionDir)src\common\inc;$(SolutionDir)src\common\Telemetry;$(SolutionDir)src;%(AdditionalIncludeDirectories) - Display.lib;shcore.lib;Dbghelp.lib;dwmapi.lib;%(AdditionalDependencies) + Display.lib;shcore.lib;Dbghelp.lib;dwmapi.lib;uxtheme.lib;%(AdditionalDependencies) $(SolutionDir)$(Platform)\$(ConfigurationName);%(AdditionalLibraryDirectories) @@ -109,7 +111,7 @@ true true - Display.lib;shcore.lib;Dbghelp.lib;dwmapi.lib;%(AdditionalDependencies) + Display.lib;shcore.lib;Dbghelp.lib;dwmapi.lib;uxtheme.lib;%(AdditionalDependencies) $(SolutionDir)$(Platform)\$(ConfigurationName);%(AdditionalLibraryDirectories) @@ -160,6 +162,10 @@ + + + + @@ -171,5 +177,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/keyboardmanager/KeyboardManagerEditor/packages.config b/src/modules/keyboardmanager/KeyboardManagerEditor/packages.config index 47bae1882f..96e6f248fe 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditor/packages.config +++ b/src/modules/keyboardmanager/KeyboardManagerEditor/packages.config @@ -1,4 +1,8 @@  + + + + \ No newline at end of file diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp index 2c4f2c681c..24126abebd 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditKeyboardWindow.cpp @@ -15,7 +15,7 @@ #include "EditKeyboardWindow.h" #include "SingleKeyRemapControl.h" #include "KeyDropDownControl.h" -#include "XamlBridge.h" +#include "XamlBridge2.h" #include "Styles.h" #include "Dialog.h" #include "LoadingAndSavingRemappingHelper.h" @@ -41,12 +41,12 @@ HWND hwndEditKeyboardNativeWindow = nullptr; std::mutex editKeyboardWindowMutex; // Stores a pointer to the Xaml Bridge object so that it can be accessed from the window procedure -static XamlBridge* xamlBridgePtr = nullptr; +static XamlBridge2* xamlBridgePtr = nullptr; // Theming -ThemeListener theme_listener{}; +static ThemeListener theme_listener{}; -void handleTheme() +static void handleTheme() { auto theme = theme_listener.AppTheme; auto isDark = theme == AppTheme::Dark; @@ -137,7 +137,7 @@ inline void CreateEditKeyboardWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMan windowClass.lpfnWndProc = EditKeyboardWindowProc; windowClass.hInstance = hInst; windowClass.lpszClassName = szWindowClass; - windowClass.hbrBackground = reinterpret_cast(COLOR_WINDOW); + windowClass.hbrBackground = CreateSolidBrush((ThemeHelpers::GetAppTheme() == AppTheme::Dark) ? 0x00000000 : 0x00FFFFFF); windowClass.hIcon = static_cast(LoadImageW( windowClass.hInstance, MAKEINTRESOURCE(IDS_KEYBOARDMANAGER_ICON), @@ -196,17 +196,19 @@ inline void CreateEditKeyboardWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMan hwndEditKeyboardNativeWindow = _hWndEditKeyboardWindow; hwndLock.unlock(); + // Hide icon and caption from title bar + const DWORD windowThemeOptionsMask = WTNCA_NODRAWCAPTION | WTNCA_NODRAWICON; + WTA_OPTIONS windowThemeOptions{ windowThemeOptionsMask, windowThemeOptionsMask }; + SetWindowThemeAttribute(_hWndEditKeyboardWindow, WTA_NONCLIENT, &windowThemeOptions, sizeof(windowThemeOptions)); + handleTheme(); theme_listener.AddChangedHandler(handleTheme); // Create the xaml bridge object - XamlBridge xamlBridge(_hWndEditKeyboardWindow); - - // DesktopSource needs to be declared before the RelativePanel xamlContainer object to avoid errors - winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource desktopSource; + XamlBridge2 xamlBridge(_hWndEditKeyboardWindow); // Create the desktop window xaml source object and set its content - hWndXamlIslandEditKeyboardWindow = xamlBridge.InitDesktopWindowsXamlSource(desktopSource); + hWndXamlIslandEditKeyboardWindow = xamlBridge.InitBridge(); // Set the pointer to the xaml bridge object xamlBridgePtr = &xamlBridge; @@ -322,11 +324,8 @@ inline void CreateEditKeyboardWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMan // Add remap key button Windows::UI::Xaml::Controls::Button addRemapKey; - FontIcon plusSymbol; - plusSymbol.FontFamily(Media::FontFamily(L"Segoe MDL2 Assets")); - plusSymbol.Glyph(L"\xE710"); - addRemapKey.Content(plusSymbol); addRemapKey.Margin({ 10, 10, 0, 25 }); + addRemapKey.Style(AccentButtonStyle()); addRemapKey.Click([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { SingleKeyRemapControl::AddNewControlKeyRemapRow(keyRemapTable, keyboardRemapControlObjects); @@ -337,14 +336,19 @@ inline void CreateEditKeyboardWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMan UIHelpers::SetFocusOnTypeButtonInLastRow(keyRemapTable, EditorConstants::RemapTableColCount); }); + // Remap key button content + StackPanel addRemapKeyContent; + addRemapKeyContent.Orientation(Orientation::Horizontal); + addRemapKeyContent.Spacing(10); + addRemapKeyContent.Children().Append(SymbolIcon(Symbol::Add)); + TextBlock addRemapKeyText; + addRemapKeyText.Text(GET_RESOURCE_STRING(IDS_ADD_KEY_REMAP_BUTTON)); + addRemapKeyContent.Children().Append(addRemapKeyText); + addRemapKey.Content(addRemapKeyContent); + // Set accessible name for the addRemapKey button addRemapKey.SetValue(Automation::AutomationProperties::NameProperty(), box_value(GET_RESOURCE_STRING(IDS_ADD_KEY_REMAP_BUTTON))); - // Add tooltip for add button which would appear on hover - ToolTip addRemapKeytoolTip; - addRemapKeytoolTip.Content(box_value(GET_RESOURCE_STRING(IDS_ADD_KEY_REMAP_BUTTON))); - ToolTipService::SetToolTip(addRemapKey, addRemapKeytoolTip); - // Header and example text at the top of the window StackPanel helperText; helperText.Children().Append(keyRemapInfoHeader); @@ -381,7 +385,20 @@ inline void CreateEditKeyboardWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMan { } - desktopSource.Content(xamlContainer); + UserControl xamlContent; + xamlContent.Content(xamlContainer); + if (Windows::Foundation::Metadata::ApiInformation::IsTypePresent(L"Windows.UI.Composition.ICompositionSupportsSystemBackdrop")) + { + // Apply Mica + muxc::BackdropMaterial::SetApplyToRootOrPageBackground(xamlContent, true); + } + else + { + // Mica isn't available + xamlContainer.Background(Application::Current().Resources().Lookup(box_value(L"ApplicationPageBackgroundThemeBrush")).as()); + } + Window::Current().Content(xamlContent); + ////End XAML Island section if (_hWndEditKeyboardWindow) { @@ -400,9 +417,6 @@ inline void CreateEditKeyboardWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMan hwndEditKeyboardNativeWindow = nullptr; keyboardManagerState.ResetUIState(); keyboardManagerState.ClearRegisteredKeyDelays(); - - // Cannot be done in WM_DESTROY because that causes crashes due to fatal app exit - xamlBridge.ClearXamlIslands(); } void CreateEditKeyboardWindow(HINSTANCE hInst, KBMEditor::KeyboardManagerState& keyboardManagerState, MappingConfiguration& mappingConfiguration) diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditShortcutsWindow.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditShortcutsWindow.cpp index d312e0091a..e5a80db5a7 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditShortcutsWindow.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditShortcutsWindow.cpp @@ -13,9 +13,10 @@ #include "ShortcutControl.h" #include "Styles.h" #include "UIHelpers.h" -#include "XamlBridge.h" +#include "XamlBridge2.h" #include "ShortcutErrorType.h" #include "EditorConstants.h" +#include using namespace winrt::Windows::Foundation; @@ -34,7 +35,21 @@ HWND hwndEditShortcutsNativeWindow = nullptr; std::mutex editShortcutsWindowMutex; // Stores a pointer to the Xaml Bridge object so that it can be accessed from the window procedure -static XamlBridge* xamlBridgePtr = nullptr; +static XamlBridge2* xamlBridgePtr = nullptr; + +// Theming +static ThemeListener theme_listener{}; + +static void handleTheme() +{ + auto theme = theme_listener.AppTheme; + auto isDark = theme == AppTheme::Dark; + Logger::info(L"Theme is now {}", isDark ? L"Dark" : L"Light"); + if (hwndEditShortcutsNativeWindow != nullptr) + { + ThemeHelpers::SetImmersiveDarkMode(hwndEditShortcutsNativeWindow, isDark); + } +} static IAsyncAction OnClickAccept( KBMEditor::KeyboardManagerState& keyboardManagerState, @@ -75,7 +90,7 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa windowClass.lpfnWndProc = EditShortcutsWindowProc; windowClass.hInstance = hInst; windowClass.lpszClassName = szWindowClass; - windowClass.hbrBackground = reinterpret_cast(COLOR_WINDOW); + windowClass.hbrBackground = CreateSolidBrush((ThemeHelpers::GetAppTheme() == AppTheme::Dark) ? 0x00000000 : 0x00FFFFFF); windowClass.hIcon = static_cast(LoadImageW( windowClass.hInstance, MAKEINTRESOURCE(IDS_KEYBOARDMANAGER_ICON), @@ -132,14 +147,19 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa hwndEditShortcutsNativeWindow = _hWndEditShortcutsWindow; hwndLock.unlock(); + // Hide icon and caption from title bar + const DWORD windowThemeOptionsMask = WTNCA_NODRAWCAPTION | WTNCA_NODRAWICON; + WTA_OPTIONS windowThemeOptions{ windowThemeOptionsMask, windowThemeOptionsMask }; + SetWindowThemeAttribute(_hWndEditShortcutsWindow, WTA_NONCLIENT, &windowThemeOptions, sizeof(windowThemeOptions)); + + handleTheme(); + theme_listener.AddChangedHandler(handleTheme); + // Create the xaml bridge object - XamlBridge xamlBridge(_hWndEditShortcutsWindow); - - // DesktopSource needs to be declared before the RelativePanel xamlContainer object to avoid errors - winrt::Windows::UI::Xaml::Hosting::DesktopWindowXamlSource desktopSource; + XamlBridge2 xamlBridge(_hWndEditShortcutsWindow); // Create the desktop window xaml source object and set its content - hWndXamlIslandEditShortcutsWindow = xamlBridge.InitDesktopWindowsXamlSource(desktopSource); + hWndXamlIslandEditShortcutsWindow = xamlBridge.InitBridge(); // Set the pointer to the xaml bridge object xamlBridgePtr = &xamlBridge; @@ -276,11 +296,8 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa // Add shortcut button Windows::UI::Xaml::Controls::Button addShortcut; - FontIcon plusSymbol; - plusSymbol.FontFamily(Xaml::Media::FontFamily(L"Segoe MDL2 Assets")); - plusSymbol.Glyph(L"\xE710"); - addShortcut.Content(plusSymbol); addShortcut.Margin({ 10, 10, 0, 25 }); + addShortcut.Style(AccentButtonStyle()); addShortcut.Click([&](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { ShortcutControl::AddNewShortcutControlRow(shortcutTable, keyboardRemapControlObjects); @@ -291,14 +308,19 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa UIHelpers::SetFocusOnTypeButtonInLastRow(shortcutTable, EditorConstants::ShortcutTableColCount); }); + // Remap shortcut button content + StackPanel addShortcutContent; + addShortcutContent.Orientation(Orientation::Horizontal); + addShortcutContent.Spacing(10); + addShortcutContent.Children().Append(SymbolIcon(Symbol::Add)); + TextBlock addShortcutText; + addShortcutText.Text(GET_RESOURCE_STRING(IDS_ADD_SHORTCUT_BUTTON)); + addShortcutContent.Children().Append(addShortcutText); + addShortcut.Content(addShortcutContent); + // Set accessible name for the add shortcut button addShortcut.SetValue(Automation::AutomationProperties::NameProperty(), box_value(GET_RESOURCE_STRING(IDS_ADD_SHORTCUT_BUTTON))); - // Add tooltip for add button which would appear on hover - ToolTip addShortcuttoolTip; - addShortcuttoolTip.Content(box_value(GET_RESOURCE_STRING(IDS_ADD_SHORTCUT_BUTTON))); - ToolTipService::SetToolTip(addShortcut, addShortcuttoolTip); - // Header and example text at the top of the window StackPanel helperText; helperText.Children().Append(shortcutRemapInfoHeader); @@ -334,7 +356,19 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa { } - desktopSource.Content(xamlContainer); + UserControl xamlContent; + xamlContent.Content(xamlContainer); + if (Windows::Foundation::Metadata::ApiInformation::IsTypePresent(L"Windows.UI.Composition.ICompositionSupportsSystemBackdrop")) + { + // Apply Mica + muxc::BackdropMaterial::SetApplyToRootOrPageBackground(xamlContent, true); + } + else + { + // Mica isn't available + xamlContainer.Background(Application::Current().Resources().Lookup(box_value(L"ApplicationPageBackgroundThemeBrush")).as()); + } + Window::Current().Content(xamlContent); ////End XAML Island section if (_hWndEditShortcutsWindow) @@ -353,9 +387,6 @@ inline void CreateEditShortcutsWindowImpl(HINSTANCE hInst, KBMEditor::KeyboardMa hwndEditShortcutsNativeWindow = nullptr; keyboardManagerState.ResetUIState(); keyboardManagerState.ClearRegisteredKeyDelays(); - - // Cannot be done in WM_DESTROY because that causes crashes due to fatal app exit - xamlBridge.ClearXamlIslands(); } void CreateEditShortcutsWindow(HINSTANCE hInst, KBMEditor::KeyboardManagerState& keyboardManagerState, MappingConfiguration& mappingConfiguration) diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditorConstants.h b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditorConstants.h index ca36c7aa33..9943c146f4 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditorConstants.h +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/EditorConstants.h @@ -3,12 +3,12 @@ namespace EditorConstants { // Default window sizes - inline const int DefaultEditKeyboardWindowWidth = 800; + inline const int DefaultEditKeyboardWindowWidth = 960; inline const int DefaultEditKeyboardWindowHeight = 600; inline const int MinimumEditKeyboardWindowWidth = 500; inline const int MinimumEditKeyboardWindowHeight = 450; inline const int EditKeyboardTableMinWidth = 700; - inline const int DefaultEditShortcutsWindowWidth = 1050; + inline const int DefaultEditShortcutsWindowWidth = 1410; inline const int DefaultEditShortcutsWindowHeight = 600; inline const int MinimumEditShortcutsWindowWidth = 500; inline const int MinimumEditShortcutsWindowHeight = 500; @@ -21,7 +21,9 @@ namespace EditorConstants inline const long RemapTableArrowColIndex = 1; inline const long RemapTableNewColIndex = 2; inline const long RemapTableRemoveColIndex = 3; - inline const DWORD64 RemapTableDropDownWidth = 110; + inline const DWORD64 RemapTableDropDownWidth = 160; + inline const DWORD64 RemapTableDropDownSpacing = 10; + inline const long RemapTargetColumnWidth = 3 * RemapTableDropDownWidth + 3 * RemapTableDropDownSpacing + 65; // Shortcut table constants inline const long ShortcutTableColCount = 5; @@ -32,14 +34,14 @@ namespace EditorConstants inline const long ShortcutTableTargetAppColIndex = 3; inline const long ShortcutTableRemoveColIndex = 4; inline const long ShortcutArrowColumnWidth = 90; - inline const DWORD64 ShortcutTableDropDownWidth = 110; + inline const DWORD64 ShortcutTableDropDownWidth = 160; inline const DWORD64 ShortcutTableDropDownSpacing = 10; inline const long ShortcutOriginColumnWidth = 3 * ShortcutTableDropDownWidth + 3 * ShortcutTableDropDownSpacing; inline const long ShortcutTargetColumnWidth = 3 * ShortcutTableDropDownWidth + 3 * ShortcutTableDropDownSpacing + 15; // Drop down height used for both Edit Keyboard and Edit Shortcuts inline const DWORD64 TableDropDownHeight = 200; - inline const DWORD64 TableArrowColWidth = 230; + inline const DWORD64 TableArrowColWidth = 130; inline const DWORD64 TableRemoveColWidth = 20; inline const DWORD64 TableWarningColWidth = 20; inline const DWORD64 TableTargetAppColWidth = ShortcutTableDropDownWidth + TableRemoveColWidth * 2; diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyDropDownControl.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyDropDownControl.cpp index 991175019f..a3ff546ac8 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyDropDownControl.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyDropDownControl.cpp @@ -51,8 +51,10 @@ std::vector> KeyDropDownControl::GetKeyList(bool void KeyDropDownControl::SetDefaultProperties(bool isShortcut, bool renderDisable) { dropDown = ComboBox(); +#ifndef USE_NEW_DROPDOWN_WARNING_TIP warningFlyout = Flyout(); warningMessage = TextBlock(); +#endif if (!isShortcut) { @@ -77,6 +79,19 @@ void KeyDropDownControl::SetDefaultProperties(bool isShortcut, bool renderDisabl CheckAndUpdateKeyboardLayout(currentDropDown, isShortcut, renderDisable); }); +#ifdef USE_NEW_DROPDOWN_WARNING_TIP + // Attach the tip to the drop down + warningTip.Target(dropDown.as()); + dropDown.as().Loaded([&](winrt::Windows::Foundation::IInspectable const& sender, auto args) { + Media::VisualTreeHelper::GetChild(dropDown.as(), 0).as().Children().Append(warningTip); + }); + + // Tip properties + muxc::SymbolIconSource warningIcon; + warningIcon.Symbol(Symbol::Important); + warningTip.IconSource(warningIcon); + warningTip.IsLightDismissEnabled(true); +#else // Attach flyout to the drop down warningFlyout.as().Content(warningMessage.as()); @@ -86,6 +101,7 @@ void KeyDropDownControl::SetDefaultProperties(bool isShortcut, bool renderDisabl style.Setters().Append(Setter(Windows::UI::Xaml::Controls::Control::TabNavigationProperty(), winrt::box_value(Windows::UI::Xaml::Input::KeyboardNavigationMode::Cycle))); warningFlyout.as().FlyoutPresenterStyle(style); dropDown.as().ContextFlyout().SetAttachedFlyout((FrameworkElement)dropDown.as(), warningFlyout.as()); +#endif // To set the accessible name of the combo-box (by default index 1) SetAccessibleNameForComboBox(dropDown.as(), 1); @@ -376,6 +392,11 @@ void KeyDropDownControl::ValidateShortcutFromDropDownList(StackPanel table, Stac void KeyDropDownControl::SetDropDownError(ComboBox currentDropDown, hstring message) { currentDropDown.SelectedIndex(-1); + +#ifdef USE_NEW_DROPDOWN_WARNING_TIP + warningTip.Title(message); + warningTip.IsOpen(true); +#else warningMessage.as().Text(message); try { @@ -386,6 +407,7 @@ void KeyDropDownControl::SetDropDownError(ComboBox currentDropDown, hstring mess // If it's loading and some remaps are invalid from previous configs, avoid crashing when flyouts can't be showed yet. Logger::error(L"Failed to show dropdown error flyout: {}", message); } +#endif } // Function to add a shortcut to the UI control as combo boxes diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyDropDownControl.h b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyDropDownControl.h index 6c8e2c78b4..b9e3c86e92 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyDropDownControl.h +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyDropDownControl.h @@ -2,6 +2,9 @@ #include +// Enables the WinUI teaching tip to show as the new warning flyout +#define USE_NEW_DROPDOWN_WARNING_TIP + namespace KBMEditor { class KeyboardManagerState; @@ -38,11 +41,16 @@ private: // Stores the previous layout HKL previousLayout = 0; +#ifdef USE_NEW_DROPDOWN_WARNING_TIP + // Stores the teaching tip attached to the current drop down + muxc::TeachingTip warningTip; +#else // Stores the flyout warning message winrt::Windows::Foundation::IInspectable warningMessage; // Stores the flyout attached to the current drop down winrt::Windows::Foundation::IInspectable warningFlyout; +#endif // Stores whether a key to shortcut warning has to be ignored bool ignoreKeyToShortcutWarning; diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerEditorLibrary.vcxproj b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerEditorLibrary.vcxproj index 1ded6395b1..9163e66903 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerEditorLibrary.vcxproj +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerEditorLibrary.vcxproj @@ -1,5 +1,7 @@ + + 16.0 @@ -53,6 +55,7 @@ + @@ -74,6 +77,7 @@ + @@ -95,6 +99,9 @@ + + + @@ -103,6 +110,11 @@ + + + + + diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerEditorLibrary.vcxproj.filters b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerEditorLibrary.vcxproj.filters index ec2a3e2bd9..e32e52416b 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerEditorLibrary.vcxproj.filters +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerEditorLibrary.vcxproj.filters @@ -75,6 +75,9 @@ Header Files + + Header Files + @@ -128,6 +131,9 @@ Source Files + + Source Files + diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerState.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerState.cpp index c3a92f4e94..4ed756ba6f 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerState.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/KeyboardManagerState.cpp @@ -118,8 +118,15 @@ void KeyboardManagerState::AddKeyToLayout(const StackPanel& panel, const hstring border.Padding({ 20, 10, 20, 10 }); border.Margin({ 0, 0, 10, 0 }); - // Use the base low brush to be consistent with the theme - border.Background(Windows::UI::Xaml::Application::Current().Resources().Lookup(box_value(L"SystemControlBackgroundBaseLowBrush")).as()); + + // Based on settings-ui\Settings.UI\SettingsXAML\Controls\KeyVisual\KeyVisual.xaml + border.Background(Application::Current().Resources().Lookup(box_value(L"ButtonBackground")).as()); + border.BorderBrush(Application::Current().Resources().Lookup(box_value(L"ButtonBorderBrush")).as()); + border.BorderThickness(unbox_value(Application::Current().Resources().Lookup(box_value(L"ButtonBorderThemeThickness")))); + border.CornerRadius(unbox_value(Application::Current().Resources().Lookup(box_value(L"ControlCornerRadius")))); + remapKey.Foreground(Application::Current().Resources().Lookup(box_value(L"ButtonForeground")).as()); + remapKey.FontWeight(Text::FontWeights::SemiBold()); + remapKey.FontSize(20); border.HorizontalAlignment(HorizontalAlignment::Left); border.Child(remapKey); diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/ShortcutControl.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/ShortcutControl.cpp index f45c3d1952..b73ddef074 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/ShortcutControl.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/ShortcutControl.cpp @@ -90,13 +90,12 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector(parent, row, 1, targetAppTextBox)); keyboardRemapControlObjects.push_back(std::move(newrow)); - row.Padding({ 10, 10, 10, 10 }); + row.Padding({ 10, 15, 10, 5 }); + row.Margin({ 0, 0, 0, 2 }); row.Orientation(Orientation::Horizontal); - auto brush = Windows::UI::Xaml::Application::Current().Resources().Lookup(box_value(L"SystemControlBackgroundListLowBrush")).as(); - if (keyboardRemapControlObjects.size() % 2) - { - row.Background(brush); - } + row.Background(Application::Current().Resources().Lookup(box_value(L"CardBackgroundFillColorDefaultBrush")).as()); + row.BorderBrush(Application::Current().Resources().Lookup(box_value(L"CardStrokeColorDefaultBrush")).as()); + row.BorderThickness({ 0, 1, 0, 1 }); // ShortcutControl for the original shortcut auto origin = keyboardRemapControlObjects.back()[0]->GetShortcutControl(); @@ -104,14 +103,13 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector(); arrowIconContainer.Orientation(Orientation::Vertical); arrowIconContainer.VerticalAlignment(VerticalAlignment::Center); + arrowIconContainer.Margin({ 0, 0, 0, 10 }); row.Children().Append(arrowIconContainer); // ShortcutControl for the new shortcut @@ -122,6 +120,7 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector(); uint32_t rowIndex; // Get index of delete button @@ -216,7 +213,6 @@ void ShortcutControl::AddNewShortcutControlRow(StackPanel& parent, std::vector(); - row.Background(i % 2 ? brush : Media::SolidColorBrush(Colors::Transparent())); StackPanel sourceCol = row.Children().GetAt(0).as(); StackPanel targetCol = row.Children().GetAt(2).as(); TextBox targetApp = row.Children().GetAt(3).as().Children().GetAt(0).as().Children().GetAt(0).as(); @@ -294,8 +290,6 @@ void ShortcutControl::CreateDetectShortcutWindow(winrt::Windows::Foundation::IIn // ContentDialog requires manually setting the XamlRoot (https://learn.microsoft.com/uwp/api/windows.ui.xaml.controls.contentdialog#contentdialog-in-appwindow-or-xaml-islands) detectShortcutBox.XamlRoot(xamlRoot); detectShortcutBox.Title(box_value(GET_RESOURCE_STRING(IDS_TYPESHORTCUT_TITLE))); - detectShortcutBox.IsPrimaryButtonEnabled(false); - detectShortcutBox.IsSecondaryButtonEnabled(false); // Get the linked stack panel for the "Type shortcut" button that was clicked VariableSizedWrapGrid linkedShortcutVariableSizedWrapGrid = UIHelpers::GetSiblingElement(sender).as(); @@ -359,16 +353,13 @@ void ShortcutControl::CreateDetectShortcutWindow(winrt::Windows::Foundation::IIn onReleaseEnter(); }; - TextBlock primaryButtonText; - primaryButtonText.Text(GET_RESOURCE_STRING(IDS_OK_BUTTON)); - - Button primaryButton; - primaryButton.HorizontalAlignment(HorizontalAlignment::Stretch); - primaryButton.Margin({ 2, 2, 2, 2 }); - primaryButton.Content(primaryButtonText); - // OK button - primaryButton.Click([onAccept](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + detectShortcutBox.DefaultButton(ContentDialogButton::Primary); + detectShortcutBox.PrimaryButtonText(GET_RESOURCE_STRING(IDS_OK_BUTTON)); + detectShortcutBox.PrimaryButtonClick([onAccept](winrt::Windows::Foundation::IInspectable const& sender, ContentDialogButtonClickEventArgs const& args) { + // Cancel default dialog events + args.Cancel(true); + onAccept(); }); @@ -376,12 +367,10 @@ void ShortcutControl::CreateDetectShortcutWindow(winrt::Windows::Foundation::IIn keyboardManagerState.RegisterKeyDelay( VK_RETURN, selectDetectedShortcutAndResetKeys, - [primaryButton, onPressEnter, detectShortcutBox](DWORD) { + [onPressEnter, detectShortcutBox](DWORD) { detectShortcutBox.Dispatcher().RunAsync( Windows::UI::Core::CoreDispatcherPriority::Normal, - [primaryButton, onPressEnter] { - // Use the base medium low brush to be consistent with the theme - primaryButton.Background(Windows::UI::Xaml::Application::Current().Resources().Lookup(box_value(L"SystemControlBackgroundBaseMediumLowBrush")).as()); + [onPressEnter] { onPressEnter(); }); }, @@ -393,9 +382,6 @@ void ShortcutControl::CreateDetectShortcutWindow(winrt::Windows::Foundation::IIn }); }); - TextBlock cancelButtonText; - cancelButtonText.Text(GET_RESOURCE_STRING(IDS_CANCEL_BUTTON)); - auto onCancel = [&keyboardManagerState, detectShortcutBox, unregisterKeys, @@ -418,12 +404,12 @@ void ShortcutControl::CreateDetectShortcutWindow(winrt::Windows::Foundation::IIn unregisterKeys(); }; - Button cancelButton; - cancelButton.HorizontalAlignment(HorizontalAlignment::Stretch); - cancelButton.Margin({ 2, 2, 2, 2 }); - cancelButton.Content(cancelButtonText); // Cancel button - cancelButton.Click([onCancel](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + detectShortcutBox.CloseButtonText(GET_RESOURCE_STRING(IDS_CANCEL_BUTTON)); + detectShortcutBox.CloseButtonClick([onCancel](winrt::Windows::Foundation::IInspectable const& sender, ContentDialogButtonClickEventArgs const& args) { + // Cancel default dialog events + args.Cancel(true); + onCancel(); }); @@ -474,21 +460,6 @@ void ShortcutControl::CreateDetectShortcutWindow(winrt::Windows::Foundation::IIn holdEnterInfo.Margin({ 0, 0, 0, 0 }); stackPanel.Children().Append(holdEnterInfo); - ColumnDefinition primaryButtonColumn; - ColumnDefinition cancelButtonColumn; - - Grid buttonPanel; - buttonPanel.Margin({ 0, 20, 0, 0 }); - buttonPanel.HorizontalAlignment(HorizontalAlignment::Stretch); - buttonPanel.ColumnDefinitions().Append(primaryButtonColumn); - buttonPanel.ColumnDefinitions().Append(cancelButtonColumn); - buttonPanel.SetColumn(primaryButton, 0); - buttonPanel.SetColumn(cancelButton, 1); - - buttonPanel.Children().Append(primaryButton); - buttonPanel.Children().Append(cancelButton); - - stackPanel.Children().Append(buttonPanel); try { // If a layout update has been triggered by other methods (e.g.: adapting to zoom level), this may throw an exception. diff --git a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/SingleKeyRemapControl.cpp b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/SingleKeyRemapControl.cpp index b83e71906b..4e91b01dab 100644 --- a/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/SingleKeyRemapControl.cpp +++ b/src/modules/keyboardmanager/KeyboardManagerEditorLibrary/SingleKeyRemapControl.cpp @@ -84,33 +84,31 @@ void SingleKeyRemapControl::AddNewControlKeyRemapRow(StackPanel& parent, std::ve newrow.emplace_back(std::make_unique(parent, row, 1)); keyboardRemapControlObjects.push_back(std::move(newrow)); - row.Padding({ 10, 10, 10, 10 }); + row.Padding({ 10, 15, 10, 5 }); + row.Margin({ 0, 0, 0, 2 }); row.Orientation(Orientation::Horizontal); - auto brush = Windows::UI::Xaml::Application::Current().Resources().Lookup(box_value(L"SystemControlBackgroundListLowBrush")).as(); - if (keyboardRemapControlObjects.size() % 2) - { - row.Background(brush); - } + row.Background(Application::Current().Resources().Lookup(box_value(L"CardBackgroundFillColorDefaultBrush")).as()); + row.BorderBrush(Application::Current().Resources().Lookup(box_value(L"CardStrokeColorDefaultBrush")).as()); + row.BorderThickness({ 0, 1, 0, 1 }); // SingleKeyRemapControl for the original key. auto originalElement = keyboardRemapControlObjects.back()[0]->getSingleKeyRemapControl(); - originalElement.Width(EditorConstants::RemapTableDropDownWidth + EditorConstants::ShortcutTableDropDownSpacing); + originalElement.Width(EditorConstants::RemapTableDropDownWidth + EditorConstants::RemapTableDropDownSpacing); row.Children().Append(originalElement); // Arrow icon - FontIcon arrowIcon; - arrowIcon.FontFamily(Media::FontFamily(L"Segoe MDL2 Assets")); - arrowIcon.Glyph(L"\xE72A"); + SymbolIcon arrowIcon(Symbol::Forward); arrowIcon.VerticalAlignment(VerticalAlignment::Center); arrowIcon.HorizontalAlignment(HorizontalAlignment::Center); auto arrowIconContainer = UIHelpers::GetWrapped(arrowIcon, EditorConstants::TableArrowColWidth).as(); arrowIconContainer.Orientation(Orientation::Vertical); arrowIconContainer.VerticalAlignment(VerticalAlignment::Center); + arrowIconContainer.Margin({ 0, 0, 0, 10 }); row.Children().Append(arrowIconContainer); // SingleKeyRemapControl for the new remap key auto targetElement = keyboardRemapControlObjects.back()[1]->getSingleKeyRemapControl(); - targetElement.Width(EditorConstants::ShortcutTargetColumnWidth); + targetElement.Width(EditorConstants::RemapTargetColumnWidth); row.Children().Append(targetElement); // Set the key text if the two keys are not null (i.e. default args) @@ -135,13 +133,11 @@ void SingleKeyRemapControl::AddNewControlKeyRemapRow(StackPanel& parent, std::ve // Delete row button Windows::UI::Xaml::Controls::Button deleteRemapKeys; - FontIcon deleteSymbol; - deleteSymbol.FontFamily(Media::FontFamily(L"Segoe MDL2 Assets")); - deleteSymbol.Glyph(L"\xE74D"); - deleteRemapKeys.Content(deleteSymbol); + deleteRemapKeys.Content(SymbolIcon(Symbol::Delete)); deleteRemapKeys.Background(Media::SolidColorBrush(Colors::Transparent())); deleteRemapKeys.HorizontalAlignment(HorizontalAlignment::Center); - deleteRemapKeys.Click([&, parent, row, brush, deleteRemapKeys](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { + deleteRemapKeys.Margin({ 0, 0, 0, 10 }); + deleteRemapKeys.Click([&, parent, row, deleteRemapKeys](winrt::Windows::Foundation::IInspectable const& sender, RoutedEventArgs const&) { uint32_t rowIndex; // Get index of delete button UIElementCollection children = parent.Children(); @@ -157,7 +153,6 @@ void SingleKeyRemapControl::AddNewControlKeyRemapRow(StackPanel& parent, std::ve for (uint32_t i = rowIndex + 1; i < children.Size(); i++) { StackPanel row = children.GetAt(i).as(); - row.Background(i % 2 ? brush : Media::SolidColorBrush(Colors::Transparent())); StackPanel sourceCol = row.Children().GetAt(0).as(); StackPanel targetCol = row.Children().GetAt(2).as(); Button delButton = row.Children().GetAt(3).as - - - - - - - - + AutomationProperties.Name="{x:Static p:Resources.ContextMenuItemAdditionalInformation}" + Foreground="{DynamicResource TextFillColorPrimaryBrush}" + Text="{Binding Title}" /> + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/SettingsReader.cs b/src/modules/launcher/PowerLauncher/SettingsReader.cs index 30ef4bbe75..a4fed3d904 100644 --- a/src/modules/launcher/PowerLauncher/SettingsReader.cs +++ b/src/modules/launcher/PowerLauncher/SettingsReader.cs @@ -2,6 +2,7 @@ // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.IO; using System.IO.Abstractions; @@ -30,6 +31,7 @@ namespace PowerLauncher private static readonly object _readSyncObject = new object(); private readonly PowerToysRunSettings _settings; private readonly ThemeManager _themeManager; + private Action _refreshPluginsOverviewCallback; private IFileSystemWatcher _watcher; @@ -92,7 +94,7 @@ namespace PowerLauncher foreach (var setting in overloadSettings.Plugins) { var plugin = PluginManager.AllPlugins.FirstOrDefault(x => x.Metadata.ID == setting.Id); - plugin?.Update(setting, App.API); + plugin?.Update(setting, App.API, _refreshPluginsOverviewCallback); } var openPowerlauncher = ConvertHotkey(overloadSettings.Properties.OpenPowerLauncher); @@ -217,6 +219,11 @@ namespace PowerLauncher Monitor.Exit(_readSyncObject); } + public void SetRefreshPluginsOverviewCallback(Action callback) + { + _refreshPluginsOverviewCallback = callback; + } + private static string ConvertHotkey(HotkeySettings hotkey) { Key key = KeyInterop.KeyFromVirtualKey(hotkey.Code); @@ -281,6 +288,9 @@ namespace PowerLauncher if (option.Key != null && defaultOptions.TryGetValue(option.Key, out PluginAdditionalOption defaultOption)) { defaultOption.Value = option.Value; + defaultOption.ComboBoxValue = option.ComboBoxValue; + defaultOption.TextValue = option.TextValue; + defaultOption.NumberValue = option.NumberValue; } } diff --git a/src/modules/launcher/PowerLauncher/Styles/Styles.xaml b/src/modules/launcher/PowerLauncher/Styles/Styles.xaml new file mode 100644 index 0000000000..d1fb1acb3f --- /dev/null +++ b/src/modules/launcher/PowerLauncher/Styles/Styles.xaml @@ -0,0 +1,154 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/Themes/Dark.xaml b/src/modules/launcher/PowerLauncher/Themes/Dark.xaml index 7ee5c17597..9dfe236b0f 100644 --- a/src/modules/launcher/PowerLauncher/Themes/Dark.xaml +++ b/src/modules/launcher/PowerLauncher/Themes/Dark.xaml @@ -1,9 +1,10 @@ - + Dark.Accent1 @@ -13,20 +14,4 @@ Accent1 Black False - - #FF818181 - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/Themes/HighContrast1.xaml b/src/modules/launcher/PowerLauncher/Themes/HighContrast1.xaml index cb1b0a78ea..bbf44dbae6 100644 --- a/src/modules/launcher/PowerLauncher/Themes/HighContrast1.xaml +++ b/src/modules/launcher/PowerLauncher/Themes/HighContrast1.xaml @@ -1,9 +1,10 @@ - + HighContrast.Accent2 @@ -12,20 +13,4 @@ HighContrast Accent2 White - - #ffff00 - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/Themes/HighContrast2.xaml b/src/modules/launcher/PowerLauncher/Themes/HighContrast2.xaml index e5562562f3..617c75e717 100644 --- a/src/modules/launcher/PowerLauncher/Themes/HighContrast2.xaml +++ b/src/modules/launcher/PowerLauncher/Themes/HighContrast2.xaml @@ -1,9 +1,10 @@ - + HighContrast.Accent3 @@ -12,20 +13,4 @@ HighContrast Accent3 White - - #ffff00 - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/Themes/HighContrastBlack.xaml b/src/modules/launcher/PowerLauncher/Themes/HighContrastBlack.xaml index dc8dc608cc..22d86a5da9 100644 --- a/src/modules/launcher/PowerLauncher/Themes/HighContrastBlack.xaml +++ b/src/modules/launcher/PowerLauncher/Themes/HighContrastBlack.xaml @@ -1,9 +1,10 @@ - + HighContrast.Accent4 @@ -12,20 +13,4 @@ HighContrast Accent4 White - - #66FFFFFF - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/Themes/HighContrastWhite.xaml b/src/modules/launcher/PowerLauncher/Themes/HighContrastWhite.xaml index 687eca98d1..98d28be0a7 100644 --- a/src/modules/launcher/PowerLauncher/Themes/HighContrastWhite.xaml +++ b/src/modules/launcher/PowerLauncher/Themes/HighContrastWhite.xaml @@ -1,9 +1,10 @@ - + HighContrast.Accent5 @@ -12,20 +13,4 @@ HighContrast Accent5 White - - #66000000 - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/Themes/Light.xaml b/src/modules/launcher/PowerLauncher/Themes/Light.xaml index 9c063003d6..ad4566b324 100644 --- a/src/modules/launcher/PowerLauncher/Themes/Light.xaml +++ b/src/modules/launcher/PowerLauncher/Themes/Light.xaml @@ -1,9 +1,10 @@ - + Light.Accent1 @@ -13,20 +14,4 @@ Accent1 White False - - #66000000 - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs b/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs index 098e16a9ba..78698733f7 100644 --- a/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs +++ b/src/modules/launcher/PowerLauncher/ViewModel/MainViewModel.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Reflection; @@ -14,6 +15,7 @@ using System.Windows.Input; using System.Windows.Threading; using Common.UI; using interop; +using Mages.Core.Runtime.Converters; using Microsoft.PowerLauncher.Telemetry; using Microsoft.PowerToys.Telemetry; using PowerLauncher.Helper; @@ -79,7 +81,6 @@ namespace PowerLauncher.ViewModel Results = new ResultsViewModel(_settings, this); History = new ResultsViewModel(_settings, this); _selectedResults = Results; - InitializeKeyCommands(); RegisterResultsUpdatedEvent(); } @@ -345,6 +346,15 @@ namespace PowerLauncher.ViewModel if (_queryText != value) { _queryText = value; + if (string.IsNullOrEmpty(_queryText) || string.IsNullOrWhiteSpace(_queryText)) + { + PluginsOverviewVisibility = Visibility.Visible; + } + else + { + PluginsOverviewVisibility = Visibility.Collapsed; + } + OnPropertyChanged(nameof(QueryText)); } } @@ -1028,34 +1038,6 @@ namespace PowerLauncher.ViewModel } } - public void ColdStartFix() - { - // Fix Cold start for List view xaml island - List list = new List(); - Result r = new Result - { - Title = "hello", - }; - list.Add(r); - Results.AddResults(list, _updateToken); - Results.Clear(); - - // Fix Cold start for plugins, "m" is just a random string needed to query results - var pluginQueryPairs = QueryBuilder.Build("m"); - - // To execute a query corresponding to each plugin - foreach (KeyValuePair pluginQueryItem in pluginQueryPairs) - { - var plugin = pluginQueryItem.Key; - var query = pluginQueryItem.Value; - - if (!plugin.Metadata.Disabled && plugin.Metadata.Name != "Window Walker") - { - _ = PluginManager.QueryForPlugin(plugin, query); - } - } - } - public void HandleContextMenu(Key acceleratorKey, ModifierKeys acceleratorModifiers) { var results = SelectedResults; @@ -1227,5 +1209,38 @@ namespace PowerLauncher.ViewModel action.Invoke(); } } + + public ObservableCollection Plugins { get; } = new(); + + private Visibility _pluginsOverviewVisibility = Visibility.Visible; + + public Visibility PluginsOverviewVisibility + { + get => _pluginsOverviewVisibility; + + set + { + if (_pluginsOverviewVisibility != value) + { + _pluginsOverviewVisibility = value; + OnPropertyChanged(nameof(PluginsOverviewVisibility)); + } + } + } + + public void RefreshPluginsOverview() + { + Log.Info("Refresh plugins overview", GetType()); + + Application.Current.Dispatcher.Invoke(() => + { + Plugins.Clear(); + + foreach (var p in PluginManager.AllPlugins.Where(a => a.IsPluginInitialized && !a.Metadata.Disabled && a.Metadata.ActionKeyword != string.Empty)) + { + Plugins.Add(p); + } + }); + } } } diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/IShellLinkHelper.cs b/src/modules/launcher/Wox.Infrastructure/IShellLinkHelper.cs similarity index 90% rename from src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/IShellLinkHelper.cs rename to src/modules/launcher/Wox.Infrastructure/IShellLinkHelper.cs index 45b7e64083..6422e108ce 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/IShellLinkHelper.cs +++ b/src/modules/launcher/Wox.Infrastructure/IShellLinkHelper.cs @@ -2,7 +2,7 @@ // 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.Program.Programs +namespace Wox.Infrastructure { public interface IShellLinkHelper { diff --git a/src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs b/src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs index 2a6e760336..6a10753df0 100644 --- a/src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs +++ b/src/modules/launcher/Wox.Infrastructure/Image/ImageLoader.cs @@ -5,14 +5,15 @@ using System; using System.Collections.Concurrent; using System.Globalization; +using System.IO; using System.IO.Abstractions; using System.Linq; using System.Reflection; +using System.Runtime.InteropServices; using System.Threading.Tasks; using System.Windows.Media; using System.Windows.Media.Imaging; using ManagedCommon; -using Wox.Infrastructure.UserSettings; using Wox.Plugin; using Wox.Plugin.Logger; @@ -43,20 +44,46 @@ namespace Wox.Infrastructure.Image ".ico", }; + // Checks whether it is a valid PNG by checking the 8 bytes at the beginning of the file. + public static bool IsValidPngSignature(string filePath) + { + byte[] pngSignature = { 137, 80, 78, 71, 13, 10, 26, 10 }; + byte[] buffer = new byte[8]; + + using FileStream fs = new(filePath, FileMode.Open, FileAccess.Read); + return fs.Read(buffer, 0, buffer.Length) == buffer.Length && pngSignature.SequenceEqual(buffer); + } + public static void Initialize(Theme theme) { _hashGenerator = new ImageHashGenerator(); foreach (var icon in new[] { Constant.ErrorIcon, Constant.LightThemedErrorIcon }) { - BitmapImage bmi = new BitmapImage(); - bmi.BeginInit(); - bmi.UriSource = new Uri(icon); - bmi.CacheOption = BitmapCacheOption.OnLoad; - bmi.EndInit(); - ImageSource img = bmi; - img.Freeze(); - ImageCache[icon] = img; + var uri = new Uri(icon); + + try + { + if (File.Exists(uri.LocalPath) && IsValidPngSignature(uri.LocalPath)) + { + BitmapImage bmi = new BitmapImage(); + bmi.BeginInit(); + bmi.UriSource = uri; + bmi.CacheOption = BitmapCacheOption.OnLoad; + bmi.EndInit(); + ImageSource img = bmi; + img.Freeze(); + ImageCache[icon] = img; + } + else + { + Log.Error($"Image file '{icon}' is not a valid PNG.", MethodBase.GetCurrentMethod().DeclaringType); + } + } + catch (COMException comEx) + { + Log.Exception($"COMException was thrown in {uri.LocalPath} file.", comEx, MethodBase.GetCurrentMethod().DeclaringType); + } } UpdateIconPath(theme); diff --git a/src/modules/launcher/Wox.Infrastructure/Image/WindowsThumbnailProvider.cs b/src/modules/launcher/Wox.Infrastructure/Image/WindowsThumbnailProvider.cs index bcd645d1a4..4dd9481678 100644 --- a/src/modules/launcher/Wox.Infrastructure/Image/WindowsThumbnailProvider.cs +++ b/src/modules/launcher/Wox.Infrastructure/Image/WindowsThumbnailProvider.cs @@ -82,7 +82,19 @@ namespace Wox.Infrastructure.Image public static BitmapSource GetThumbnail(string fileName, int width, int height, ThumbnailOptions options) { - IntPtr hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options); + IntPtr hBitmap = IntPtr.Zero; + if (Path.GetExtension(fileName).Equals(".lnk", StringComparison.OrdinalIgnoreCase)) + { + // If the file has a '.lnk' extension, it is a shortcut file. Use the shellLinkHelper to retrieve the actual target file path from the shortcut. + IShellLinkHelper shellLinkHelper = new ShellLinkHelper(); + + string targetFilePath = shellLinkHelper.RetrieveTargetPath(fileName); + hBitmap = ExtractIconToHBitmap(targetFilePath); + } + else + { + hBitmap = GetHBitmap(Path.GetFullPath(fileName), width, height, options); + } try { @@ -119,7 +131,7 @@ namespace Wox.Infrastructure.Image HResult hr = ((IShellItemImageFactory)nativeShellItem).GetImage(nativeSize, options, out hBitmap); - // if extracting image thumbnail and failed, extract shell icon + // if extracting image thumbnail and failed, extract shell icon if (options == ThumbnailOptions.ThumbnailOnly && hr == HResult.ExtractionFailed) { hr = ((IShellItemImageFactory)nativeShellItem).GetImage(nativeSize, ThumbnailOptions.IconOnly, out hBitmap); @@ -146,6 +158,19 @@ namespace Wox.Infrastructure.Image } } + public static IntPtr ExtractIconToHBitmap(string fileName) + { + // Extracts the icon associated with the file + using (System.Drawing.Icon thumbnailIcon = System.Drawing.Icon.ExtractAssociatedIcon(fileName)) + { + // Convert to Bitmap + using (System.Drawing.Bitmap bitmap = thumbnailIcon.ToBitmap()) + { + return bitmap.GetHbitmap(); + } + } + } + private static bool logReportedAdobeReaderDetected; // Keep track if Adobe Reader detection has been logged yet. private static bool logReportedErrorInDetectingAdobeReader; // Keep track if we reported an exception while trying to detect Adobe Reader yet. private static bool adobeReaderDetectionLastResult; // The last result when Adobe Reader detection has read the registry. diff --git a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/ShellLinkHelper.cs b/src/modules/launcher/Wox.Infrastructure/ShellLinkHelper.cs similarity index 97% rename from src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/ShellLinkHelper.cs rename to src/modules/launcher/Wox.Infrastructure/ShellLinkHelper.cs index a664e7fc69..2dd2b24096 100644 --- a/src/modules/launcher/Plugins/Microsoft.Plugin.Program/Programs/ShellLinkHelper.cs +++ b/src/modules/launcher/Wox.Infrastructure/ShellLinkHelper.cs @@ -7,10 +7,9 @@ using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Text; using Accessibility; -using Microsoft.Plugin.Program.Logger; using Wox.Plugin.Logger; -namespace Microsoft.Plugin.Program.Programs +namespace Wox.Infrastructure { public class ShellLinkHelper : IShellLinkHelper { @@ -142,7 +141,7 @@ namespace Microsoft.Plugin.Program.Programs } catch (System.IO.FileNotFoundException ex) { - ProgramLogger.Exception("Path could not be retrieved", ex, GetType(), path); + Log.Exception("Path could not be retrieved", ex, GetType(), path); return string.Empty; } @@ -165,7 +164,7 @@ namespace Microsoft.Plugin.Program.Programs ((IShellLinkW)link).GetDescription(buffer, MAX_PATH); Description = buffer.ToString(); } - catch (Exception e) + catch (System.Exception e) { Log.Exception($"Failed to fetch description for {target}, {e.Message}", e, GetType()); Description = string.Empty; diff --git a/src/modules/launcher/Wox.Infrastructure/StringMatcher.cs b/src/modules/launcher/Wox.Infrastructure/StringMatcher.cs index f74c4d75b4..4d84d875b6 100644 --- a/src/modules/launcher/Wox.Infrastructure/StringMatcher.cs +++ b/src/modules/launcher/Wox.Infrastructure/StringMatcher.cs @@ -226,9 +226,7 @@ namespace Wox.Infrastructure } else { - int? ind = spaceIndices.OrderBy(item => (firstMatchIndex - item)).Where(item => firstMatchIndex > item).FirstOrDefault(); - int closestSpaceIndex = ind ?? -1; - return closestSpaceIndex; + return spaceIndices.OrderBy(item => (firstMatchIndex - item)).Where(item => firstMatchIndex > item).FirstOrDefault(-1); } } diff --git a/src/modules/launcher/Wox.Plugin/PluginPair.cs b/src/modules/launcher/Wox.Plugin/PluginPair.cs index 3d46dfde74..6fd0834483 100644 --- a/src/modules/launcher/Wox.Plugin/PluginPair.cs +++ b/src/modules/launcher/Wox.Plugin/PluginPair.cs @@ -4,10 +4,10 @@ using System; using System.Diagnostics; -using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.Loader; +using System.Windows; using Microsoft.PowerToys.Settings.UI.Library; using Wox.Plugin.Logger; using Wox.Plugin.Properties; @@ -56,13 +56,20 @@ namespace Wox.Plugin return; } - public void Update(PowerLauncherPluginSettings setting, IPublicAPI api) + public void Update(PowerLauncherPluginSettings setting, IPublicAPI api, Action refreshPluginsOverviewCallback) { if (setting == null || api == null) { return; } + bool refreshOverview = false; + if (Metadata.Disabled != setting.Disabled + || Metadata.ActionKeyword != setting.ActionKeyword) + { + refreshOverview = true; + } + // If the enabled state is policy managed then we skip the update of the disabled state as it must be a manual settings.json manipulation. if (!Metadata.IsEnabledPolicyConfigured) { @@ -74,7 +81,7 @@ namespace Wox.Plugin if (!IsPluginInitialized) { string description = $"{Resources.FailedToLoadPluginDescription} {Metadata.Name}\n\n{Resources.FailedToLoadPluginDescriptionPartTwo}"; - api.ShowMsg(Resources.FailedToLoadPluginTitle, description, string.Empty, false); + Application.Current.Dispatcher.InvokeAsync(() => api.ShowMsg(Resources.FailedToLoadPluginTitle, description, string.Empty, false)); } } else @@ -94,6 +101,11 @@ namespace Wox.Plugin { (Plugin as IReloadable)?.ReloadData(); } + + if (refreshOverview) + { + refreshPluginsOverviewCallback?.Invoke(); + } } public override string ToString() diff --git a/src/modules/peek/Peek.Common/Extensions/ApplicationExtensions.cs b/src/modules/peek/Peek.Common/Extensions/ApplicationExtensions.cs new file mode 100644 index 0000000000..ab3ae3f79b --- /dev/null +++ b/src/modules/peek/Peek.Common/Extensions/ApplicationExtensions.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.UI.Xaml; + +namespace Peek.Common.Extensions +{ + public static class ApplicationExtensions + { + /// + /// Get registered services at the application level from anywhere + public static T GetService(this Application application) + where T : class + { + return (application as IApp)!.GetService(); + } + } +} diff --git a/src/modules/peek/Peek.Common/IApp.cs b/src/modules/peek/Peek.Common/IApp.cs new file mode 100644 index 0000000000..8f6c9e3441 --- /dev/null +++ b/src/modules/peek/Peek.Common/IApp.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Peek.Common +{ + public interface IApp + { + public T GetService() + where T : class; + } +} diff --git a/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml b/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml index 1f2329c987..674cd85079 100644 --- a/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml +++ b/src/modules/peek/Peek.FilePreviewer/FilePreview.xaml @@ -40,6 +40,9 @@ Source="{x:Bind VideoPreviewer.Preview, Mode=OneWay}" ToolTipService.ToolTip="{x:Bind ImageInfoTooltip, Mode=OneWay}" Visibility="{x:Bind IsPreviewVisible(VideoPreviewer, Previewer.State), Mode=OneWay}"> + + + LoadSettingsFromJson()); + } + + private void LoadSettingsFromJson() + { + lock (_loadingSettingsLock) + { + var retry = true; + var retryCount = 0; + + while (retry) + { + try + { + retryCount++; + + if (!_settingsUtils.SettingsExists(PeekSettings.ModuleName, PeekPreviewSettings.FileName)) + { + Logger.LogInfo("Peek preview-settings.json was missing, creating a new one"); + var defaultSettings = new PeekPreviewSettings(); + _settingsUtils.SaveSettings(defaultSettings.ToJsonString(), PeekSettings.ModuleName, PeekPreviewSettings.FileName); + } + + var settings = _settingsUtils.GetSettingsOrDefault(PeekSettings.ModuleName, PeekPreviewSettings.FileName); + if (settings != null) + { + SourceCodeWrapText = settings.SourceCodeWrapText.Value; + SourceCodeTryFormat = settings.SourceCodeTryFormat.Value; + } + + retry = false; + } + catch (IOException e) + { + if (retryCount > MaxNumberOfRetry) + { + retry = false; + Logger.LogError($"Failed to deserialize preview settings, Retrying {e.Message}", e); + } + else + { + Thread.Sleep(500); + } + } + catch (Exception ex) + { + retry = false; + Logger.LogError("Failed to read changed preview settings", ex); + } + } + } + } + } +} diff --git a/src/modules/peek/Peek.FilePreviewer/Peek.FilePreviewer.csproj b/src/modules/peek/Peek.FilePreviewer/Peek.FilePreviewer.csproj index 1a8832a4e0..f10a171428 100644 --- a/src/modules/peek/Peek.FilePreviewer/Peek.FilePreviewer.csproj +++ b/src/modules/peek/Peek.FilePreviewer/Peek.FilePreviewer.csproj @@ -31,6 +31,8 @@ + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/PreviewerFactory.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/PreviewerFactory.cs index 8a505d9f9c..661fbb4360 100644 --- a/src/modules/peek/Peek.FilePreviewer/Previewers/PreviewerFactory.cs +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/PreviewerFactory.cs @@ -3,7 +3,10 @@ // See the LICENSE file in the project root for more information. using Microsoft.PowerToys.Telemetry; +using Microsoft.UI.Xaml; +using Peek.Common.Extensions; using Peek.Common.Models; +using Peek.FilePreviewer.Models; using Peek.FilePreviewer.Previewers.Archives; using Peek.UI.Telemetry.Events; @@ -11,6 +14,13 @@ namespace Peek.FilePreviewer.Previewers { public class PreviewerFactory { + private readonly IPreviewSettings _previewSettings; + + public PreviewerFactory() + { + _previewSettings = Application.Current.GetService(); + } + public IPreviewer Create(IFileSystemItem file) { if (ImagePreviewer.IsFileTypeSupported(file.Extension)) @@ -23,7 +33,7 @@ namespace Peek.FilePreviewer.Previewers } else if (WebBrowserPreviewer.IsFileTypeSupported(file.Extension)) { - return new WebBrowserPreviewer(file); + return new WebBrowserPreviewer(file, _previewSettings); } else if (ArchivePreviewer.IsFileTypeSupported(file.Extension)) { diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/WebBrowserPreviewer/Helpers/MonacoHelper.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/WebBrowserPreviewer/Helpers/MonacoHelper.cs index 57c72517d4..deff795baf 100644 --- a/src/modules/peek/Peek.FilePreviewer/Previewers/WebBrowserPreviewer/Helpers/MonacoHelper.cs +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/WebBrowserPreviewer/Helpers/MonacoHelper.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text.Json; using Common.UI; using ManagedCommon; @@ -23,11 +24,11 @@ namespace Peek.FilePreviewer.Previewers JsonDocument languageListDocument = Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.GetLanguages(); JsonElement languageList = languageListDocument.RootElement.GetProperty("list"); foreach (JsonElement e in languageList.EnumerateArray()) - { - if (e.TryGetProperty("extensions", out var extensions)) { - for (int j = 0; j < extensions.GetArrayLength(); j++) + if (e.TryGetProperty("extensions", out var extensions)) { + for (int j = 0; j < extensions.GetArrayLength(); j++) + { set.Add(extensions[j].ToString()); } } @@ -44,15 +45,32 @@ namespace Peek.FilePreviewer.Previewers /// /// Prepares temp html for the previewing /// - public static string PreviewTempFile(string fileText, string extension, string tempFolder) + public static string PreviewTempFile(string fileText, string extension, string tempFolder, bool tryFormat, bool wrapText) { // TODO: check if file is too big, add MaxFileSize to settings - return InitializeIndexFileAndSelectedFile(fileText, extension, tempFolder); + return InitializeIndexFileAndSelectedFile(fileText, extension, tempFolder, tryFormat, wrapText); } - private static string InitializeIndexFileAndSelectedFile(string fileContent, string extension, string tempFolder) + private static string InitializeIndexFileAndSelectedFile(string fileContent, string extension, string tempFolder, bool tryFormat, bool wrapText) { string vsCodeLangSet = Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.GetLanguage(extension); + + if (tryFormat) + { + var formatter = Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.Formatters.SingleOrDefault(f => f.LangSet == vsCodeLangSet); + if (formatter != null) + { + try + { + fileContent = formatter.Format(fileContent); + } + catch (Exception ex) + { + Logger.LogError($"Failed to apply formatting", ex); + } + } + } + string base64FileCode = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(fileContent)); string theme = ThemeManager.GetWindowsBaseColor().ToLowerInvariant(); @@ -60,7 +78,7 @@ namespace Peek.FilePreviewer.Previewers string html = Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.ReadIndexHtml(); html = html.Replace("[[PT_LANG]]", vsCodeLangSet, StringComparison.InvariantCulture); - html = html.Replace("[[PT_WRAP]]", "1", StringComparison.InvariantCulture); // TODO: add to settings + html = html.Replace("[[PT_WRAP]]", wrapText ? "1" : "0", StringComparison.InvariantCulture); html = html.Replace("[[PT_THEME]]", theme, StringComparison.InvariantCulture); html = html.Replace("[[PT_CODE]]", base64FileCode, StringComparison.InvariantCulture); html = html.Replace("[[PT_URL]]", Microsoft.PowerToys.FilePreviewCommon.MonacoHelper.VirtualHostName, StringComparison.InvariantCulture); diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/WebBrowserPreviewer/Helpers/ReadHelper.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/WebBrowserPreviewer/Helpers/ReadHelper.cs index f7696d164a..1d0cb804e2 100644 --- a/src/modules/peek/Peek.FilePreviewer/Previewers/WebBrowserPreviewer/Helpers/ReadHelper.cs +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/WebBrowserPreviewer/Helpers/ReadHelper.cs @@ -5,6 +5,7 @@ using System.IO; using System.Text; using System.Threading.Tasks; +using UtfUnknown; namespace Peek.FilePreviewer.Previewers { @@ -12,8 +13,14 @@ namespace Peek.FilePreviewer.Previewers { public static async Task Read(string path) { + DetectionResult result = CharsetDetector.DetectFromFile(path); + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + // Check if the detected encoding is not null, otherwise default to UTF-8 + Encoding encodingToUse = result.Detected?.Encoding ?? Encoding.UTF8; + using var fs = OpenReadOnly(path); - using var sr = new StreamReader(fs, Encoding.UTF8); + using var sr = new StreamReader(fs, encodingToUse); string content = await sr.ReadToEndAsync(); return content; diff --git a/src/modules/peek/Peek.FilePreviewer/Previewers/WebBrowserPreviewer/WebBrowserPreviewer.cs b/src/modules/peek/Peek.FilePreviewer/Previewers/WebBrowserPreviewer/WebBrowserPreviewer.cs index f13bad1d39..adbcd8eb4c 100644 --- a/src/modules/peek/Peek.FilePreviewer/Previewers/WebBrowserPreviewer/WebBrowserPreviewer.cs +++ b/src/modules/peek/Peek.FilePreviewer/Previewers/WebBrowserPreviewer/WebBrowserPreviewer.cs @@ -13,13 +13,14 @@ using Peek.Common.Extensions; using Peek.Common.Helpers; using Peek.Common.Models; using Peek.FilePreviewer.Models; -using Windows.Foundation; namespace Peek.FilePreviewer.Previewers { public partial class WebBrowserPreviewer : ObservableObject, IBrowserPreviewer, IDisposable { - private static readonly HashSet _supportedFileTypes = new HashSet + private readonly IPreviewSettings _previewSettings; + + private static readonly HashSet _supportedFileTypes = new() { // Web ".html", @@ -43,8 +44,9 @@ namespace Peek.FilePreviewer.Previewers private bool disposed; - public WebBrowserPreviewer(IFileSystemItem file) + public WebBrowserPreviewer(IFileSystemItem file, IPreviewSettings previewSettings) { + _previewSettings = previewSettings; File = file; Dispatcher = DispatcherQueue.GetForCurrentThread(); } @@ -109,7 +111,7 @@ namespace Peek.FilePreviewer.Previewers if (IsDevFilePreview && !isHtml && !isMarkdown) { var raw = await ReadHelper.Read(File.Path.ToString()); - Preview = new Uri(MonacoHelper.PreviewTempFile(raw, File.Extension, TempFolderPath.Path)); + Preview = new Uri(MonacoHelper.PreviewTempFile(raw, File.Extension, TempFolderPath.Path, _previewSettings.SourceCodeTryFormat, _previewSettings.SourceCodeWrapText)); } else if (isMarkdown) { diff --git a/src/modules/peek/Peek.UI/PeekXAML/App.xaml.cs b/src/modules/peek/Peek.UI/PeekXAML/App.xaml.cs index 8e99c57977..54661f2bb4 100644 --- a/src/modules/peek/Peek.UI/PeekXAML/App.xaml.cs +++ b/src/modules/peek/Peek.UI/PeekXAML/App.xaml.cs @@ -3,12 +3,16 @@ // See the LICENSE file in the project root for more information. using System; +using interop; using ManagedCommon; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.PowerToys.Telemetry; using Microsoft.UI.Xaml; +using Peek.Common; using Peek.FilePreviewer; +using Peek.FilePreviewer.Models; +using Peek.UI.Native; using Peek.UI.Telemetry.Events; using Peek.UI.Views; @@ -17,7 +21,7 @@ namespace Peek.UI /// /// Provides application-specific behavior to supplement the default Application class. /// - public partial class App : Application + public partial class App : Application, IApp { public static int PowerToysPID { get; set; } @@ -26,7 +30,7 @@ namespace Peek.UI get; } - private Window? Window { get; set; } + private MainWindow? Window { get; set; } /// /// Initializes a new instance of the class. @@ -46,6 +50,7 @@ namespace Peek.UI // Core Services services.AddTransient(); services.AddSingleton(); + services.AddSingleton(); // Views and ViewModels services.AddTransient(); @@ -57,7 +62,7 @@ namespace Peek.UI UnhandledException += App_UnhandledException; } - public static T GetService() + public T GetService() where T : class { if ((App.Current as App)!.Host.Services.GetService(typeof(T)) is not T service) @@ -93,12 +98,32 @@ namespace Peek.UI } } - Window = new MainWindow(); + NativeEventWaiter.WaitForEventLoop(Constants.ShowPeekEvent(), OnPeekHotkey); } private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) { PowerToysTelemetry.Log.WriteEvent(new ErrorEvent() { HResult = (Common.Models.HResult)e.Exception.HResult, Failure = ErrorEvent.FailureType.AppCrash }); } + + /// + /// Handle Peek hotkey + /// + private void OnPeekHotkey() + { + // Need to read the foreground HWND before activating Peek to avoid focus stealing + // Foreground HWND must always be Explorer or Desktop + var foregroundWindowHandle = Windows.Win32.PInvoke.GetForegroundWindow(); + + bool firstActivation = false; + + if (Window == null) + { + firstActivation = true; + Window = new MainWindow(); + } + + Window.Toggle(firstActivation, foregroundWindowHandle); + } } } diff --git a/src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml b/src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml index b066bc00a1..e3c8c7d4ec 100644 --- a/src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml +++ b/src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml @@ -22,7 +22,11 @@ - + + diff --git a/src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml.cs b/src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml.cs index 5254e54513..7fc54a5520 100644 --- a/src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml.cs +++ b/src/modules/peek/Peek.UI/PeekXAML/MainWindow.xaml.cs @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation +// 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 interop; using ManagedCommon; using Microsoft.PowerToys.Telemetry; using Microsoft.UI; using Microsoft.UI.Windowing; +using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Input; using Peek.Common.Constants; +using Peek.Common.Extensions; using Peek.FilePreviewer.Models; using Peek.UI.Extensions; using Peek.UI.Helpers; -using Peek.UI.Native; using Peek.UI.Telemetry.Events; using Windows.Foundation; using WinUIEx; @@ -28,7 +28,6 @@ namespace Peek.UI public MainWindowViewModel ViewModel { get; } private ThemeListener? themeListener; - private bool activated; public MainWindow() { @@ -45,56 +44,22 @@ namespace Peek.UI Logger.LogError($"HandleThemeChange exception. Please install .NET 4.", e); } - ViewModel = App.GetService(); - - NativeEventWaiter.WaitForEventLoop(Constants.ShowPeekEvent(), OnPeekHotkey); + ViewModel = Application.Current.GetService(); TitleBarControl.SetTitleBarToWindow(this); AppWindow.Closing += AppWindow_Closing; } - private void HandleThemeChange() - { - AppWindow appWindow = this.AppWindow; - - if (ThemeHelpers.GetAppTheme() == AppTheme.Light) - { - appWindow.TitleBar.ButtonForegroundColor = Colors.DarkSlateGray; - } - else - { - appWindow.TitleBar.ButtonForegroundColor = Colors.White; - } - } - - private void PeekWindow_Activated(object sender, Microsoft.UI.Xaml.WindowActivatedEventArgs args) - { - if (args.WindowActivationState == Microsoft.UI.Xaml.WindowActivationState.Deactivated) - { - var userSettings = App.GetService(); - if (userSettings.CloseAfterLosingFocus) - { - Uninitialize(); - } - } - } - /// - /// Handle Peek hotkey, by toggling the window visibility and querying files when necessary. + /// Toggling the window visibility and querying files when necessary. /// - private void OnPeekHotkey() + public void Toggle(bool firstActivation, Windows.Win32.Foundation.HWND foregroundWindowHandle) { - // Need to read the foreground HWND before activating Peek to avoid focus stealing - // Foreground HWND must always be Explorer or Desktop - var foregroundWindowHandle = Windows.Win32.PInvoke.GetForegroundWindow(); - - // First Peek activation - if (!activated) + if (firstActivation) { Activate(); Initialize(foregroundWindowHandle); - activated = true; return; } @@ -115,6 +80,32 @@ namespace Peek.UI } } + private void HandleThemeChange() + { + AppWindow appWindow = this.AppWindow; + + if (ThemeHelpers.GetAppTheme() == AppTheme.Light) + { + appWindow.TitleBar.ButtonForegroundColor = Colors.DarkSlateGray; + } + else + { + appWindow.TitleBar.ButtonForegroundColor = Colors.White; + } + } + + private void PeekWindow_Activated(object sender, WindowActivatedEventArgs args) + { + if (args.WindowActivationState == WindowActivationState.Deactivated) + { + var userSettings = Application.Current.GetService(); + if (userSettings.CloseAfterLosingFocus) + { + Uninitialize(); + } + } + } + private void PreviousNavigationInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) { ViewModel.AttemptPreviousNavigation(); @@ -125,7 +116,7 @@ namespace Peek.UI ViewModel.AttemptNextNavigation(); } - private void EscKeyInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + private void CloseInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) { Uninitialize(); } diff --git a/src/modules/peek/Peek.UI/Services/UserSettings.cs b/src/modules/peek/Peek.UI/Services/UserSettings.cs index d15ed3570e..6e5607c6e9 100644 --- a/src/modules/peek/Peek.UI/Services/UserSettings.cs +++ b/src/modules/peek/Peek.UI/Services/UserSettings.cs @@ -48,7 +48,7 @@ namespace Peek.UI if (!_settingsUtils.SettingsExists(PeekModuleName)) { - Logger.LogInfo("Hosts settings.json was missing, creating a new one"); + Logger.LogInfo("Peek settings.json was missing, creating a new one"); var defaultSettings = new PeekSettings(); defaultSettings.Save(_settingsUtils); } diff --git a/src/modules/poweraccent/PowerAccent.Core/Languages.cs b/src/modules/poweraccent/PowerAccent.Core/Languages.cs index d025213a1c..87cb2c25b2 100644 --- a/src/modules/poweraccent/PowerAccent.Core/Languages.cs +++ b/src/modules/poweraccent/PowerAccent.Core/Languages.cs @@ -14,10 +14,13 @@ namespace PowerAccent.Core CUR, CY, CZ, + DK, GA, GD, DE, + EL, EST, + FI, FR, HR, HE, @@ -52,10 +55,13 @@ namespace PowerAccent.Core Language.CUR => GetDefaultLetterKeyCUR(letter), // Currency Language.CY => GetDefaultLetterKeyCY(letter), // Welsh Language.CZ => GetDefaultLetterKeyCZ(letter), // Czech + Language.DK => GetDefaultLetterKeyDK(letter), // Danish Language.GA => GetDefaultLetterKeyGA(letter), // Gaeilge (Irish) Language.GD => GetDefaultLetterKeyGD(letter), // Gàidhlig (Scottish Gaelic) Language.DE => GetDefaultLetterKeyDE(letter), // German + Language.EL => GetDefaultLetterKeyEL(letter), // Greek Language.EST => GetDefaultLetterKeyEST(letter), // Estonian + Language.FI => GetDefaultLetterKeyFI(letter), // Finnish Language.FR => GetDefaultLetterKeyFR(letter), // French Language.HR => GetDefaultLetterKeyHR(letter), // Croatian Language.HE => GetDefaultLetterKeyHE(letter), // Hebrew @@ -93,10 +99,13 @@ namespace PowerAccent.Core .Union(GetDefaultLetterKeyCUR(letter)) .Union(GetDefaultLetterKeyCY(letter)) .Union(GetDefaultLetterKeyCZ(letter)) + .Union(GetDefaultLetterKeyDK(letter)) .Union(GetDefaultLetterKeyGA(letter)) .Union(GetDefaultLetterKeyGD(letter)) .Union(GetDefaultLetterKeyDE(letter)) + .Union(GetDefaultLetterKeyEL(letter)) .Union(GetDefaultLetterKeyEST(letter)) + .Union(GetDefaultLetterKeyFI(letter)) .Union(GetDefaultLetterKeyFR(letter)) .Union(GetDefaultLetterKeyHR(letter)) .Union(GetDefaultLetterKeyHE(letter)) @@ -131,32 +140,39 @@ namespace PowerAccent.Core { return letter switch { - LetterKey.VK_A => new[] { "α", "ά", "ȧ" }, - LetterKey.VK_B => new[] { "ḃ", "β" }, - LetterKey.VK_C => new[] { "ċ", "χ", "°C", "©", "ℂ" }, - LetterKey.VK_D => new[] { "ḍ", "ḋ", "δ" }, - LetterKey.VK_E => new[] { "ε", "έ", "η", "ή", "∈" }, + LetterKey.VK_0 => new[] { "↉" }, + LetterKey.VK_1 => new[] { "½", "⅓", "¼", "⅕", "⅙", "⅐", "⅛", "⅑", "⅒" }, + LetterKey.VK_2 => new[] { "⅔", "⅖" }, + LetterKey.VK_3 => new[] { "¾", "⅗", "⅜" }, + LetterKey.VK_4 => new[] { "⅘" }, + LetterKey.VK_5 => new[] { "⅚", "⅝" }, + LetterKey.VK_7 => new[] { "⅞" }, + LetterKey.VK_A => new[] { "ά", "ȧ" }, + LetterKey.VK_B => new[] { "ḃ" }, + LetterKey.VK_C => new[] { "ċ", "°C", "©", "ℂ" }, + LetterKey.VK_D => new[] { "ḍ", "ḋ" }, + LetterKey.VK_E => new[] { "έ", "ή", "∈" }, LetterKey.VK_F => new[] { "ḟ", "°F" }, - LetterKey.VK_G => new[] { "ģ", "ǧ", "ġ", "ĝ", "ǥ", "γ" }, + LetterKey.VK_G => new[] { "ģ", "ǧ", "ġ", "ĝ", "ǥ" }, LetterKey.VK_H => new[] { "ḣ", "ĥ", "ħ" }, - LetterKey.VK_I => new[] { "ι", "ί" }, + LetterKey.VK_I => new[] { "ί" }, LetterKey.VK_J => new[] { "ĵ" }, - LetterKey.VK_K => new[] { "ķ", "ǩ", "κ" }, - LetterKey.VK_L => new[] { "ļ", "₺", "λ" }, // ₺ is in VK_T for other languages, but not VK_L, so we add it here. - LetterKey.VK_M => new[] { "ṁ", "μ" }, - LetterKey.VK_N => new[] { "ņ", "ṅ", "ⁿ", "ν", "ℕ" }, - LetterKey.VK_O => new[] { "ȯ", "ω", "ώ", "ο", "ό" }, - LetterKey.VK_P => new[] { "ṗ", "φ", "ψ", "℗" }, + LetterKey.VK_K => new[] { "ķ", "ǩ" }, + LetterKey.VK_L => new[] { "ļ", "₺" }, // ₺ is in VK_T for other languages, but not VK_L, so we add it here. + LetterKey.VK_M => new[] { "ṁ" }, + LetterKey.VK_N => new[] { "ņ", "ṅ", "ⁿ", "ℕ" }, + LetterKey.VK_O => new[] { "ȯ", "ώ", "ό" }, + LetterKey.VK_P => new[] { "ṗ", "℗" }, LetterKey.VK_Q => new[] { "ℚ" }, - LetterKey.VK_R => new[] { "ṙ", "ρ", "®", "ℝ" }, - LetterKey.VK_S => new[] { "ṡ", "σ", "\u00A7" }, - LetterKey.VK_T => new[] { "ţ", "ṫ", "ŧ", "θ", "τ", "™" }, - LetterKey.VK_U => new[] { "ŭ", "υ", "ύ" }, + LetterKey.VK_R => new[] { "ṙ", "®", "ℝ" }, + LetterKey.VK_S => new[] { "ṡ", "\u00A7" }, + LetterKey.VK_T => new[] { "ţ", "ṫ", "ŧ", "™" }, + LetterKey.VK_U => new[] { "ŭ", "ύ" }, LetterKey.VK_V => new[] { "V̇" }, LetterKey.VK_W => new[] { "ẇ" }, - LetterKey.VK_X => new[] { "ẋ", "ξ", "×" }, - LetterKey.VK_Y => new[] { "ẏ" }, - LetterKey.VK_Z => new[] { "ʒ", "ǯ", "ζ", "ℤ" }, + LetterKey.VK_X => new[] { "ẋ", "×" }, + LetterKey.VK_Y => new[] { "ẏ", "ꝡ" }, + LetterKey.VK_Z => new[] { "ʒ", "ǯ", "ℤ" }, LetterKey.VK_COMMA => new[] { "∙", "₋", "⁻", "–" }, // – is in VK_MINUS for other languages, but not VK_COMMA, so we add it here. LetterKey.VK_PERIOD => new[] { "\u0300", "\u0301", "\u0302", "\u0303", "\u0304", "\u0308", "\u030C" }, LetterKey.VK_MINUS => new[] { "~", "‐", "‑", "‒", "—", "―", "⁓", "−", "⸺", "⸻" }, @@ -185,7 +201,7 @@ namespace PowerAccent.Core LetterKey.VK_P => new[] { "£", "₽" }, LetterKey.VK_R => new[] { "₹", "៛", "﷼" }, LetterKey.VK_S => new[] { "$", "₪" }, - LetterKey.VK_T => new[] { "₮", "₺" }, + LetterKey.VK_T => new[] { "₮", "₺", "₸" }, LetterKey.VK_W => new[] { "₩" }, LetterKey.VK_Y => new[] { "¥" }, LetterKey.VK_Z => new[] { "z" }, @@ -200,6 +216,7 @@ namespace PowerAccent.Core { LetterKey.VK_C => new[] { "ć", "č" }, LetterKey.VK_D => new[] { "đ" }, + LetterKey.VK_E => new[] { "€" }, LetterKey.VK_S => new[] { "š" }, LetterKey.VK_Z => new[] { "ž" }, _ => Array.Empty(), @@ -221,6 +238,18 @@ namespace PowerAccent.Core }; } + // Finnish + private static string[] GetDefaultLetterKeyFI(LetterKey letter) + { + return letter switch + { + LetterKey.VK_A => new[] { "ä", "å" }, + LetterKey.VK_E => new[] { "€" }, + LetterKey.VK_O => new[] { "ö" }, + _ => Array.Empty(), + }; + } + // French private static string[] GetDefaultLetterKeyFR(LetterKey letter) { @@ -438,7 +467,7 @@ namespace PowerAccent.Core return letter switch { LetterKey.VK_A => new[] { "á" }, - LetterKey.VK_E => new[] { "é" }, + LetterKey.VK_E => new[] { "é", "€" }, LetterKey.VK_I => new[] { "í" }, LetterKey.VK_O => new[] { "ó" }, LetterKey.VK_U => new[] { "ú" }, @@ -455,6 +484,7 @@ namespace PowerAccent.Core LetterKey.VK_E => new[] { "è" }, LetterKey.VK_I => new[] { "ì" }, LetterKey.VK_O => new[] { "ò" }, + LetterKey.VK_P => new[] { "£" }, LetterKey.VK_U => new[] { "ù" }, _ => Array.Empty(), }; @@ -496,6 +526,36 @@ namespace PowerAccent.Core }; } + // Greek + private static string[] GetDefaultLetterKeyEL(LetterKey letter) + { + return letter switch + { + LetterKey.VK_A => new string[] { "α" }, + LetterKey.VK_B => new string[] { "β" }, + LetterKey.VK_C => new string[] { "χ" }, + LetterKey.VK_D => new string[] { "δ" }, + LetterKey.VK_E => new string[] { "ε", "η" }, + LetterKey.VK_F => new string[] { "φ" }, + LetterKey.VK_G => new string[] { "γ" }, + LetterKey.VK_I => new string[] { "ι" }, + LetterKey.VK_K => new string[] { "κ" }, + LetterKey.VK_L => new string[] { "λ" }, + LetterKey.VK_M => new string[] { "μ" }, + LetterKey.VK_N => new string[] { "ν" }, + LetterKey.VK_O => new string[] { "ο", "ω" }, + LetterKey.VK_P => new string[] { "π", "φ", "ψ" }, + LetterKey.VK_R => new string[] { "ρ" }, + LetterKey.VK_S => new string[] { "σ" }, + LetterKey.VK_T => new string[] { "τ", "θ" }, + LetterKey.VK_U => new string[] { "υ" }, + LetterKey.VK_X => new string[] { "ξ" }, + LetterKey.VK_Y => new string[] { "υ" }, + LetterKey.VK_Z => new string[] { "ζ" }, + _ => Array.Empty(), + }; + } + // Hebrew private static string[] GetDefaultLetterKeyHE(LetterKey letter) { @@ -588,6 +648,7 @@ namespace PowerAccent.Core LetterKey.VK_E => new[] { "ê" }, LetterKey.VK_I => new[] { "î" }, LetterKey.VK_O => new[] { "ô" }, + LetterKey.VK_P => new[] { "£" }, LetterKey.VK_U => new[] { "û" }, LetterKey.VK_Y => new[] { "ŷ" }, LetterKey.VK_W => new[] { "ŵ" }, @@ -644,6 +705,18 @@ namespace PowerAccent.Core }; } + // Danish + private static string[] GetDefaultLetterKeyDK(LetterKey letter) + { + return letter switch + { + LetterKey.VK_A => new[] { "å", "æ" }, + LetterKey.VK_E => new[] { "€" }, + LetterKey.VK_O => new[] { "ø" }, + _ => Array.Empty(), + }; + } + // Lithuanian private static string[] GetDefaultLetterKeyLT(LetterKey letter) { diff --git a/src/modules/poweraccent/PowerAccent.UI/App.xaml b/src/modules/poweraccent/PowerAccent.UI/App.xaml index abc58f7b3e..aabf3459af 100644 --- a/src/modules/poweraccent/PowerAccent.UI/App.xaml +++ b/src/modules/poweraccent/PowerAccent.UI/App.xaml @@ -2,15 +2,14 @@ x:Class="PowerAccent.UI.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:local="clr-namespace:PowerAccent" - xmlns:ui="http://schemas.modernwpf.com/2019" + xmlns:ui="http://schemas.lepo.co/wpfui/2022/xaml" StartupUri="Selector.xaml"> - - + + diff --git a/src/modules/poweraccent/PowerAccent.UI/App.xaml.cs b/src/modules/poweraccent/PowerAccent.UI/App.xaml.cs index af3ff38db9..1ae590ce88 100644 --- a/src/modules/poweraccent/PowerAccent.UI/App.xaml.cs +++ b/src/modules/poweraccent/PowerAccent.UI/App.xaml.cs @@ -5,9 +5,7 @@ using System; using System.Threading; using System.Windows; -using Common.UI; using ManagedCommon; -using PowerAccent.Core.Tools; namespace PowerAccent.UI { @@ -18,7 +16,6 @@ namespace PowerAccent.UI { private static Mutex _mutex; private bool _disposed; - private ThemeManager _themeManager; protected override void OnStartup(StartupEventArgs e) { @@ -30,7 +27,6 @@ namespace PowerAccent.UI Application.Current.Shutdown(); } - _themeManager = new ThemeManager(this); base.OnStartup(e); } @@ -50,7 +46,6 @@ namespace PowerAccent.UI if (disposing) { _mutex?.Dispose(); - _themeManager?.Dispose(); } _disposed = true; diff --git a/src/modules/poweraccent/PowerAccent.UI/PowerAccent.UI.csproj b/src/modules/poweraccent/PowerAccent.UI/PowerAccent.UI.csproj index b459272213..be88b83789 100644 --- a/src/modules/poweraccent/PowerAccent.UI/PowerAccent.UI.csproj +++ b/src/modules/poweraccent/PowerAccent.UI/PowerAccent.UI.csproj @@ -35,7 +35,7 @@ - + diff --git a/src/modules/poweraccent/PowerAccent.UI/Program.cs b/src/modules/poweraccent/PowerAccent.UI/Program.cs index 9ca3de95be..d45b7c601c 100644 --- a/src/modules/poweraccent/PowerAccent.UI/Program.cs +++ b/src/modules/poweraccent/PowerAccent.UI/Program.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using System.Windows; using interop; using ManagedCommon; -using PowerAccent.Core.Tools; namespace PowerAccent.UI; diff --git a/src/modules/poweraccent/PowerAccent.UI/Selector.xaml b/src/modules/poweraccent/PowerAccent.UI/Selector.xaml index 1f30d628e2..1396528383 100644 --- a/src/modules/poweraccent/PowerAccent.UI/Selector.xaml +++ b/src/modules/poweraccent/PowerAccent.UI/Selector.xaml @@ -1,30 +1,30 @@ - - + @@ -33,101 +33,89 @@ - + - + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - - + + + + - + \ No newline at end of file diff --git a/src/modules/poweraccent/PowerAccent.UI/Selector.xaml.cs b/src/modules/poweraccent/PowerAccent.UI/Selector.xaml.cs index f83e20c037..14df70a72f 100644 --- a/src/modules/poweraccent/PowerAccent.UI/Selector.xaml.cs +++ b/src/modules/poweraccent/PowerAccent.UI/Selector.xaml.cs @@ -5,13 +5,13 @@ using System; using System.ComponentModel; using System.Windows; -using PowerAccent.Core.Services; +using Wpf.Ui.Controls; using Point = PowerAccent.Core.Point; using Size = PowerAccent.Core.Size; namespace PowerAccent.UI; -public partial class Selector : Window, IDisposable, INotifyPropertyChanged +public partial class Selector : FluentWindow, IDisposable, INotifyPropertyChanged { private readonly Core.PowerAccent _powerAccent = new(); @@ -38,6 +38,7 @@ public partial class Selector : Window, IDisposable, INotifyPropertyChanged public Selector() { InitializeComponent(); + Wpf.Ui.Appearance.SystemThemeWatcher.Watch(this); Application.Current.MainWindow.ShowActivated = false; Application.Current.MainWindow.Topmost = true; } @@ -67,7 +68,6 @@ public partial class Selector : Window, IDisposable, INotifyPropertyChanged characters.SelectedIndex = _selectedIndex; this.UpdateLayout(); // Required for filling the actual width/height before positioning. SetWindowPosition(); - SetWindowAlignment(); Show(); Microsoft.PowerToys.Telemetry.PowerToysTelemetry.Log.WriteEvent(new PowerAccent.Core.Telemetry.PowerAccentShowAccentMenuEvent()); } @@ -90,17 +90,6 @@ public partial class Selector : Window, IDisposable, INotifyPropertyChanged this.Top = position.Y; } - private void SetWindowAlignment() - { - gridBorder.HorizontalAlignment = _powerAccent.GetToolbarPosition() switch - { - Position.Left or Position.TopLeft or Position.BottomLeft => HorizontalAlignment.Left, - Position.Right or Position.TopRight or Position.BottomRight => HorizontalAlignment.Right, - Position.Center or Position.Top or Position.Bottom => HorizontalAlignment.Center, - _ => HorizontalAlignment.Center, - }; - } - protected override void OnClosed(EventArgs e) { _powerAccent.Dispose(); diff --git a/src/modules/poweraccent/PowerAccent.UI/Themes/Dark.xaml b/src/modules/poweraccent/PowerAccent.UI/Themes/Dark.xaml deleted file mode 100644 index 07ed9f95ce..0000000000 --- a/src/modules/poweraccent/PowerAccent.UI/Themes/Dark.xaml +++ /dev/null @@ -1,32 +0,0 @@ - - - - Dark.Accent1 - PowerToysRun - Accent1 (Dark) - Dark - Accent1 - Black - - - - - - - - \ No newline at end of file diff --git a/src/modules/poweraccent/PowerAccent.UI/Themes/HighContrast1.xaml b/src/modules/poweraccent/PowerAccent.UI/Themes/HighContrast1.xaml deleted file mode 100644 index 42d269d7fe..0000000000 --- a/src/modules/poweraccent/PowerAccent.UI/Themes/HighContrast1.xaml +++ /dev/null @@ -1,26 +0,0 @@ - - - - HighContrast.Accent2 - PowerToysRun - Accent2 (HighContrast) - HighContrast - Accent2 - White - - - - - - - - \ No newline at end of file diff --git a/src/modules/poweraccent/PowerAccent.UI/Themes/HighContrast2.xaml b/src/modules/poweraccent/PowerAccent.UI/Themes/HighContrast2.xaml deleted file mode 100644 index 825f0267bf..0000000000 --- a/src/modules/poweraccent/PowerAccent.UI/Themes/HighContrast2.xaml +++ /dev/null @@ -1,26 +0,0 @@ - - - - HighContrast.Accent3 - PowerToysRun - Accent3 (HighContrast) - HighContrast - Accent3 - White - - - - - - - - \ No newline at end of file diff --git a/src/modules/poweraccent/PowerAccent.UI/Themes/HighContrastBlack.xaml b/src/modules/poweraccent/PowerAccent.UI/Themes/HighContrastBlack.xaml deleted file mode 100644 index eee95d5823..0000000000 --- a/src/modules/poweraccent/PowerAccent.UI/Themes/HighContrastBlack.xaml +++ /dev/null @@ -1,26 +0,0 @@ - - - - HighContrast.Accent4 - PowerToysRun - Accent4 (HighContrast) - HighContrast - Accent4 - White - - - - - - - - \ No newline at end of file diff --git a/src/modules/poweraccent/PowerAccent.UI/Themes/HighContrastWhite.xaml b/src/modules/poweraccent/PowerAccent.UI/Themes/HighContrastWhite.xaml deleted file mode 100644 index 5a9f6df8ab..0000000000 --- a/src/modules/poweraccent/PowerAccent.UI/Themes/HighContrastWhite.xaml +++ /dev/null @@ -1,26 +0,0 @@ - - - - HighContrast.Accent5 - PowerToysRun - Accent5 (HighContrast) - HighContrast - Accent5 - White - - - - - - - - \ No newline at end of file diff --git a/src/modules/poweraccent/PowerAccent.UI/Themes/Light.xaml b/src/modules/poweraccent/PowerAccent.UI/Themes/Light.xaml deleted file mode 100644 index 22f8213eee..0000000000 --- a/src/modules/poweraccent/PowerAccent.UI/Themes/Light.xaml +++ /dev/null @@ -1,35 +0,0 @@ - - - - Light.Accent1 - PowerToysRun - Accent1 (Light) - Light - Accent1 - White - - - - - - - - \ No newline at end of file diff --git a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml index 17b7ac9fab..c3c30cbeb9 100644 --- a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml +++ b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml @@ -217,7 +217,7 @@ FontFamily="{ThemeResource SymbolThemeFontFamily}" Visibility="{Binding ElementName=checkBox_regex, Path=IsChecked}"> - + diff --git a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml.cpp b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml.cpp index 15d542bb52..1ce714a13f 100644 --- a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml.cpp +++ b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml.cpp @@ -134,7 +134,7 @@ namespace winrt::PowerRenameUI::implementation GetDpiForMonitor(hMonitor, MONITOR_DPI_TYPE::MDT_EFFECTIVE_DPI, &x_dpi, &x_dpi); UINT window_dpi = GetDpiForWindow(m_window); - const auto& [width, height] = CSettingsInstance().GetLastWindowSize(); + const auto& [width, height] = LastRunSettingsInstance().GetLastWindowSize(); winrt::Windows::Graphics::RectInt32 rect; // Scale window size @@ -297,12 +297,15 @@ namespace winrt::PowerRenameUI::implementation Microsoft::UI::Windowing::AppWindow::GetFromWindowId(Microsoft::UI::GetWindowIdFromWindow(m_window)); const auto [width, height] = appWindow.Size(); - CSettingsInstance().UpdateLastWindowSize(static_cast(width), static_cast(height)); + m_updatedWindowSize.emplace(std::make_pair(width, height)); } void MainWindow::OnClosed(winrt::Windows::Foundation::IInspectable const&, winrt::Microsoft::UI::Xaml::WindowEventArgs const&) { - CSettingsInstance().Save(); + if (m_updatedWindowSize) + { + LastRunSettingsInstance().UpdateLastWindowSize(m_updatedWindowSize->first, m_updatedWindowSize->second); + } } void MainWindow::InvalidateItemListViewState() @@ -820,8 +823,8 @@ namespace winrt::PowerRenameUI::implementation { flags = CSettingsInstance().GetFlags(); - textBox_search().Text(CSettingsInstance().GetSearchText().c_str()); - textBox_replace().Text(CSettingsInstance().GetReplaceText().c_str()); + textBox_search().Text(LastRunSettingsInstance().GetSearchText().c_str()); + textBox_replace().Text(LastRunSettingsInstance().GetReplaceText().c_str()); } else { @@ -846,7 +849,7 @@ namespace winrt::PowerRenameUI::implementation CSettingsInstance().SetFlags(flags); winrt::hstring searchTerm = textBox_search().Text(); - CSettingsInstance().SetSearchText(std::wstring{ searchTerm }); + LastRunSettingsInstance().SetSearchText(std::wstring{ searchTerm }); if (CSettingsInstance().GetMRUEnabled() && m_searchMRU) { @@ -858,7 +861,7 @@ namespace winrt::PowerRenameUI::implementation } winrt::hstring replaceTerm = textBox_replace().Text(); - CSettingsInstance().SetReplaceText(std::wstring{ replaceTerm }); + LastRunSettingsInstance().SetReplaceText(std::wstring{ replaceTerm }); if (CSettingsInstance().GetMRUEnabled() && m_replaceMRU) { diff --git a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml.h b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml.h index ed9d6a6c2b..dc35a6b022 100644 --- a/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml.h +++ b/src/modules/powerrename/PowerRenameUILib/PowerRenameXAML/MainWindow.xaml.h @@ -153,6 +153,8 @@ namespace winrt::PowerRenameUI::implementation UINT m_selectedCount = 0; UINT m_renamingCount = 0; winrt::event m_propertyChanged; + std::optional> m_updatedWindowSize; + bool m_flagValidationInProgress = false; diff --git a/src/modules/powerrename/lib/Settings.cpp b/src/modules/powerrename/lib/Settings.cpp index 1ebee29e5e..6f90de9d4c 100644 --- a/src/modules/powerrename/lib/Settings.cpp +++ b/src/modules/powerrename/lib/Settings.cpp @@ -13,6 +13,7 @@ namespace { const wchar_t c_powerRenameDataFilePath[] = L"\\power-rename-settings.json"; + const wchar_t c_powerRenameLastRunFilePath[] = L"\\power-rename-last-run-data.json"; const wchar_t c_powerRenameUIFlagsFilePath[] = L"\\power-rename-ui-flags"; const wchar_t c_enabled[] = L"Enabled"; @@ -48,11 +49,7 @@ void CSettings::Save() jsonData.SetNamedValue(c_persistState, json::value(settings.persistState)); jsonData.SetNamedValue(c_mruEnabled, json::value(settings.MRUEnabled)); jsonData.SetNamedValue(c_maxMRUSize, json::value(settings.maxMRUSize)); - jsonData.SetNamedValue(c_searchText, json::value(settings.searchText)); - jsonData.SetNamedValue(c_replaceText, json::value(settings.replaceText)); jsonData.SetNamedValue(c_useBoostLib, json::value(settings.useBoostLib)); - jsonData.SetNamedValue(c_lastWindowWidth, json::value(settings.lastWindowWidth)); - jsonData.SetNamedValue(c_lastWindowHeight, json::value(settings.lastWindowHeight)); json::to_file(jsonFilePath, jsonData); GetSystemTimeAsFileTime(&lastLoadedTime); @@ -94,8 +91,10 @@ void CSettings::MigrateFromRegistry() settings.MRUEnabled = GetRegBoolean(c_mruEnabled, true); settings.maxMRUSize = GetRegNumber(c_maxMRUSize, 10); settings.flags = GetRegNumber(c_flags, 0); - settings.searchText = GetRegString(c_searchText, L""); - settings.replaceText = GetRegString(c_replaceText, L""); + + LastRunSettingsInstance().SetSearchText(GetRegString(c_searchText, L"")); + LastRunSettingsInstance().SetReplaceText(GetRegString(c_replaceText, L"")); + settings.useBoostLib = false; // Never existed in registry, disabled by default. } @@ -131,21 +130,10 @@ void CSettings::ParseJson() { settings.maxMRUSize = static_cast(jsonSettings.GetNamedNumber(c_maxMRUSize)); } - if (json::has(jsonSettings, c_searchText, json::JsonValueType::String)) - { - settings.searchText = jsonSettings.GetNamedString(c_searchText); - } - if (json::has(jsonSettings, c_replaceText, json::JsonValueType::String)) - { - settings.replaceText = jsonSettings.GetNamedString(c_replaceText); - } if (json::has(jsonSettings, c_useBoostLib, json::JsonValueType::Boolean)) { settings.useBoostLib = jsonSettings.GetNamedBoolean(c_useBoostLib); } - - settings.lastWindowWidth = static_cast(jsonSettings.GetNamedNumber(c_lastWindowWidth, DEFAULT_WINDOW_WIDTH)); - settings.lastWindowHeight = static_cast(jsonSettings.GetNamedNumber(c_lastWindowHeight, DEFAULT_WINDOW_HEIGHT)); } catch (const winrt::hresult_error&) { @@ -177,3 +165,35 @@ CSettings& CSettingsInstance() static CSettings instance; return instance; } + +LastRunSettings& LastRunSettingsInstance() +{ + static LastRunSettings instance; + return instance; +} + +void LastRunSettings::Load() +{ + const auto lastRunSettingsFilePath = PTSettingsHelper::get_module_save_folder_location(PowerRenameConstants::ModuleKey) + c_powerRenameLastRunFilePath; + auto json = json::from_file(lastRunSettingsFilePath); + if (!json) + return; + + json::get(*json, c_searchText, searchText, L""); + json::get(*json, c_replaceText, replaceText, L""); + json::get(*json, c_lastWindowWidth, lastWindowWidth, DEFAULT_WINDOW_WIDTH); + json::get(*json, c_lastWindowHeight, lastWindowHeight, DEFAULT_WINDOW_HEIGHT); +} + +void LastRunSettings::Save() +{ + json::JsonObject json; + + json.SetNamedValue(c_searchText, json::value(searchText)); + json.SetNamedValue(c_replaceText, json::value(replaceText)); + json.SetNamedValue(c_lastWindowWidth, json::value(lastWindowWidth)); + json.SetNamedValue(c_lastWindowHeight, json::value(lastWindowHeight)); + + const auto lastRunSettingsFilePath = PTSettingsHelper::get_module_save_folder_location(PowerRenameConstants::ModuleKey) + c_powerRenameLastRunFilePath; + json::to_file(lastRunSettingsFilePath, json); +} diff --git a/src/modules/powerrename/lib/Settings.h b/src/modules/powerrename/lib/Settings.h index 00e5f2df1e..633b1fca24 100644 --- a/src/modules/powerrename/lib/Settings.h +++ b/src/modules/powerrename/lib/Settings.h @@ -6,9 +6,6 @@ class CSettings { public: - static constexpr inline int DEFAULT_WINDOW_WIDTH = 1400; - static constexpr inline int DEFAULT_WINDOW_HEIGHT = 800; - CSettings(); inline bool GetEnabled() @@ -28,17 +25,6 @@ public: Save(); } - inline std::tuple GetLastWindowSize() const - { - return std::make_tuple(settings.lastWindowWidth, settings.lastWindowHeight); - } - - inline void UpdateLastWindowSize(const int width, const int height) - { - settings.lastWindowWidth = std::max(width, DEFAULT_WINDOW_WIDTH); - settings.lastWindowHeight = std::max(height, DEFAULT_WINDOW_HEIGHT); - } - inline bool GetShowIconOnMenu() const { return settings.showIconOnMenu; @@ -110,28 +96,6 @@ public: WriteFlags(); } - inline const std::wstring& GetSearchText() const - { - return settings.searchText; - } - - inline void SetSearchText(const std::wstring& text) - { - settings.searchText = text; - Save(); - } - - inline const std::wstring& GetReplaceText() const - { - return settings.replaceText; - } - - inline void SetReplaceText(const std::wstring& text) - { - settings.replaceText = text; - Save(); - } - void Save(); void Load(); @@ -146,10 +110,6 @@ private: bool MRUEnabled{ true }; unsigned int maxMRUSize{ 10 }; unsigned int flags{ 0 }; - std::wstring searchText{}; - std::wstring replaceText{}; - int lastWindowWidth{ DEFAULT_WINDOW_WIDTH }; - int lastWindowHeight{ DEFAULT_WINDOW_HEIGHT }; }; void Reload(); @@ -166,3 +126,60 @@ private: }; CSettings& CSettingsInstance(); + +class LastRunSettings +{ + static constexpr inline int DEFAULT_WINDOW_WIDTH = 1400; + static constexpr inline int DEFAULT_WINDOW_HEIGHT = 800; + + int lastWindowWidth{ DEFAULT_WINDOW_WIDTH }; + int lastWindowHeight{ DEFAULT_WINDOW_HEIGHT }; + + std::wstring searchText{}; + std::wstring replaceText{}; + +public: + inline LastRunSettings() + { + Load(); + } + + inline std::tuple GetLastWindowSize() const + { + return std::make_tuple(lastWindowWidth, lastWindowHeight); + } + + inline void UpdateLastWindowSize(const int width, const int height) + { + lastWindowWidth = std::max(width, DEFAULT_WINDOW_WIDTH); + lastWindowHeight = std::max(height, DEFAULT_WINDOW_HEIGHT); + Save(); + } + + inline const std::wstring& GetSearchText() const + { + return searchText; + } + + inline void SetSearchText(const std::wstring& text) + { + searchText = text; + Save(); + } + + inline const std::wstring& GetReplaceText() const + { + return replaceText; + } + + inline void SetReplaceText(const std::wstring& text) + { + replaceText = text; + Save(); + } + + void Load(); + void Save(); +}; + +LastRunSettings& LastRunSettingsInstance(); diff --git a/src/modules/previewpane/GcodePreviewHandler/GcodePreviewHandlerControl.cs b/src/modules/previewpane/GcodePreviewHandler/GcodePreviewHandlerControl.cs index 7ddec18b64..63d9828d38 100644 --- a/src/modules/previewpane/GcodePreviewHandler/GcodePreviewHandlerControl.cs +++ b/src/modules/previewpane/GcodePreviewHandler/GcodePreviewHandlerControl.cs @@ -29,6 +29,14 @@ namespace Microsoft.PowerToys.PreviewHandler.Gcode /// private bool _infoBarAdded; + /// + /// Initializes a new instance of the class. + /// + public GcodePreviewHandlerControl() + { + SetBackgroundColor(Settings.BackgroundColor); + } + /// /// Start the preview on the Control. /// @@ -124,7 +132,7 @@ namespace Microsoft.PowerToys.PreviewHandler.Gcode { _pictureBox = new PictureBox(); _pictureBox.BackgroundImage = image; - _pictureBox.BackgroundImageLayout = ImageLayout.Center; + _pictureBox.BackgroundImageLayout = Width >= image.Width && Height >= image.Height ? ImageLayout.Center : ImageLayout.Zoom; _pictureBox.Dock = DockStyle.Fill; Controls.Add(_pictureBox); } diff --git a/src/modules/previewpane/GcodePreviewHandler/Settings.cs b/src/modules/previewpane/GcodePreviewHandler/Settings.cs new file mode 100644 index 0000000000..0bfdd62ad4 --- /dev/null +++ b/src/modules/previewpane/GcodePreviewHandler/Settings.cs @@ -0,0 +1,38 @@ +// 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.PowerToys.PreviewHandler.Gcode +{ + internal sealed class Settings + { + /// + /// Gets the color of the window background. + /// Even though this is not a setting yet, it's retrieved from a "Settings" class to be aligned with other preview handlers that contain this setting. + /// It's possible it can be converted into a setting in the future. + /// + public static Color BackgroundColor + { + get + { + if (GetTheme() == "dark") + { + return Color.FromArgb(30, 30, 30); // #1e1e1e + } + else + { + return Color.White; + } + } + } + + /// + /// Returns the theme. + /// + /// Theme that should be used. + public static string GetTheme() + { + return Common.UI.ThemeManager.GetWindowsBaseColor().ToLowerInvariant(); + } + } +} diff --git a/src/modules/previewpane/GcodePreviewHandlerCpp/GcodePreviewHandler.cpp b/src/modules/previewpane/GcodePreviewHandlerCpp/GcodePreviewHandler.cpp index 8903d9908c..3fc60d750b 100644 --- a/src/modules/previewpane/GcodePreviewHandlerCpp/GcodePreviewHandler.cpp +++ b/src/modules/previewpane/GcodePreviewHandlerCpp/GcodePreviewHandler.cpp @@ -1,5 +1,6 @@ #include "pch.h" #include "GcodePreviewHandler.h" +#include "../powerpreview/powerpreviewConstants.h" #include #include @@ -9,6 +10,7 @@ #include #include #include +#include extern HINSTANCE g_hInst; extern long g_cDllRef; @@ -202,6 +204,8 @@ IFACEMETHODIMP GcodePreviewHandler::Unload() IFACEMETHODIMP GcodePreviewHandler::SetBackgroundColor(COLORREF color) { + HBRUSH brush = CreateSolidBrush(WindowsColors::is_dark_mode() ? powerpreviewConstants::DARK_THEME_COLOR : powerpreviewConstants::LIGHT_THEME_COLOR); + SetClassLongPtr(m_hwndParent, GCLP_HBRBACKGROUND, reinterpret_cast(brush)); return S_OK; } diff --git a/src/modules/previewpane/GcodePreviewHandlerCpp/GcodePreviewHandlerCpp.vcxproj b/src/modules/previewpane/GcodePreviewHandlerCpp/GcodePreviewHandlerCpp.vcxproj index 7ff68885e4..094c55e6cb 100644 --- a/src/modules/previewpane/GcodePreviewHandlerCpp/GcodePreviewHandlerCpp.vcxproj +++ b/src/modules/previewpane/GcodePreviewHandlerCpp/GcodePreviewHandlerCpp.vcxproj @@ -102,6 +102,9 @@ {6955446d-23f7-4023-9bb3-8657f904af99} + + {98537082-0fdb-40de-abd8-0dc5a4269bab} + diff --git a/src/modules/previewpane/GcodeThumbnailProvider/GcodeThumbnailProvider.cs b/src/modules/previewpane/GcodeThumbnailProvider/GcodeThumbnailProvider.cs index deb619f915..4afe553f3d 100644 --- a/src/modules/previewpane/GcodeThumbnailProvider/GcodeThumbnailProvider.cs +++ b/src/modules/previewpane/GcodeThumbnailProvider/GcodeThumbnailProvider.cs @@ -2,6 +2,7 @@ // 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.Drawing.Drawing2D; +using System.Drawing.Imaging; using Common.Utilities; namespace Microsoft.PowerToys.ThumbnailHandler.Gcode @@ -47,9 +48,20 @@ namespace Microsoft.PowerToys.ThumbnailHandler.Gcode var gcodeThumbnail = GcodeHelper.GetBestThumbnail(reader); - var thumbnail = gcodeThumbnail?.GetBitmap(); + Bitmap thumbnail = null; - if (thumbnail != null && thumbnail.Width != cx && thumbnail.Height != cx) + try + { + thumbnail = gcodeThumbnail?.GetBitmap(); + } + catch (Exception) + { + // TODO: add logger + } + + if (thumbnail != null && ( + ((thumbnail.Width != cx || thumbnail.Height > cx) && (thumbnail.Height != cx || thumbnail.Width > cx)) || + thumbnail.PixelFormat != PixelFormat.Format32bppArgb)) { // We are not the appropriate size for caller. Resize now while // respecting the aspect ratio. @@ -80,7 +92,7 @@ namespace Microsoft.PowerToys.ThumbnailHandler.Gcode return null; } - Bitmap destImage = new Bitmap(width, height); + Bitmap destImage = new Bitmap(width, height, PixelFormat.Format32bppArgb); destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); @@ -95,6 +107,8 @@ namespace Microsoft.PowerToys.ThumbnailHandler.Gcode graphics.DrawImage(image, 0, 0, width, height); } + image.Dispose(); + return destImage; } diff --git a/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandlerControl.cs b/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandlerControl.cs index 7480acd1dc..192395fa31 100644 --- a/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandlerControl.cs +++ b/src/modules/previewpane/MonacoPreviewHandler/MonacoPreviewHandlerControl.cs @@ -4,11 +4,13 @@ using System.Globalization; using System.Runtime.CompilerServices; +using System.Text; using Common; using ManagedCommon; using Microsoft.PowerToys.PreviewHandler.Monaco.Properties; using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.WinForms; +using UtfUnknown; using Windows.System; namespace Microsoft.PowerToys.PreviewHandler.Monaco @@ -358,7 +360,13 @@ namespace Microsoft.PowerToys.PreviewHandler.Monaco Logger.LogInfo("Starting getting monaco language id out of filetype"); _vsCodeLangSet = FileHandler.GetLanguage(Path.GetExtension(filePath)); - using (StreamReader fileReader = new StreamReader(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) + DetectionResult result = CharsetDetector.DetectFromFile(filePath); + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + + // Check if the detected encoding is not null, otherwise default to UTF-8 + Encoding encodingToUse = result.Detected?.Encoding ?? Encoding.UTF8; + + using (StreamReader fileReader = new StreamReader(new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), encodingToUse)) { Logger.LogInfo("Starting reading requested file"); var fileContent = fileReader.ReadToEnd(); diff --git a/src/modules/previewpane/QoiPreviewHandler/Program.cs b/src/modules/previewpane/QoiPreviewHandler/Program.cs new file mode 100644 index 0000000000..4a012bef5a --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandler/Program.cs @@ -0,0 +1,63 @@ +// 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.Globalization; +using System.Windows.Threading; +using Common.UI; +using interop; + +namespace Microsoft.PowerToys.PreviewHandler.Qoi +{ + internal static class Program + { + private static CancellationTokenSource _tokenSource = new CancellationTokenSource(); + + private static QoiPreviewHandlerControl _previewHandlerControl; + + /// + /// The main entry point for the application. + /// + [STAThread] + public static void Main(string[] args) + { + ApplicationConfiguration.Initialize(); + if (args != null) + { + if (args.Length == 6) + { + string filePath = args[0]; + int hwnd = Convert.ToInt32(args[1], 16); + + Rectangle s = default(Rectangle); + int left = Convert.ToInt32(args[2], 10); + int right = Convert.ToInt32(args[3], 10); + int top = Convert.ToInt32(args[4], 10); + int bottom = Convert.ToInt32(args[5], 10); + + _previewHandlerControl = new QoiPreviewHandlerControl(); + _previewHandlerControl.SetWindow((IntPtr)hwnd, s); + _previewHandlerControl.DoPreview(filePath); + + NativeEventWaiter.WaitForEventLoop( + Constants.QoiPreviewResizeEvent(), + () => + { + Rectangle s = default(Rectangle); + _previewHandlerControl.SetRect(s); + }, + Dispatcher.CurrentDispatcher, + _tokenSource.Token); + } + else + { + MessageBox.Show("Wrong number of args: " + args.Length.ToString(CultureInfo.InvariantCulture)); + } + } + + // To customize application configuration such as set high DPI settings or default font, + // see https://aka.ms/applicationconfiguration. + Application.Run(); + } + } +} diff --git a/src/modules/previewpane/QoiPreviewHandler/Properties/Resource.Designer.cs b/src/modules/previewpane/QoiPreviewHandler/Properties/Resource.Designer.cs new file mode 100644 index 0000000000..f00aa4da78 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandler/Properties/Resource.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.PowerToys.PreviewHandler.Qoi.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // 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()] + internal class Resource { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resource() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.PowerToys.PreviewHandler.Qoi.Properties.Resource", typeof(Resource).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to This Qoi could not be previewed due to an internal error.. + /// + internal static string QoiNotPreviewedError { + get { + return ResourceManager.GetString("QoiNotPreviewedError", resourceCulture); + } + } + + /// + /// Looks up a localized string for an error when Gpo has the utility disabled. + /// + internal static string GpoDisabledErrorText { + get { + return ResourceManager.GetString("GpoDisabledErrorText", resourceCulture); + } + } + } +} diff --git a/src/modules/previewpane/QoiPreviewHandler/Properties/Resource.resx b/src/modules/previewpane/QoiPreviewHandler/Properties/Resource.resx new file mode 100644 index 0000000000..ed37e8f252 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandler/Properties/Resource.resx @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + This Qoi could not be previewed due to an internal error. + This text is displayed if Qoi fails to preview + + + Tried to start with a GPO policy setting the utility to always be disabled. Please contact your systems administrator. + GPO stands for the Windows Group Policy Object feature. + + \ No newline at end of file diff --git a/src/modules/previewpane/QoiPreviewHandler/QoiPreviewHandler.csproj b/src/modules/previewpane/QoiPreviewHandler/QoiPreviewHandler.csproj new file mode 100644 index 0000000000..8ba906c5e0 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandler/QoiPreviewHandler.csproj @@ -0,0 +1,77 @@ + + + enable + true + PowerToys.QoiPreviewHandler + PowerToys QoiPreviewHandler + PowerToys QoiPreviewHandler + ..\..\..\..\$(Platform)\$(Configuration)\QoiPreviewPaneDocumentation.xml + ..\..\..\..\$(Platform)\$(Configuration) + false + false + true + true + PowerToys.QoiPreviewHandler + true + + + + + win10-x64 + + + win10-arm64 + + + + {6B04803D-B418-4833-A67E-B0FC966636A5} + Microsoft.PowerToys.PreviewHandler.Qoi + net7.0-windows10.0.20348.0 + 10.0.19041.0 + 10.0.19041.0 + + + + + + + + True + True + Resource.resx + + + + + + PowerToys.GPOWrapper + $(OutDir) + false + + + + $(NoWarn);1591 + WinExe + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resource.Designer.cs + + + + diff --git a/src/modules/previewpane/QoiPreviewHandler/QoiPreviewHandlerControl.cs b/src/modules/previewpane/QoiPreviewHandler/QoiPreviewHandlerControl.cs new file mode 100644 index 0000000000..95d606ce82 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandler/QoiPreviewHandlerControl.cs @@ -0,0 +1,166 @@ +// 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 Common; +using Microsoft.PowerToys.PreviewHandler.Qoi.Telemetry.Events; +using Microsoft.PowerToys.Telemetry; +using PreviewHandlerCommon.Utilities; + +namespace Microsoft.PowerToys.PreviewHandler.Qoi +{ + /// + /// Implementation of Control for Qoi Preview Handler. + /// + public class QoiPreviewHandlerControl : FormHandlerControl + { + /// + /// Picture box control to display the Qoi thumbnail. + /// + private PictureBox _pictureBox; + + /// + /// Text box to display errors. + /// + private RichTextBox _textBox; + + /// + /// Represent if an text box info bar is added for showing message. + /// + private bool _infoBarAdded; + + /// + /// Initializes a new instance of the class. + /// + public QoiPreviewHandlerControl() + { + SetBackgroundColor(Settings.BackgroundColor); + } + + /// + /// Start the preview on the Control. + /// + /// Stream reference to access source file. + public override void DoPreview(T dataSource) + { + if (global::PowerToys.GPOWrapper.GPOWrapper.GetConfiguredQoiPreviewEnabledValue() == global::PowerToys.GPOWrapper.GpoRuleConfigured.Disabled) + { + // GPO is disabling this utility. Show an error message instead. + _infoBarAdded = true; + AddTextBoxControl(Properties.Resource.GpoDisabledErrorText); + Resize += FormResized; + base.DoPreview(dataSource); + + return; + } + + try + { + Bitmap thumbnail = null; + + if (!(dataSource is string filePath)) + { + throw new ArgumentException($"{nameof(dataSource)} for {nameof(QoiPreviewHandlerControl)} must be a string but was a '{typeof(T)}'"); + } + + FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read); + + thumbnail = QoiImage.FromStream(fs); + + _infoBarAdded = false; + + AddPictureBoxControl(thumbnail); + + Resize += FormResized; + base.DoPreview(fs); + try + { + PowerToysTelemetry.Log.WriteEvent(new QoiFilePreviewed()); + } + catch + { // Should not crash if sending telemetry is failing. Ignore the exception. + } + } + catch (Exception ex) + { + PreviewError(ex, dataSource); + } + } + + /// + /// Occurs when RichtextBox is resized. + /// + /// Reference to resized control. + /// Provides data for the ContentsResized event. + private void RTBContentsResized(object sender, ContentsResizedEventArgs e) + { + var richTextBox = sender as RichTextBox; + richTextBox.Height = e.NewRectangle.Height + 5; + } + + /// + /// Occurs when form is resized. + /// + /// Reference to resized control. + /// Provides data for the resize event. + private void FormResized(object sender, EventArgs e) + { + if (_infoBarAdded) + { + _textBox.Width = Width; + } + } + + /// + /// Adds a PictureBox Control to Control Collection. + /// + /// Image to display on PictureBox Control. + private void AddPictureBoxControl(Image image) + { + _pictureBox = new PictureBox(); + _pictureBox.BackgroundImage = image; + _pictureBox.BackgroundImageLayout = Width >= image.Width && Height >= image.Height ? ImageLayout.Center : ImageLayout.Zoom; + _pictureBox.Dock = DockStyle.Fill; + Controls.Add(_pictureBox); + } + + /// + /// Adds a Text Box to display errors. + /// + /// Message to be displayed in textbox. + private void AddTextBoxControl(string message) + { + _textBox = new RichTextBox(); + _textBox.Text = message; + _textBox.BackColor = Color.LightYellow; + _textBox.Multiline = true; + _textBox.Dock = DockStyle.Top; + _textBox.ReadOnly = true; + _textBox.ContentsResized += RTBContentsResized; + _textBox.ScrollBars = RichTextBoxScrollBars.None; + _textBox.BorderStyle = BorderStyle.None; + Controls.Add(_textBox); + } + + /// + /// Called when an error occurs during preview. + /// + /// The exception which occurred. + /// Stream reference to access source file. + private void PreviewError(Exception exception, T dataSource) + { + try + { + PowerToysTelemetry.Log.WriteEvent(new QoiFilePreviewError { Message = exception.Message }); + } + catch + { // Should not crash if sending telemetry is failing. Ignore the exception. + } + + Controls.Clear(); + _infoBarAdded = true; + AddTextBoxControl(Properties.Resource.QoiNotPreviewedError); + base.DoPreview(dataSource); + } + } +} diff --git a/src/modules/previewpane/QoiPreviewHandler/Settings.cs b/src/modules/previewpane/QoiPreviewHandler/Settings.cs new file mode 100644 index 0000000000..779f18a48f --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandler/Settings.cs @@ -0,0 +1,38 @@ +// 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.PowerToys.PreviewHandler.Qoi +{ + internal sealed class Settings + { + /// + /// Gets the color of the window background. + /// Even though this is not a setting yet, it's retrieved from a "Settings" class to be aligned with other preview handlers that contain this setting. + /// It's possible it can be converted into a setting in the future. + /// + public static Color BackgroundColor + { + get + { + if (GetTheme() == "dark") + { + return Color.FromArgb(30, 30, 30); // #1e1e1e + } + else + { + return Color.White; + } + } + } + + /// + /// Returns the theme. + /// + /// Theme that should be used. + public static string GetTheme() + { + return Common.UI.ThemeManager.GetWindowsBaseColor().ToLowerInvariant(); + } + } +} diff --git a/src/modules/previewpane/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewError.cs b/src/modules/previewpane/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewError.cs new file mode 100644 index 0000000000..71aa4f575a --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewError.cs @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.Tracing; +using Microsoft.PowerToys.Telemetry; +using Microsoft.PowerToys.Telemetry.Events; + +namespace Microsoft.PowerToys.PreviewHandler.Qoi.Telemetry.Events +{ + /// + /// A telemetry event to be raised when an error has occurred in the preview pane. + /// + [EventData] + public class QoiFilePreviewError : EventBase, IEvent + { + /// + /// Gets or sets the error message to log as part of the telemetry event. + /// + public string Message { get; set; } + + /// + public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServicePerformance; + } +} diff --git a/src/modules/previewpane/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewed.cs b/src/modules/previewpane/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewed.cs new file mode 100644 index 0000000000..b38727ebbb --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandler/Telemetry/Events/QoiFilePreviewed.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Diagnostics.Tracing; +using Microsoft.PowerToys.Telemetry; +using Microsoft.PowerToys.Telemetry.Events; + +namespace Microsoft.PowerToys.PreviewHandler.Qoi.Telemetry.Events +{ + /// + /// A telemetry event to be raised when a Qoi file has been viewed in the preview pane. + /// + [EventData] + public class QoiFilePreviewed : EventBase, IEvent + { + /// + public PartA_PrivTags PartA_PrivTags => PartA_PrivTags.ProductAndServiceUsage; + } +} diff --git a/src/modules/previewpane/QoiPreviewHandlerCpp/ClassFactory.cpp b/src/modules/previewpane/QoiPreviewHandlerCpp/ClassFactory.cpp new file mode 100644 index 0000000000..0d6a9934cc --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/ClassFactory.cpp @@ -0,0 +1,84 @@ +#include "pch.h" +#include "ClassFactory.h" +#include "QoiPreviewHandler.h" + +#include +#include + +extern long g_cDllRef; + +ClassFactory::ClassFactory() : + m_cRef(1) +{ + InterlockedIncrement(&g_cDllRef); +} + +ClassFactory::~ClassFactory() +{ + InterlockedDecrement(&g_cDllRef); +} + +// +// IUnknown +// + +IFACEMETHODIMP ClassFactory::QueryInterface(REFIID riid, void **ppv) +{ + static const QITAB qit[] = { + QITABENT(ClassFactory, IClassFactory), + { 0 }, + }; + return QISearch(this, qit, riid, ppv); +} + +IFACEMETHODIMP_(ULONG) ClassFactory::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +IFACEMETHODIMP_(ULONG) ClassFactory::Release() +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (0 == cRef) + { + delete this; + } + return cRef; +} + +// +// IClassFactory +// + +IFACEMETHODIMP ClassFactory::CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppv) +{ + HRESULT hr = CLASS_E_NOAGGREGATION; + + if (pUnkOuter == NULL) + { + hr = E_OUTOFMEMORY; + + QoiPreviewHandler* pExt = new (std::nothrow) QoiPreviewHandler(); + if (pExt) + { + hr = pExt->QueryInterface(riid, ppv); + pExt->Release(); + } + } + + return hr; +} + +IFACEMETHODIMP ClassFactory::LockServer(BOOL fLock) +{ + if (fLock) + { + InterlockedIncrement(&g_cDllRef); + } + else + { + InterlockedDecrement(&g_cDllRef); + } + + return S_OK; +} \ No newline at end of file diff --git a/src/modules/previewpane/QoiPreviewHandlerCpp/ClassFactory.h b/src/modules/previewpane/QoiPreviewHandlerCpp/ClassFactory.h new file mode 100644 index 0000000000..b393c3916e --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/ClassFactory.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +class ClassFactory : public IClassFactory +{ +public: + // IUnknown + IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv); + IFACEMETHODIMP_(ULONG) AddRef(); + IFACEMETHODIMP_(ULONG) Release(); + + // IClassFactory + IFACEMETHODIMP CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv); + IFACEMETHODIMP LockServer(BOOL fLock); + + ClassFactory(); + +protected: + ~ClassFactory(); + +private: + long m_cRef; +}; diff --git a/src/modules/previewpane/QoiPreviewHandlerCpp/GlobalExportFunctions.def b/src/modules/previewpane/QoiPreviewHandlerCpp/GlobalExportFunctions.def new file mode 100644 index 0000000000..76fc66cac3 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/GlobalExportFunctions.def @@ -0,0 +1,3 @@ +EXPORTS + DllGetClassObject PRIVATE + DllCanUnloadNow PRIVATE diff --git a/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandler.cpp b/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandler.cpp new file mode 100644 index 0000000000..c5d9816478 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandler.cpp @@ -0,0 +1,266 @@ +#include "pch.h" +#include "QoiPreviewHandler.h" +#include "../powerpreview/powerpreviewConstants.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +extern HINSTANCE g_hInst; +extern long g_cDllRef; + +QoiPreviewHandler::QoiPreviewHandler() : + m_cRef(1), m_hwndParent(NULL), m_rcParent(), m_punkSite(NULL), m_process(NULL) +{ + m_resizeEvent = CreateEvent(nullptr, false, false, CommonSharedConstants::QOI_PREVIEW_RESIZE_EVENT); + + std::filesystem::path logFilePath(PTSettingsHelper::get_local_low_folder_location()); + logFilePath.append(LogSettings::qoiPrevLogPath); + Logger::init(LogSettings::qoiPrevLoggerName, logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location()); + + InterlockedIncrement(&g_cDllRef); +} + +QoiPreviewHandler::~QoiPreviewHandler() +{ + InterlockedDecrement(&g_cDllRef); +} + +#pragma region IUnknown + +IFACEMETHODIMP QoiPreviewHandler::QueryInterface(REFIID riid, void** ppv) +{ + static const QITAB qit[] = { + QITABENT(QoiPreviewHandler, IPreviewHandler), + QITABENT(QoiPreviewHandler, IInitializeWithFile), + QITABENT(QoiPreviewHandler, IPreviewHandlerVisuals), + QITABENT(QoiPreviewHandler, IOleWindow), + QITABENT(QoiPreviewHandler, IObjectWithSite), + { 0 }, + }; + return QISearch(this, qit, riid, ppv); +} + +IFACEMETHODIMP_(ULONG) +QoiPreviewHandler::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +IFACEMETHODIMP_(ULONG) +QoiPreviewHandler::Release() +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (0 == cRef) + { + delete this; + } + return cRef; +} + +#pragma endregion + +#pragma region IInitializationWithFile + +IFACEMETHODIMP QoiPreviewHandler::Initialize(LPCWSTR pszFilePath, DWORD grfMode) +{ + m_filePath = pszFilePath; + return S_OK; +} + +#pragma endregion + +#pragma region IPreviewHandler + +IFACEMETHODIMP QoiPreviewHandler::SetWindow(HWND hwnd, const RECT* prc) +{ + if (hwnd && prc) + { + m_hwndParent = hwnd; + m_rcParent = *prc; + } + return S_OK; +} + +IFACEMETHODIMP QoiPreviewHandler::SetFocus() +{ + return S_OK; +} + +IFACEMETHODIMP QoiPreviewHandler::QueryFocus(HWND* phwnd) +{ + HRESULT hr = E_INVALIDARG; + if (phwnd) + { + *phwnd = ::GetFocus(); + if (*phwnd) + { + hr = S_OK; + } + else + { + hr = HRESULT_FROM_WIN32(GetLastError()); + } + } + return hr; +} + +IFACEMETHODIMP QoiPreviewHandler::TranslateAccelerator(MSG* pmsg) +{ + HRESULT hr = S_FALSE; + IPreviewHandlerFrame* pFrame = NULL; + if (m_punkSite && SUCCEEDED(m_punkSite->QueryInterface(&pFrame))) + { + hr = pFrame->TranslateAccelerator(pmsg); + + pFrame->Release(); + } + return hr; +} + +IFACEMETHODIMP QoiPreviewHandler::SetRect(const RECT* prc) +{ + HRESULT hr = E_INVALIDARG; + if (prc != NULL) + { + if (!m_resizeEvent) + { + Logger::error(L"Failed to create resize event for QoiPreviewHandler"); + } + else + { + if (m_rcParent.right != prc->right || m_rcParent.left != prc->left || m_rcParent.top != prc->top || m_rcParent.bottom != prc->bottom) + { + if (!SetEvent(m_resizeEvent)) + { + Logger::error(L"Failed to signal resize event for QoiPreviewHandler"); + } + } + } + m_rcParent = *prc; + hr = S_OK; + } + return hr; +} + +IFACEMETHODIMP QoiPreviewHandler::DoPreview() +{ + try + { + Logger::info(L"Starting QoiPreviewHandler.exe"); + + STARTUPINFO info = { sizeof(info) }; + std::wstring cmdLine{ L"\"" + m_filePath + L"\"" }; + cmdLine += L" "; + std::wostringstream ss; + ss << std::hex << m_hwndParent; + + cmdLine += ss.str(); + cmdLine += L" "; + cmdLine += std::to_wstring(m_rcParent.left); + cmdLine += L" "; + cmdLine += std::to_wstring(m_rcParent.right); + cmdLine += L" "; + cmdLine += std::to_wstring(m_rcParent.top); + cmdLine += L" "; + cmdLine += std::to_wstring(m_rcParent.bottom); + std::wstring appPath = get_module_folderpath(g_hInst) + L"\\PowerToys.QoiPreviewHandler.exe"; + + SHELLEXECUTEINFO sei{ sizeof(sei) }; + sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; + sei.lpFile = appPath.c_str(); + sei.lpParameters = cmdLine.c_str(); + sei.nShow = SW_SHOWDEFAULT; + ShellExecuteEx(&sei); + m_process = sei.hProcess; + } + catch (std::exception& e) + { + std::wstring errorMessage = std::wstring{ winrt::to_hstring(e.what()) }; + Logger::error(L"Failed to start QoiPreviewHandler.exe. Error: {}", errorMessage); + } + + return S_OK; +} + +IFACEMETHODIMP QoiPreviewHandler::Unload() +{ + Logger::info(L"Unload and terminate .exe"); + + m_hwndParent = NULL; + TerminateProcess(m_process, 0); + return S_OK; +} + +#pragma endregion + +#pragma region IPreviewHandlerVisuals + +IFACEMETHODIMP QoiPreviewHandler::SetBackgroundColor(COLORREF color) +{ + HBRUSH brush = CreateSolidBrush(WindowsColors::is_dark_mode() ? powerpreviewConstants::DARK_THEME_COLOR : powerpreviewConstants::LIGHT_THEME_COLOR); + SetClassLongPtr(m_hwndParent, GCLP_HBRBACKGROUND, reinterpret_cast(brush)); + return S_OK; +} + +IFACEMETHODIMP QoiPreviewHandler::SetFont(const LOGFONTW* plf) +{ + return S_OK; +} + +IFACEMETHODIMP QoiPreviewHandler::SetTextColor(COLORREF color) +{ + return S_OK; +} + +#pragma endregion + +#pragma region IOleWindow + +IFACEMETHODIMP QoiPreviewHandler::GetWindow(HWND* phwnd) +{ + HRESULT hr = E_INVALIDARG; + if (phwnd) + { + *phwnd = m_hwndParent; + hr = S_OK; + } + return hr; +} + +IFACEMETHODIMP QoiPreviewHandler::ContextSensitiveHelp(BOOL fEnterMode) +{ + return E_NOTIMPL; +} + +#pragma endregion + +#pragma region IObjectWithSite + +IFACEMETHODIMP QoiPreviewHandler::SetSite(IUnknown* punkSite) +{ + if (m_punkSite) + { + m_punkSite->Release(); + m_punkSite = NULL; + } + return punkSite ? punkSite->QueryInterface(&m_punkSite) : S_OK; +} + +IFACEMETHODIMP QoiPreviewHandler::GetSite(REFIID riid, void** ppv) +{ + *ppv = NULL; + return m_punkSite ? m_punkSite->QueryInterface(riid, ppv) : E_FAIL; +} + +#pragma endregion + +#pragma region Helper Functions + +#pragma endregion diff --git a/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandler.h b/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandler.h new file mode 100644 index 0000000000..0534f7cd55 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandler.h @@ -0,0 +1,69 @@ +#pragma once + +#include "pch.h" + +#include +#include +#include + +class QoiPreviewHandler : + public IInitializeWithFile, + public IPreviewHandler, + public IPreviewHandlerVisuals, + public IOleWindow, + public IObjectWithSite +{ +public: + // IUnknown + IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv); + IFACEMETHODIMP_(ULONG) AddRef(); + IFACEMETHODIMP_(ULONG) Release(); + + // IInitializeWithFile + IFACEMETHODIMP Initialize(LPCWSTR pszFilePath, DWORD grfMode); + + // IPreviewHandler + IFACEMETHODIMP SetWindow(HWND hwnd, const RECT* prc); + IFACEMETHODIMP SetFocus(); + IFACEMETHODIMP QueryFocus(HWND* phwnd); + IFACEMETHODIMP TranslateAccelerator(MSG* pmsg); + IFACEMETHODIMP SetRect(const RECT* prc); + IFACEMETHODIMP DoPreview(); + IFACEMETHODIMP Unload(); + + // IPreviewHandlerVisuals + IFACEMETHODIMP SetBackgroundColor(COLORREF color); + IFACEMETHODIMP SetFont(const LOGFONTW* plf); + IFACEMETHODIMP SetTextColor(COLORREF color); + + // IOleWindow + IFACEMETHODIMP GetWindow(HWND* phwnd); + IFACEMETHODIMP ContextSensitiveHelp(BOOL fEnterMode); + + // IObjectWithSite + IFACEMETHODIMP SetSite(IUnknown* punkSite); + IFACEMETHODIMP GetSite(REFIID riid, void** ppv); + + QoiPreviewHandler(); +protected: + ~QoiPreviewHandler(); + +private: + // Reference count of component. + long m_cRef; + + // Provided during initialization. + std::wstring m_filePath; + + // Parent window that hosts the previewer window. + // Note: do NOT DestroyWindow this. + HWND m_hwndParent; + // Bounding rect of the parent window. + RECT m_rcParent; + + // Site pointer from host, used to get IPreviewHandlerFrame. + IUnknown* m_punkSite; + + HANDLE m_process; + HANDLE m_resizeEvent; +}; \ No newline at end of file diff --git a/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.rc b/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.rc new file mode 100644 index 0000000000..5fa3c8b90d --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.rc @@ -0,0 +1,40 @@ +#include +#include "resource.h" +#include "../../../common/version/version.h" + +#define APSTUDIO_READONLY_SYMBOLS +#include "winres.h" +#undef APSTUDIO_READONLY_SYMBOLS + +1 VERSIONINFO +FILEVERSION FILE_VERSION +PRODUCTVERSION PRODUCT_VERSION +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG +FILEFLAGS VS_FF_DEBUG +#else +FILEFLAGS 0x0L +#endif +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_DLL +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset + BEGIN + VALUE "CompanyName", COMPANY_NAME + VALUE "FileDescription", FILE_DESCRIPTION + VALUE "FileVersion", FILE_VERSION_STRING + VALUE "InternalName", INTERNAL_NAME + VALUE "LegalCopyright", COPYRIGHT_NOTE + VALUE "OriginalFilename", ORIGINAL_FILENAME + VALUE "ProductName", PRODUCT_NAME + VALUE "ProductVersion", PRODUCT_VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset + END +END diff --git a/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.vcxproj b/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.vcxproj new file mode 100644 index 0000000000..28307d2d30 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.vcxproj @@ -0,0 +1,121 @@ + + + + + 16.0 + Win32Proj + {3BAF9C81-A194-4925-A035-5E24A5D1E542} + QoiPreviewHandlerCpp + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + ..\..\..\..\$(Platform)\$(Configuration)\ + + + PowerToys.$(ProjectName) + + + + Level3 + true + _DEBUG;MARKDOWNPREVIEWHANDLERCPP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + ../../.. + + + Windows + true + false + GlobalExportFunctions.def + Shlwapi.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + + + + + Level3 + true + true + true + NDEBUG;MARKDOWNPREVIEWHANDLERCPP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + ../../.. + + + Windows + true + true + true + false + GlobalExportFunctions.def + Shlwapi.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + + + + + + + + + + + + + + Create + + + + + + + + + + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + {6955446d-23f7-4023-9bb3-8657f904af99} + + + {98537082-0fdb-40de-abd8-0dc5a4269bab} + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.vcxproj.filters b/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.vcxproj.filters new file mode 100644 index 0000000000..5878befcdc --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/QoiPreviewHandlerCpp.vcxproj.filters @@ -0,0 +1,56 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Resource Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + + + + Resource Files + + + \ No newline at end of file diff --git a/src/modules/previewpane/QoiPreviewHandlerCpp/dllmain.cpp b/src/modules/previewpane/QoiPreviewHandlerCpp/dllmain.cpp new file mode 100644 index 0000000000..444d42f1a9 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/dllmain.cpp @@ -0,0 +1,73 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "pch.h" +#include "ClassFactory.h" + +HINSTANCE g_hInst = NULL; +long g_cDllRef = 0; + +// {729B72CD-B72E-4FE9-BCBF-E954B33FE699} +static const GUID CLSID_QoiPreviewHandler = { 0x729b72cd, 0xb72e, 0x4fe9, { 0xbc, 0xbf, 0xe9, 0x54, 0xb3, 0x3f, 0xe6, 0x99 } }; + +BOOL APIENTRY DllMain(HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + g_hInst = hModule; + DisableThreadLibraryCalls(hModule); + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + +// +// FUNCTION: DllGetClassObject +// +// PURPOSE: Create the class factory and query to the specific interface. +// +// PARAMETERS: +// * rclsid - The CLSID that will associate the correct data and code. +// * riid - A reference to the identifier of the interface that the caller +// is to use to communicate with the class object. +// * ppv - The address of a pointer variable that receives the interface +// pointer requested in riid. Upon successful return, *ppv contains the +// requested interface pointer. If an error occurs, the interface pointer +// is NULL. +// +STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv) +{ + HRESULT hr = CLASS_E_CLASSNOTAVAILABLE; + + if (IsEqualCLSID(CLSID_QoiPreviewHandler, rclsid)) + { + hr = E_OUTOFMEMORY; + + ClassFactory* pClassFactory = new ClassFactory(); + if (pClassFactory) + { + hr = pClassFactory->QueryInterface(riid, ppv); + pClassFactory->Release(); + } + } + + return hr; +} + +// +// FUNCTION: DllCanUnloadNow +// +// PURPOSE: Check if we can unload the component from the memory. +// +// NOTE: The component can be unloaded from the memory when its reference +// count is zero (i.e. nobody is still using the component). +// +STDAPI DllCanUnloadNow(void) +{ + return g_cDllRef > 0 ? S_FALSE : S_OK; +} diff --git a/src/modules/previewpane/QoiPreviewHandlerCpp/packages.config b/src/modules/previewpane/QoiPreviewHandlerCpp/packages.config new file mode 100644 index 0000000000..47bae1882f --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/modules/previewpane/QoiPreviewHandlerCpp/pch.cpp b/src/modules/previewpane/QoiPreviewHandlerCpp/pch.cpp new file mode 100644 index 0000000000..64b7eef6d6 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/src/modules/previewpane/QoiPreviewHandlerCpp/pch.h b/src/modules/previewpane/QoiPreviewHandlerCpp/pch.h new file mode 100644 index 0000000000..125ddcdf24 --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/pch.h @@ -0,0 +1,14 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef PCH_H +#define PCH_H + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files +#include + +#endif //PCH_H diff --git a/src/modules/previewpane/QoiPreviewHandlerCpp/resource.h b/src/modules/previewpane/QoiPreviewHandlerCpp/resource.h new file mode 100644 index 0000000000..7adebf4efe --- /dev/null +++ b/src/modules/previewpane/QoiPreviewHandlerCpp/resource.h @@ -0,0 +1,13 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by AlwaysOnTopModuleInterface.rc + +////////////////////////////// +// Non-localizable + +#define FILE_DESCRIPTION "PowerToys Qoi Preview Handler Module" +#define INTERNAL_NAME "PowerToys.QoiPreviewHandlerCpp" +#define ORIGINAL_FILENAME "PowerToys.QoiPreviewHandlerCpp.dll" + +// Non-localizable +////////////////////////////// diff --git a/src/modules/previewpane/QoiThumbnailProvider/Program.cs b/src/modules/previewpane/QoiThumbnailProvider/Program.cs new file mode 100644 index 0000000000..7d0e898212 --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProvider/Program.cs @@ -0,0 +1,42 @@ +// 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.Globalization; + +namespace Microsoft.PowerToys.ThumbnailHandler.Qoi +{ + internal static class Program + { + private static QoiThumbnailProvider _thumbnailProvider; + + /// + /// The main entry point for the application. + /// + [STAThread] + public static void Main(string[] args) + { + ApplicationConfiguration.Initialize(); + if (args != null) + { + if (args.Length == 2) + { + string filePath = args[0]; + uint cx = Convert.ToUInt32(args[1], 10); + + _thumbnailProvider = new QoiThumbnailProvider(filePath); + Bitmap thumbnail = _thumbnailProvider.GetThumbnail(cx); + if (thumbnail != null) + { + filePath = filePath.Replace(".qoi", ".bmp"); + thumbnail.Save(filePath, System.Drawing.Imaging.ImageFormat.Bmp); + } + } + else + { + MessageBox.Show("Qoi thumbnail - wrong number of args: " + args.Length.ToString(CultureInfo.InvariantCulture)); + } + } + } + } +} diff --git a/src/modules/previewpane/QoiThumbnailProvider/QoiThumbnailProvider.cs b/src/modules/previewpane/QoiThumbnailProvider/QoiThumbnailProvider.cs new file mode 100644 index 0000000000..b055a26d0b --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProvider/QoiThumbnailProvider.cs @@ -0,0 +1,139 @@ +// 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.Drawing.Drawing2D; +using System.Drawing.Imaging; +using PreviewHandlerCommon.Utilities; + +namespace Microsoft.PowerToys.ThumbnailHandler.Qoi +{ + /// + /// Qoi Thumbnail Provider. + /// + public class QoiThumbnailProvider + { + public QoiThumbnailProvider(string filePath) + { + FilePath = filePath; + Stream = new FileStream(filePath, FileMode.Open, FileAccess.Read); + } + + /// + /// Gets the file path to the file creating thumbnail for. + /// + public string FilePath { get; private set; } + + /// + /// Gets the stream object to access file. + /// + public Stream Stream { get; private set; } + + /// + /// The maximum dimension (width or height) thumbnail we will generate. + /// + private const uint MaxThumbnailSize = 10000; + + /// + /// Generate thumbnail bitmap for provided Qoi stream. + /// + /// The Stream instance for the Qoi bitmap. + /// The maximum thumbnail size, in pixels. + /// A thumbnail rendered from the Qoi bitmap. + public static Bitmap GetThumbnail(Stream stream, uint cx) + { + if (cx > MaxThumbnailSize || stream == null || stream.Length == 0) + { + return null; + } + + Bitmap thumbnail = null; + try + { + thumbnail = QoiImage.FromStream(stream); + } + catch (Exception) + { + // TODO: add logger + } + + if (thumbnail != null && ( + ((thumbnail.Width != cx || thumbnail.Height > cx) && (thumbnail.Height != cx || thumbnail.Width > cx)) || + thumbnail.PixelFormat != PixelFormat.Format32bppArgb)) + { + // We are not the appropriate size for caller. Resize now while + // respecting the aspect ratio. + float scale = Math.Min((float)cx / thumbnail.Width, (float)cx / thumbnail.Height); + int scaleWidth = (int)(thumbnail.Width * scale); + int scaleHeight = (int)(thumbnail.Height * scale); + thumbnail = ResizeImage(thumbnail, scaleWidth, scaleHeight); + } + + return thumbnail; + } + + /// + /// Resize the image with high quality to the specified width and height. + /// + /// The image to resize. + /// The width to resize to. + /// The height to resize to. + /// The resized image. + public static Bitmap ResizeImage(Image image, int width, int height) + { + if (width <= 0 || + height <= 0 || + width > MaxThumbnailSize || + height > MaxThumbnailSize || + image == null) + { + return null; + } + + Bitmap destImage = new Bitmap(width, height, PixelFormat.Format32bppArgb); + + destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); + + using (var graphics = Graphics.FromImage(destImage)) + { + graphics.CompositingMode = CompositingMode.SourceCopy; + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.SmoothingMode = SmoothingMode.HighQuality; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + + graphics.DrawImage(image, 0, 0, width, height); + } + + image.Dispose(); + + return destImage; + } + + /// + /// Generate thumbnail bitmap for provided Qoi file/stream. + /// + /// Maximum thumbnail size, in pixels. + /// Generated bitmap + public Bitmap GetThumbnail(uint cx) + { + if (cx == 0 || cx > MaxThumbnailSize) + { + return null; + } + + if (global::PowerToys.GPOWrapper.GPOWrapper.GetConfiguredQoiThumbnailsEnabledValue() == global::PowerToys.GPOWrapper.GpoRuleConfigured.Disabled) + { + // GPO is disabling this utility. + return null; + } + + Bitmap thumbnail = GetThumbnail(this.Stream, cx); + if (thumbnail != null && thumbnail.Size.Width > 0 && thumbnail.Size.Height > 0) + { + return thumbnail; + } + + return null; + } + } +} diff --git a/src/modules/previewpane/QoiThumbnailProvider/QoiThumbnailProvider.csproj b/src/modules/previewpane/QoiThumbnailProvider/QoiThumbnailProvider.csproj new file mode 100644 index 0000000000..e78b84b355 --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProvider/QoiThumbnailProvider.csproj @@ -0,0 +1,54 @@ + + + enable + true + {D949EC7D-48A9-4279-95D5-078E7FD1F048} + Microsoft.PowerToys.ThumbnailHandler.Qoi + PowerToys.QoiThumbnailProvider + PowerToys.QoiThumbnailProvider + PowerToys QoiPreviewHandler + net7.0-windows10.0.20348.0 + 10.0.19041.0 + 10.0.19041.0 + true + PowerToys QoiPreviewHandler + ..\..\..\..\$(Platform)\$(Configuration) + false + false + true + true + + + + + win10-x64 + + + win10-arm64 + + + + + + + PowerToys.GPOWrapper + $(OutDir) + false + + + + $(NoWarn);1591 + WinExe + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/QoiThumbnailProviderCpp/ClassFactory.cpp b/src/modules/previewpane/QoiThumbnailProviderCpp/ClassFactory.cpp new file mode 100644 index 0000000000..1765e8d101 --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/ClassFactory.cpp @@ -0,0 +1,84 @@ +#include "pch.h" +#include "ClassFactory.h" +#include "QoiThumbnailProvider.h" + +#include +#include + +extern long g_cDllRef; + +ClassFactory::ClassFactory() : + m_cRef(1) +{ + InterlockedIncrement(&g_cDllRef); +} + +ClassFactory::~ClassFactory() +{ + InterlockedDecrement(&g_cDllRef); +} + +// +// IUnknown +// + +IFACEMETHODIMP ClassFactory::QueryInterface(REFIID riid, void **ppv) +{ + static const QITAB qit[] = { + QITABENT(ClassFactory, IClassFactory), + { 0 }, + }; + return QISearch(this, qit, riid, ppv); +} + +IFACEMETHODIMP_(ULONG) ClassFactory::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +IFACEMETHODIMP_(ULONG) ClassFactory::Release() +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (0 == cRef) + { + delete this; + } + return cRef; +} + +// +// IClassFactory +// + +IFACEMETHODIMP ClassFactory::CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppv) +{ + HRESULT hr = CLASS_E_NOAGGREGATION; + + if (pUnkOuter == NULL) + { + hr = E_OUTOFMEMORY; + + QoiThumbnailProvider* pExt = new (std::nothrow) QoiThumbnailProvider(); + if (pExt) + { + hr = pExt->QueryInterface(riid, ppv); + pExt->Release(); + } + } + + return hr; +} + +IFACEMETHODIMP ClassFactory::LockServer(BOOL fLock) +{ + if (fLock) + { + InterlockedIncrement(&g_cDllRef); + } + else + { + InterlockedDecrement(&g_cDllRef); + } + + return S_OK; +} \ No newline at end of file diff --git a/src/modules/previewpane/QoiThumbnailProviderCpp/ClassFactory.h b/src/modules/previewpane/QoiThumbnailProviderCpp/ClassFactory.h new file mode 100644 index 0000000000..b393c3916e --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/ClassFactory.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +class ClassFactory : public IClassFactory +{ +public: + // IUnknown + IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv); + IFACEMETHODIMP_(ULONG) AddRef(); + IFACEMETHODIMP_(ULONG) Release(); + + // IClassFactory + IFACEMETHODIMP CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv); + IFACEMETHODIMP LockServer(BOOL fLock); + + ClassFactory(); + +protected: + ~ClassFactory(); + +private: + long m_cRef; +}; diff --git a/src/modules/previewpane/QoiThumbnailProviderCpp/GlobalExportFunctions.def b/src/modules/previewpane/QoiThumbnailProviderCpp/GlobalExportFunctions.def new file mode 100644 index 0000000000..76fc66cac3 --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/GlobalExportFunctions.def @@ -0,0 +1,3 @@ +EXPORTS + DllGetClassObject PRIVATE + DllCanUnloadNow PRIVATE diff --git a/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProvider.cpp b/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProvider.cpp new file mode 100644 index 0000000000..50781f5d29 --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProvider.cpp @@ -0,0 +1,195 @@ +#include "pch.h" +#include "QoiThumbnailProvider.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +extern HINSTANCE g_hInst; +extern long g_cDllRef; + +QoiThumbnailProvider::QoiThumbnailProvider() : + m_cRef(1), m_pStream(NULL), m_process(NULL) +{ + std::filesystem::path logFilePath(PTSettingsHelper::get_local_low_folder_location()); + logFilePath.append(LogSettings::qoiThumbLogPath); + Logger::init(LogSettings::qoiThumbLoggerName, logFilePath.wstring(), PTSettingsHelper::get_log_settings_file_location()); + + InterlockedIncrement(&g_cDllRef); +} + +QoiThumbnailProvider::~QoiThumbnailProvider() +{ + InterlockedDecrement(&g_cDllRef); +} + +#pragma region IUnknown + +IFACEMETHODIMP QoiThumbnailProvider::QueryInterface(REFIID riid, void** ppv) +{ + static const QITAB qit[] = { + QITABENT(QoiThumbnailProvider, IThumbnailProvider), + QITABENT(QoiThumbnailProvider, IInitializeWithStream), + { 0 }, + }; + return QISearch(this, qit, riid, ppv); +} + +IFACEMETHODIMP_(ULONG) +QoiThumbnailProvider::AddRef() +{ + return InterlockedIncrement(&m_cRef); +} + +IFACEMETHODIMP_(ULONG) +QoiThumbnailProvider::Release() +{ + ULONG cRef = InterlockedDecrement(&m_cRef); + if (0 == cRef) + { + delete this; + } + return cRef; +} + +#pragma endregion + +#pragma region IInitializationWithStream + +IFACEMETHODIMP QoiThumbnailProvider::Initialize(IStream* pStream, DWORD grfMode) +{ + HRESULT hr = E_INVALIDARG; + if (pStream) + { + // Initialize can be called more than once, so release existing valid + // m_pStream. + if (m_pStream) + { + m_pStream->Release(); + m_pStream = NULL; + } + + m_pStream = pStream; + m_pStream->AddRef(); + hr = S_OK; + } + return hr; +} + +#pragma endregion + +#pragma region IThumbnailProvider + +IFACEMETHODIMP QoiThumbnailProvider::GetThumbnail(UINT cx, HBITMAP* phbmp, WTS_ALPHATYPE* pdwAlpha) +{ + // Read stream into the buffer + char buffer[4096]; + ULONG cbRead; + + Logger::trace(L"Begin"); + + GUID guid; + if (CoCreateGuid(&guid) == S_OK) + { + wil::unique_cotaskmem_string guidString; + if (SUCCEEDED(StringFromCLSID(guid, &guidString))) + { + Logger::info(L"Read stream and save to tmp file."); + + // {CLSID} -> CLSID + std::wstring guid = std::wstring(guidString.get()).substr(1, std::wstring(guidString.get()).size() - 2); + std::wstring filePath = PTSettingsHelper::get_local_low_folder_location() + L"\\QoiThumbnail-Temp\\"; + if (!std::filesystem::exists(filePath)) + { + std::filesystem::create_directories(filePath); + } + + std::wstring fileName = filePath + guid + L".qoi"; + + // Write data to tmp file + std::fstream file; + file.open(fileName, std::ios_base::out | std::ios_base::binary); + + if (!file.is_open()) + { + return 0; + } + + while (true) + { + auto result = m_pStream->Read(buffer, 4096, &cbRead); + + file.write(buffer, cbRead); + if (result == S_FALSE) + { + break; + } + } + file.close(); + + m_pStream->Release(); + m_pStream = NULL; + + try + { + Logger::info(L"Start QoiThumbnailProvider.exe"); + + STARTUPINFO info = { sizeof(info) }; + std::wstring cmdLine{ L"\"" + fileName + L"\"" }; + cmdLine += L" "; + cmdLine += std::to_wstring(cx); + + std::wstring appPath = get_module_folderpath(g_hInst) + L"\\PowerToys.QoiThumbnailProvider.exe"; + + SHELLEXECUTEINFO sei{ sizeof(sei) }; + sei.fMask = { SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI }; + sei.lpFile = appPath.c_str(); + sei.lpParameters = cmdLine.c_str(); + sei.nShow = SW_SHOWDEFAULT; + ShellExecuteEx(&sei); + m_process = sei.hProcess; + WaitForSingleObject(m_process, INFINITE); + std::filesystem::remove(fileName); + + + std::wstring fileNameBmp = filePath + guid + L".bmp"; + if (std::filesystem::exists(fileNameBmp)) + { + *phbmp = static_cast(LoadImage(NULL, fileNameBmp.c_str(), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE)); + *pdwAlpha = WTS_ALPHATYPE::WTSAT_ARGB; + std::filesystem::remove(fileNameBmp); + } + else + { + Logger::info(L"Bmp file not generated."); + return E_FAIL; + } + } + catch (std::exception& e) + { + std::wstring errorMessage = std::wstring{ winrt::to_hstring(e.what()) }; + Logger::error(L"Failed to start QoiThumbnailProvider.exe. Error: {}", errorMessage); + } + } + } + + + return S_OK; +} + + +#pragma endregion + +#pragma region Helper Functions + +#pragma endregion diff --git a/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProvider.h b/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProvider.h new file mode 100644 index 0000000000..ad959d707b --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProvider.h @@ -0,0 +1,37 @@ +#pragma once + +#include "pch.h" + +#include +#include +#include + +class QoiThumbnailProvider : + public IInitializeWithStream, + public IThumbnailProvider +{ +public: + // IUnknown + IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv); + IFACEMETHODIMP_(ULONG) AddRef(); + IFACEMETHODIMP_(ULONG) Release(); + + // IInitializeWithStream + IFACEMETHODIMP Initialize(IStream* pstream, DWORD grfMode); + + // IThumbnailProvider + IFACEMETHODIMP GetThumbnail(UINT cx, HBITMAP* phbmp, WTS_ALPHATYPE* pdwAlpha); + + QoiThumbnailProvider(); +protected: + ~QoiThumbnailProvider(); + +private: + // Reference count of component. + long m_cRef; + + // Provided during initialization. + IStream* m_pStream; + + HANDLE m_process; +}; \ No newline at end of file diff --git a/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.rc b/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.rc new file mode 100644 index 0000000000..5fa3c8b90d --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.rc @@ -0,0 +1,40 @@ +#include +#include "resource.h" +#include "../../../common/version/version.h" + +#define APSTUDIO_READONLY_SYMBOLS +#include "winres.h" +#undef APSTUDIO_READONLY_SYMBOLS + +1 VERSIONINFO +FILEVERSION FILE_VERSION +PRODUCTVERSION PRODUCT_VERSION +FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG +FILEFLAGS VS_FF_DEBUG +#else +FILEFLAGS 0x0L +#endif +FILEOS VOS_NT_WINDOWS32 +FILETYPE VFT_DLL +FILESUBTYPE VFT2_UNKNOWN +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // US English (0x0409), Unicode (0x04B0) charset + BEGIN + VALUE "CompanyName", COMPANY_NAME + VALUE "FileDescription", FILE_DESCRIPTION + VALUE "FileVersion", FILE_VERSION_STRING + VALUE "InternalName", INTERNAL_NAME + VALUE "LegalCopyright", COPYRIGHT_NOTE + VALUE "OriginalFilename", ORIGINAL_FILENAME + VALUE "ProductName", PRODUCT_NAME + VALUE "ProductVersion", PRODUCT_VERSION_STRING + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 // US English (0x0409), Unicode (1200) charset + END +END diff --git a/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.vcxproj b/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.vcxproj new file mode 100644 index 0000000000..2167c7cecf --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.vcxproj @@ -0,0 +1,120 @@ + + + + + 16.0 + Win32Proj + {CCB5E44F-84D9-4203-83C6-1C9EC9302BC7} + QoiThumbnailProviderCpp + + + + DynamicLibrary + true + v143 + Unicode + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + ..\..\..\..\$(Platform)\$(Configuration)\ + + + PowerToys.$(ProjectName) + + + + Level3 + true + _DEBUG;MARKDOWNPREVIEWHANDLERCPP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + ../../.. + + + Windows + true + false + GlobalExportFunctions.def + Shlwapi.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + + + + + Level3 + true + true + true + NDEBUG;MARKDOWNPREVIEWHANDLERCPP_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + Use + ../../.. + + + Windows + true + true + true + false + GlobalExportFunctions.def + Shlwapi.lib;$(CoreLibraryDependencies);%(AdditionalDependencies) + + + + + + + + + + + + + + Create + + + + + + + + + {d9b8fc84-322a-4f9f-bbb9-20915c47ddfd} + + + {6955446d-23f7-4023-9bb3-8657f904af99} + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.vcxproj.filters b/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.vcxproj.filters new file mode 100644 index 0000000000..7ff177923e --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/QoiThumbnailProviderCpp.vcxproj.filters @@ -0,0 +1,59 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Resource Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Source Files + + + + + + + + + Resource Files + + + \ No newline at end of file diff --git a/src/modules/previewpane/QoiThumbnailProviderCpp/dllmain.cpp b/src/modules/previewpane/QoiThumbnailProviderCpp/dllmain.cpp new file mode 100644 index 0000000000..c39cf8fb7f --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/dllmain.cpp @@ -0,0 +1,73 @@ +// dllmain.cpp : Defines the entry point for the DLL application. +#include "pch.h" +#include "ClassFactory.h" + +HINSTANCE g_hInst = NULL; +long g_cDllRef = 0; + +// {AD856B15-D25E-4008-AFB7-AFAA55586188} +static const GUID CLSID_QoiThumbnailProvider = { 0xad856b15, 0xd25e, 0x4008, { 0xaf, 0xb7, 0xaf, 0xaa, 0x55, 0x58, 0x61, 0x88 } }; + +BOOL APIENTRY DllMain(HMODULE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved) +{ + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + g_hInst = hModule; + DisableThreadLibraryCalls(hModule); + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + break; + } + return TRUE; +} + +// +// FUNCTION: DllGetClassObject +// +// PURPOSE: Create the class factory and query to the specific interface. +// +// PARAMETERS: +// * rclsid - The CLSID that will associate the correct data and code. +// * riid - A reference to the identifier of the interface that the caller +// is to use to communicate with the class object. +// * ppv - The address of a pointer variable that receives the interface +// pointer requested in riid. Upon successful return, *ppv contains the +// requested interface pointer. If an error occurs, the interface pointer +// is NULL. +// +STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void** ppv) +{ + HRESULT hr = CLASS_E_CLASSNOTAVAILABLE; + + if (IsEqualCLSID(CLSID_QoiThumbnailProvider, rclsid)) + { + hr = E_OUTOFMEMORY; + + ClassFactory* pClassFactory = new ClassFactory(); + if (pClassFactory) + { + hr = pClassFactory->QueryInterface(riid, ppv); + pClassFactory->Release(); + } + } + + return hr; +} + +// +// FUNCTION: DllCanUnloadNow +// +// PURPOSE: Check if we can unload the component from the memory. +// +// NOTE: The component can be unloaded from the memory when its reference +// count is zero (i.e. nobody is still using the component). +// +STDAPI DllCanUnloadNow(void) +{ + return g_cDllRef > 0 ? S_FALSE : S_OK; +} diff --git a/src/modules/previewpane/QoiThumbnailProviderCpp/packages.config b/src/modules/previewpane/QoiThumbnailProviderCpp/packages.config new file mode 100644 index 0000000000..e11b462529 --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/modules/previewpane/QoiThumbnailProviderCpp/pch.cpp b/src/modules/previewpane/QoiThumbnailProviderCpp/pch.cpp new file mode 100644 index 0000000000..64b7eef6d6 --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to the pre-compiled header + +#include "pch.h" + +// When you are using pre-compiled headers, this source file is necessary for compilation to succeed. diff --git a/src/modules/previewpane/QoiThumbnailProviderCpp/pch.h b/src/modules/previewpane/QoiThumbnailProviderCpp/pch.h new file mode 100644 index 0000000000..8a0d004247 --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/pch.h @@ -0,0 +1,15 @@ +// pch.h: This is a precompiled header file. +// Files listed below are compiled only once, improving build performance for future builds. +// This also affects IntelliSense performance, including code completion and many code browsing features. +// However, files listed here are ALL re-compiled if any one of them is updated between builds. +// Do not add files here that you will be updating frequently as this negates the performance advantage. + +#ifndef PCH_H +#define PCH_H + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files +#include +#include + +#endif //PCH_H diff --git a/src/modules/previewpane/QoiThumbnailProviderCpp/resource.h b/src/modules/previewpane/QoiThumbnailProviderCpp/resource.h new file mode 100644 index 0000000000..e6392a085f --- /dev/null +++ b/src/modules/previewpane/QoiThumbnailProviderCpp/resource.h @@ -0,0 +1,13 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by AlwaysOnTopModuleInterface.rc + +////////////////////////////// +// Non-localizable + +#define FILE_DESCRIPTION "PowerToys Qoi Thumbnail Provider Module" +#define INTERNAL_NAME "PowerToys.QoiThumbnailProviderCpp" +#define ORIGINAL_FILENAME "PowerToys.QoiThumbnailProviderCpp.dll" + +// Non-localizable +////////////////////////////// diff --git a/src/modules/previewpane/UnitTests-MarkdownPreviewHandler/MarkdownPreviewHandlerTest.cs b/src/modules/previewpane/UnitTests-MarkdownPreviewHandler/MarkdownPreviewHandlerTest.cs index 33dfeb29cc..773bfe65b4 100644 --- a/src/modules/previewpane/UnitTests-MarkdownPreviewHandler/MarkdownPreviewHandlerTest.cs +++ b/src/modules/previewpane/UnitTests-MarkdownPreviewHandler/MarkdownPreviewHandlerTest.cs @@ -17,7 +17,7 @@ namespace MarkdownPreviewHandlerUnitTests public class MarkdownPreviewHandlerTest { // A long timeout is needed. WebView2 can take a long time to load the first time in some CI systems. - private static readonly int HardTimeoutInMilliseconds = 30000; + private static readonly int HardTimeoutInMilliseconds = 60000; private static readonly int SleepTimeInMilliseconds = 200; [TestMethod] diff --git a/src/modules/previewpane/UnitTests-QoiPreviewHandler/HelperFiles/sample.qoi b/src/modules/previewpane/UnitTests-QoiPreviewHandler/HelperFiles/sample.qoi new file mode 100644 index 0000000000..90eef44feb Binary files /dev/null and b/src/modules/previewpane/UnitTests-QoiPreviewHandler/HelperFiles/sample.qoi differ diff --git a/src/modules/previewpane/UnitTests-QoiPreviewHandler/QoiPreviewHandlerTest.cs b/src/modules/previewpane/UnitTests-QoiPreviewHandler/QoiPreviewHandlerTest.cs new file mode 100644 index 0000000000..417e739a05 --- /dev/null +++ b/src/modules/previewpane/UnitTests-QoiPreviewHandler/QoiPreviewHandlerTest.cs @@ -0,0 +1,92 @@ +// 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.Drawing; +using System.IO; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using System.Windows.Forms; +using Microsoft.PowerToys.PreviewHandler.Qoi; +using Microsoft.PowerToys.STATestExtension; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Moq; + +namespace QoiPreviewHandlerUnitTests +{ + [STATestClass] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2201:Do not raise reserved exception types", Justification = "new Exception() is fine in test projects.")] + public class QoiPreviewHandlerTest + { + [TestMethod] + public void QoiPreviewHandlerControlAddsControlsToFormWhenDoPreviewIsCalled() + { + // Arrange + using (var qoiPreviewHandlerControl = new QoiPreviewHandlerControl()) + { + // Act + var file = File.ReadAllBytes("HelperFiles/sample.qoi"); + + qoiPreviewHandlerControl.DoPreview(GetMockStream(file)); + + var flowLayoutPanel = qoiPreviewHandlerControl.Controls[0] as FlowLayoutPanel; + + // Assert + Assert.AreEqual(1, qoiPreviewHandlerControl.Controls.Count); + } + } + + [TestMethod] + public void QoiPreviewHandlerControlShouldAddValidInfoBarIfQoiPreviewThrows() + { + // Arrange + using (var qoiPreviewHandlerControl = new QoiPreviewHandlerControl()) + { + var mockStream = new Mock(); + mockStream + .Setup(x => x.Read(It.IsAny(), It.IsAny(), It.IsAny())) + .Throws(new Exception()); + + // Act + qoiPreviewHandlerControl.DoPreview(mockStream.Object); + var textBox = qoiPreviewHandlerControl.Controls[0] as RichTextBox; + + // Assert + Assert.IsFalse(string.IsNullOrWhiteSpace(textBox.Text)); + Assert.AreEqual(1, qoiPreviewHandlerControl.Controls.Count); + Assert.AreEqual(DockStyle.Top, textBox.Dock); + Assert.AreEqual(Color.LightYellow, textBox.BackColor); + Assert.IsTrue(textBox.Multiline); + Assert.IsTrue(textBox.ReadOnly); + Assert.AreEqual(RichTextBoxScrollBars.None, textBox.ScrollBars); + Assert.AreEqual(BorderStyle.None, textBox.BorderStyle); + } + } + + private static IStream GetMockStream(byte[] sourceArray) + { + var streamMock = new Mock(); + int bytesRead = 0; + + streamMock + .Setup(x => x.Read(It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((buffer, countToRead, bytesReadPtr) => + { + int actualCountToRead = Math.Min(sourceArray.Length - bytesRead, countToRead); + if (actualCountToRead > 0) + { + Array.Copy(sourceArray, bytesRead, buffer, 0, actualCountToRead); + Marshal.WriteInt32(bytesReadPtr, actualCountToRead); + bytesRead += actualCountToRead; + } + else + { + Marshal.WriteInt32(bytesReadPtr, 0); + } + }); + + return streamMock.Object; + } + } +} diff --git a/src/modules/previewpane/UnitTests-QoiPreviewHandler/UnitTests-QoiPreviewHandler.csproj b/src/modules/previewpane/UnitTests-QoiPreviewHandler/UnitTests-QoiPreviewHandler.csproj new file mode 100644 index 0000000000..6f48cfca68 --- /dev/null +++ b/src/modules/previewpane/UnitTests-QoiPreviewHandler/UnitTests-QoiPreviewHandler.csproj @@ -0,0 +1,47 @@ + + + UnitTests-QoiPreviewHandler + PowerToys UnitTests-QoiPreviewHandler + UnitTests-QoiPreviewHandler + PowerToys UnitTests-QoiPreviewHandler + + + + {3940AD4D-F748-4BE4-9083-85769CD553EF} + PdfPreviewHandlerUnitTests + net7.0-windows10.0.20348.0 + 10.0.19041.0 + 10.0.19041.0 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + + diff --git a/src/modules/previewpane/UnitTests-QoiThumbnailProvider/HelperFiles/sample.qoi b/src/modules/previewpane/UnitTests-QoiThumbnailProvider/HelperFiles/sample.qoi new file mode 100644 index 0000000000..90eef44feb Binary files /dev/null and b/src/modules/previewpane/UnitTests-QoiThumbnailProvider/HelperFiles/sample.qoi differ diff --git a/src/modules/previewpane/UnitTests-QoiThumbnailProvider/QoiThumbnailProviderTests.cs b/src/modules/previewpane/UnitTests-QoiThumbnailProvider/QoiThumbnailProviderTests.cs new file mode 100644 index 0000000000..bb1516d8d1 --- /dev/null +++ b/src/modules/previewpane/UnitTests-QoiThumbnailProvider/QoiThumbnailProviderTests.cs @@ -0,0 +1,61 @@ +// 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.Drawing; +using Microsoft.PowerToys.STATestExtension; +using Microsoft.PowerToys.ThumbnailHandler.Qoi; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace QoiThumbnailProviderUnitTests +{ + [STATestClass] + public class QoiThumbnailProviderTests + { + [TestMethod] + public void GetThumbnailValidStreamQoi() + { + // Act + var filePath = "HelperFiles/sample.qoi"; + + QoiThumbnailProvider provider = new QoiThumbnailProvider(filePath); + + Bitmap bitmap = provider.GetThumbnail(256); + + Assert.IsTrue(bitmap != null); + } + + [TestMethod] + public void GetThumbnailInValidSizeQoi() + { + // Act + var filePath = "HelperFiles/sample.qoi"; + + QoiThumbnailProvider provider = new QoiThumbnailProvider(filePath); + + Bitmap bitmap = provider.GetThumbnail(0); + + Assert.IsTrue(bitmap == null); + } + + [TestMethod] + public void GetThumbnailToBigQoi() + { + // Act + var filePath = "HelperFiles/sample.qoi"; + + QoiThumbnailProvider provider = new QoiThumbnailProvider(filePath); + + Bitmap bitmap = provider.GetThumbnail(10001); + + Assert.IsTrue(bitmap == null); + } + + [TestMethod] + public void CheckNoQoiNullStringShouldReturnNullBitmap() + { + Bitmap thumbnail = QoiThumbnailProvider.GetThumbnail(null, 256); + Assert.IsTrue(thumbnail == null); + } + } +} diff --git a/src/modules/previewpane/UnitTests-QoiThumbnailProvider/UnitTests-QoiThumbnailProvider.csproj b/src/modules/previewpane/UnitTests-QoiThumbnailProvider/UnitTests-QoiThumbnailProvider.csproj new file mode 100644 index 0000000000..6bdeabad59 --- /dev/null +++ b/src/modules/previewpane/UnitTests-QoiThumbnailProvider/UnitTests-QoiThumbnailProvider.csproj @@ -0,0 +1,48 @@ + + + UnitTests-QoiThumbnailProvider + PowerToys UnitTests-QoiThumbnailProvider + UnitTests-QoiThumbnailProvider + PowerToys UnitTests-QoiThumbnailProvider + + + + {F8FFFC12-A31A-4AFA-B3DF-14DCF42B5E38} + QoiThumbnailProviderUnitTests + net7.0-windows10.0.20348.0 + 10.0.19041.0 + 10.0.19041.0 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + diff --git a/src/modules/previewpane/UnitTests-SvgPreviewHandler/SvgPreviewControlTests.cs b/src/modules/previewpane/UnitTests-SvgPreviewHandler/SvgPreviewControlTests.cs index d6a6f911ff..2636970e31 100644 --- a/src/modules/previewpane/UnitTests-SvgPreviewHandler/SvgPreviewControlTests.cs +++ b/src/modules/previewpane/UnitTests-SvgPreviewHandler/SvgPreviewControlTests.cs @@ -4,9 +4,7 @@ using System; using System.Drawing; -using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; -using System.Text; using System.Threading; using System.Windows.Forms; using Microsoft.PowerToys.PreviewHandler.Svg; @@ -22,7 +20,7 @@ namespace SvgPreviewHandlerUnitTests public class SvgPreviewControlTests { // A long timeout is needed. WebView2 can take a long time to load the first time in some CI systems. - private static readonly int HardTimeoutInMilliseconds = 30000; + private static readonly int HardTimeoutInMilliseconds = 60000; private static readonly int SleepTimeInMilliseconds = 200; [TestMethod] diff --git a/src/modules/previewpane/powerpreview/CLSID.h b/src/modules/previewpane/powerpreview/CLSID.h index a33d9709f2..0c75ed23f4 100644 --- a/src/modules/previewpane/powerpreview/CLSID.h +++ b/src/modules/previewpane/powerpreview/CLSID.h @@ -37,17 +37,27 @@ const CLSID CLSID_SHIMActivateGcodePreviewHandler = { 0x516cb24f, 0x562f, 0x422f // ec52dea8-7c9f-4130-a77b-1737d0418507 const CLSID CLSID_GcodePreviewHandler = { 0xec52dea8, 0x7c9f, 0x4130, { 0xa7, 0x7b, 0x17, 0x37, 0xd0, 0x41, 0x85, 0x07 } }; +// F498BE36-5C94-4EC9-A65A-AD1CF4C38271 +const GUID CLSID_SHIMActivateQoiPreviewHandler = { 0xf498be36, 0x5c94, 0x4ec9, { 0xa6, 0x5a, 0xad, 0x1c, 0xf4, 0xc3, 0x82, 0x71 } }; + +// 8AA07897-C30B-4543-865B-00A0E5A1B32D +const GUID CLSID_QoiPreviewHandler = { 0x8aa07897, 0xc30b, 0x4543, { 0x86, 0x5b, 0x0, 0xa0, 0xe5, 0xa1, 0xb3, 0x2d } }; + // BFEE99B4-B74D-4348-BCA5-E757029647FF const GUID CLSID_GcodeThumbnailProvider = { 0xbfee99b4, 0xb74d, 0x4348, { 0xbc, 0xa5, 0xe7, 0x57, 0x02, 0x96, 0x47, 0xff } }; // 8BC8AFC2-4E7C-4695-818E-8C1FFDCEA2AF const GUID CLSID_StlThumbnailProvider = { 0x8bc8afc2, 0x4e7c, 0x4695, { 0x81, 0x8e, 0x8c, 0x1f, 0xfd, 0xce, 0xa2, 0xaf } }; +// 907B7E38-38ED-42E7-A276-9EF0ECABB003 +const GUID CLSID_QoiThumbnailProvider = { 0x907b7e38, 0x38ed, 0x42e7, { 0xa2, 0x76, 0x9e, 0xf0, 0xec, 0xab, 0xb0, 0x3 } }; + // Pairs of NativeClsid vs ManagedClsid used for preview handlers. const std::vector> NativeToManagedClsid({ { CLSID_SHIMActivateMdPreviewHandler, CLSID_MdPreviewHandler }, { CLSID_SHIMActivatePdfPreviewHandler, CLSID_PdfPreviewHandler }, { CLSID_SHIMActivateGcodePreviewHandler, CLSID_GcodePreviewHandler }, + { CLSID_SHIMActivateQoiPreviewHandler, CLSID_QoiPreviewHandler }, { CLSID_SHIMActivateSvgPreviewHandler, CLSID_SvgPreviewHandler }, { CLSID_SHIMActivateSvgThumbnailProvider, CLSID_SvgThumbnailProvider } }); diff --git a/src/modules/previewpane/powerpreview/Resources.resx b/src/modules/previewpane/powerpreview/Resources.resx index f21c3ba477..6703e476ef 100644 --- a/src/modules/previewpane/powerpreview/Resources.resx +++ b/src/modules/previewpane/powerpreview/Resources.resx @@ -192,4 +192,10 @@ Stl Thumbnail Provider + + Qoi Previewer + + + Qoi Thumbnail Provider + \ No newline at end of file diff --git a/src/modules/previewpane/powerpreview/powerpreview.cpp b/src/modules/previewpane/powerpreview/powerpreview.cpp index eade6a3a43..1eef1f0398 100644 --- a/src/modules/previewpane/powerpreview/powerpreview.cpp +++ b/src/modules/previewpane/powerpreview/powerpreview.cpp @@ -71,6 +71,16 @@ PowerPreviewModule::PowerPreviewModule() : .checkModuleGPOEnabledRuleFunction = powertoys_gpo::getConfiguredStlThumbnailsEnabledValue, .registryChanges = getStlThumbnailHandlerChangeSet(installationDir, installPerUser) }); + m_fileExplorerModules.push_back({ .settingName = L"qoi-previewer-toggle-setting", + .settingDescription = GET_RESOURCE_STRING(IDS_PREVPANE_QOI_SETTINGS_DESCRIPTION), + .checkModuleGPOEnabledRuleFunction = powertoys_gpo::getConfiguredQoiPreviewEnabledValue, + .registryChanges = getQoiPreviewHandlerChangeSet(installationDir, installPerUser) }); + + m_fileExplorerModules.push_back({ .settingName = L"qoi-thumbnail-toggle-setting", + .settingDescription = GET_RESOURCE_STRING(IDS_QOI_THUMBNAIL_PROVIDER_SETTINGS_DESCRIPTION), + .checkModuleGPOEnabledRuleFunction = powertoys_gpo::getConfiguredQoiThumbnailsEnabledValue, + .registryChanges = getQoiThumbnailHandlerChangeSet(installationDir, installPerUser) }); + try { PowerToysSettings::PowerToyValues settings = diff --git a/src/modules/videoconference/VideoConferenceModule/Toolbar.cpp b/src/modules/videoconference/VideoConferenceModule/Toolbar.cpp index aac13b3dec..0dabd58d9c 100644 --- a/src/modules/videoconference/VideoConferenceModule/Toolbar.cpp +++ b/src/modules/videoconference/VideoConferenceModule/Toolbar.cpp @@ -109,7 +109,7 @@ LRESULT Toolbar::WindowProcessMessages(HWND hwnd, UINT msg, WPARAM wparam, LPARA } case WM_DPICHANGED: { - UINT dpi = LOWORD(dpi); + UINT dpi = LOWORD(wparam); RECT* prcNewWindow = reinterpret_cast(lparam); POINT suggestedPosition; diff --git a/src/modules/videoconference/VideoConferenceShared/Logging.cpp b/src/modules/videoconference/VideoConferenceShared/Logging.cpp index 45f643fa12..faeab04bb5 100644 --- a/src/modules/videoconference/VideoConferenceShared/Logging.cpp +++ b/src/modules/videoconference/VideoConferenceShared/Logging.cpp @@ -99,8 +99,6 @@ std::string toMediaTypeString(GUID subtype) return "MFVideoFormat_D16"; else if (subtype == MFVideoFormat_AYUV) return "MFVideoFormat_AYUV"; - else if (subtype == MFVideoFormat_YUY2) - return "MFVideoFormat_YUY2"; else if (subtype == MFVideoFormat_YVYU) return "MFVideoFormat_YVYU"; else if (subtype == MFVideoFormat_YVU9) diff --git a/src/runner/settings_window.cpp b/src/runner/settings_window.cpp index 86a6f25c13..a834748986 100644 --- a/src/runner/settings_window.cpp +++ b/src/runner/settings_window.cpp @@ -616,10 +616,10 @@ void close_settings_window() { if (g_settings_process_id != 0) { - HANDLE proc = OpenProcess(PROCESS_TERMINATE, false, g_settings_process_id); - if (proc != INVALID_HANDLE_VALUE) + wil::unique_handle proc{ OpenProcess(PROCESS_TERMINATE, false, g_settings_process_id) }; + if (proc) { - TerminateProcess(proc, 0); + TerminateProcess(proc.get(), 0); } } } diff --git a/src/settings-ui/Directory.Build.targets b/src/settings-ui/Directory.Build.targets index 742af06995..d8e2e4e8ce 100644 --- a/src/settings-ui/Directory.Build.targets +++ b/src/settings-ui/Directory.Build.targets @@ -1,4 +1,5 @@ + diff --git a/src/settings-ui/Settings.UI.Library/PeekPreviewSettings.cs b/src/settings-ui/Settings.UI.Library/PeekPreviewSettings.cs new file mode 100644 index 0000000000..33fc4e270f --- /dev/null +++ b/src/settings-ui/Settings.UI.Library/PeekPreviewSettings.cs @@ -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.Text.Json; +using Microsoft.PowerToys.Settings.UI.Library; +using Microsoft.PowerToys.Settings.UI.Library.Interfaces; + +namespace Settings.UI.Library +{ + public class PeekPreviewSettings : ISettingsConfig + { + public const string FileName = "preview-settings.json"; + + public BoolProperty SourceCodeWrapText { get; set; } + + public BoolProperty SourceCodeTryFormat { get; set; } + + public PeekPreviewSettings() + { + SourceCodeWrapText = new BoolProperty(false); + SourceCodeTryFormat = new BoolProperty(false); + } + + public string ToJsonString() + { + return JsonSerializer.Serialize(this); + } + + public string GetModuleName() + { + return PeekSettings.ModuleName; + } + + public bool UpgradeSettingsConfiguration() + { + return false; + } + } +} diff --git a/src/settings-ui/Settings.UI.Library/PowerPreviewProperties.cs b/src/settings-ui/Settings.UI.Library/PowerPreviewProperties.cs index 1167325cce..d015f049c9 100644 --- a/src/settings-ui/Settings.UI.Library/PowerPreviewProperties.cs +++ b/src/settings-ui/Settings.UI.Library/PowerPreviewProperties.cs @@ -221,6 +221,40 @@ namespace Microsoft.PowerToys.Settings.UI.Library [JsonPropertyName("stl-thumbnail-color-setting")] public StringProperty StlThumbnailColor { get; set; } + private bool enableQoiPreview = true; + + [JsonPropertyName("qoi-previewer-toggle-setting")] + [JsonConverter(typeof(BoolPropertyJsonConverter))] + public bool EnableQoiPreview + { + get => enableQoiPreview; + set + { + if (value != enableQoiPreview) + { + LogTelemetryEvent(value); + enableQoiPreview = value; + } + } + } + + private bool enableQoiThumbnail = true; + + [JsonPropertyName("qoi-thumbnail-toggle-setting")] + [JsonConverter(typeof(BoolPropertyJsonConverter))] + public bool EnableQoiThumbnail + { + get => enableQoiThumbnail; + set + { + if (value != enableQoiThumbnail) + { + LogTelemetryEvent(value); + enableQoiThumbnail = value; + } + } + } + public PowerPreviewProperties() { SvgBackgroundColorMode = new IntProperty(DefaultSvgBackgroundColorMode); diff --git a/src/settings-ui/Settings.UI.Library/Utilities/Helper.cs b/src/settings-ui/Settings.UI.Library/Utilities/Helper.cs index 7c4070b209..4f75ae6a25 100644 --- a/src/settings-ui/Settings.UI.Library/Utilities/Helper.cs +++ b/src/settings-ui/Settings.UI.Library/Utilities/Helper.cs @@ -150,10 +150,5 @@ namespace Microsoft.PowerToys.Settings.UI.Library.Utilities } public const uint VirtualKeyWindows = interop.Constants.VK_WIN_BOTH; - - public static bool Windows11() - { - return Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build >= 22000; - } } } diff --git a/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/PowerPreview.cs b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/PowerPreview.cs index 07869ede8e..f70357293e 100644 --- a/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/PowerPreview.cs +++ b/src/settings-ui/Settings.UI.UnitTests/ViewModelTests/PowerPreview.cs @@ -60,11 +60,13 @@ namespace ViewModelTests Assert.AreEqual(originalSettings.Properties.EnableMonacoPreview, viewModel.MonacoRenderIsEnabled); Assert.AreEqual(originalSettings.Properties.EnablePdfPreview, viewModel.PDFRenderIsEnabled); Assert.AreEqual(originalSettings.Properties.EnableGcodePreview, viewModel.GCODERenderIsEnabled); + Assert.AreEqual(originalSettings.Properties.EnableQoiPreview, viewModel.QOIRenderIsEnabled); Assert.AreEqual(originalSettings.Properties.EnableSvgPreview, viewModel.SVGRenderIsEnabled); Assert.AreEqual(originalSettings.Properties.EnableSvgThumbnail, viewModel.SVGThumbnailIsEnabled); Assert.AreEqual(originalSettings.Properties.EnablePdfThumbnail, viewModel.PDFThumbnailIsEnabled); Assert.AreEqual(originalSettings.Properties.EnableGcodeThumbnail, viewModel.GCODEThumbnailIsEnabled); Assert.AreEqual(originalSettings.Properties.EnableStlThumbnail, viewModel.STLThumbnailIsEnabled); + Assert.AreEqual(originalSettings.Properties.EnableQoiThumbnail, viewModel.QOIThumbnailIsEnabled); // Verify that the stub file was used var expectedCallCount = 2; // once via the view model, and once by the test (GetSettings) @@ -161,6 +163,24 @@ namespace ViewModelTests viewModel.STLThumbnailIsEnabled = true; } + [TestMethod] + public void QOIThumbnailIsEnabledShouldPrevHandlerWhenSuccessful() + { + // Assert + Func sendMockIPCConfigMSG = msg => + { + SndModuleSettings snd = JsonSerializer.Deserialize>(msg); + Assert.IsTrue(snd.PowertoysSetting.FileExplorerPreviewSettings.Properties.EnableQoiThumbnail); + return 0; + }; + + // arrange + PowerPreviewViewModel viewModel = new PowerPreviewViewModel(SettingsRepository.GetInstance(mockPowerPreviewSettingsUtils.Object), SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), sendMockIPCConfigMSG, PowerPreviewSettings.ModuleName); + + // act + viewModel.QOIThumbnailIsEnabled = true; + } + [TestMethod] public void MDRenderIsEnabledShouldPrevHandlerWhenSuccessful() { @@ -232,5 +252,23 @@ namespace ViewModelTests // act viewModel.GCODERenderIsEnabled = true; } + + [TestMethod] + public void QOIRenderIsEnabledShouldPrevHandlerWhenSuccessful() + { + // Assert + Func sendMockIPCConfigMSG = msg => + { + SndModuleSettings snd = JsonSerializer.Deserialize>(msg); + Assert.IsTrue(snd.PowertoysSetting.FileExplorerPreviewSettings.Properties.EnableQoiPreview); + return 0; + }; + + // arrange + PowerPreviewViewModel viewModel = new PowerPreviewViewModel(SettingsRepository.GetInstance(mockPowerPreviewSettingsUtils.Object), SettingsRepository.GetInstance(mockGeneralSettingsUtils.Object), sendMockIPCConfigMSG, PowerPreviewSettings.ModuleName); + + // act + viewModel.QOIRenderIsEnabled = true; + } } } diff --git a/src/settings-ui/Settings.UI/Assets/Settings/FluentIcons/FluentIconsFileExplorerPreview.png b/src/settings-ui/Settings.UI/Assets/Settings/FluentIcons/FluentIconsFileExplorerPreview.png index 5e50452303..654eb194b2 100644 Binary files a/src/settings-ui/Settings.UI/Assets/Settings/FluentIcons/FluentIconsFileExplorerPreview.png and b/src/settings-ui/Settings.UI/Assets/Settings/FluentIcons/FluentIconsFileExplorerPreview.png differ diff --git a/src/settings-ui/Settings.UI/Helpers/ModuleHelper.cs b/src/settings-ui/Settings.UI/Helpers/ModuleHelper.cs new file mode 100644 index 0000000000..25d5fb4f59 --- /dev/null +++ b/src/settings-ui/Settings.UI/Helpers/ModuleHelper.cs @@ -0,0 +1,199 @@ +// 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 global::PowerToys.GPOWrapper; +using ManagedCommon; +using Microsoft.PowerToys.Settings.UI.Library; +using Microsoft.PowerToys.Settings.UI.Views; +using Windows.UI; + +namespace Microsoft.PowerToys.Settings.UI.Helpers +{ + internal sealed class ModuleHelper + { + public static string GetModuleLabelResourceName(ModuleType moduleType) + { + switch (moduleType) + { + case ModuleType.PowerAccent: return "QuickAccent/ModuleTitle"; + case ModuleType.PowerOCR: return "TextExtractor/ModuleTitle"; + case ModuleType.FindMyMouse: + case ModuleType.MouseHighlighter: + case ModuleType.MouseJump: + case ModuleType.MousePointerCrosshairs: return $"MouseUtils_{moduleType}/Header"; + default: return $"{moduleType}/ModuleTitle"; + } + } + + public static string GetModuleTypeFluentIconName(ModuleType moduleType) + { + switch (moduleType) + { + case ModuleType.MousePointerCrosshairs: return "ms-appx:///Assets/Settings/FluentIcons/FluentIconsMouseCrosshairs.png"; + case ModuleType.MeasureTool: return "ms-appx:///Assets/Settings/FluentIcons/FluentIconsScreenRuler.png"; + case ModuleType.PowerLauncher: return $"ms-appx:///Assets/Settings/FluentIcons/FluentIconsPowerToysRun.png"; + default: return $"ms-appx:///Assets/Settings/FluentIcons/FluentIcons{moduleType}.png"; + } + } + + public static bool GetIsModuleEnabled(Library.GeneralSettings generalSettingsConfig, ModuleType moduleType) + { + switch (moduleType) + { + case ModuleType.AlwaysOnTop: return generalSettingsConfig.Enabled.AlwaysOnTop; + case ModuleType.Awake: return generalSettingsConfig.Enabled.Awake; + case ModuleType.ColorPicker: return generalSettingsConfig.Enabled.ColorPicker; + case ModuleType.CropAndLock: return generalSettingsConfig.Enabled.CropAndLock; + case ModuleType.EnvironmentVariables: return generalSettingsConfig.Enabled.EnvironmentVariables; + case ModuleType.FancyZones: return generalSettingsConfig.Enabled.FancyZones; + case ModuleType.FileLocksmith: return generalSettingsConfig.Enabled.FileLocksmith; + case ModuleType.FindMyMouse: return generalSettingsConfig.Enabled.FindMyMouse; + case ModuleType.Hosts: return generalSettingsConfig.Enabled.Hosts; + case ModuleType.ImageResizer: return generalSettingsConfig.Enabled.ImageResizer; + case ModuleType.KeyboardManager: return generalSettingsConfig.Enabled.KeyboardManager; + case ModuleType.MouseHighlighter: return generalSettingsConfig.Enabled.MouseHighlighter; + case ModuleType.MouseJump: return generalSettingsConfig.Enabled.MouseJump; + case ModuleType.MousePointerCrosshairs: return generalSettingsConfig.Enabled.MousePointerCrosshairs; + case ModuleType.MouseWithoutBorders: return generalSettingsConfig.Enabled.MouseWithoutBorders; + case ModuleType.PastePlain: return generalSettingsConfig.Enabled.PastePlain; + case ModuleType.Peek: return generalSettingsConfig.Enabled.Peek; + case ModuleType.PowerRename: return generalSettingsConfig.Enabled.PowerRename; + case ModuleType.PowerLauncher: return generalSettingsConfig.Enabled.PowerLauncher; + case ModuleType.PowerAccent: return generalSettingsConfig.Enabled.PowerAccent; + case ModuleType.RegistryPreview: return generalSettingsConfig.Enabled.RegistryPreview; + case ModuleType.MeasureTool: return generalSettingsConfig.Enabled.MeasureTool; + case ModuleType.ShortcutGuide: return generalSettingsConfig.Enabled.ShortcutGuide; + case ModuleType.PowerOCR: return generalSettingsConfig.Enabled.PowerOCR; + default: return false; + } + } + + internal static void SetIsModuleEnabled(GeneralSettings generalSettingsConfig, ModuleType moduleType, bool isEnabled) + { + switch (moduleType) + { + case ModuleType.AlwaysOnTop: generalSettingsConfig.Enabled.AlwaysOnTop = isEnabled; break; + case ModuleType.Awake: generalSettingsConfig.Enabled.Awake = isEnabled; break; + case ModuleType.ColorPicker: generalSettingsConfig.Enabled.ColorPicker = isEnabled; break; + case ModuleType.CropAndLock: generalSettingsConfig.Enabled.CropAndLock = isEnabled; break; + case ModuleType.EnvironmentVariables: generalSettingsConfig.Enabled.EnvironmentVariables = isEnabled; break; + case ModuleType.FancyZones: generalSettingsConfig.Enabled.FancyZones = isEnabled; break; + case ModuleType.FileLocksmith: generalSettingsConfig.Enabled.FileLocksmith = isEnabled; break; + case ModuleType.FindMyMouse: generalSettingsConfig.Enabled.FindMyMouse = isEnabled; break; + case ModuleType.Hosts: generalSettingsConfig.Enabled.Hosts = isEnabled; break; + case ModuleType.ImageResizer: generalSettingsConfig.Enabled.ImageResizer = isEnabled; break; + case ModuleType.KeyboardManager: generalSettingsConfig.Enabled.KeyboardManager = isEnabled; break; + case ModuleType.MouseHighlighter: generalSettingsConfig.Enabled.MouseHighlighter = isEnabled; break; + case ModuleType.MouseJump: generalSettingsConfig.Enabled.MouseJump = isEnabled; break; + case ModuleType.MousePointerCrosshairs: generalSettingsConfig.Enabled.MousePointerCrosshairs = isEnabled; break; + case ModuleType.MouseWithoutBorders: generalSettingsConfig.Enabled.MouseWithoutBorders = isEnabled; break; + case ModuleType.PastePlain: generalSettingsConfig.Enabled.PastePlain = isEnabled; break; + case ModuleType.Peek: generalSettingsConfig.Enabled.Peek = isEnabled; break; + case ModuleType.PowerRename: generalSettingsConfig.Enabled.PowerRename = isEnabled; break; + case ModuleType.PowerLauncher: generalSettingsConfig.Enabled.PowerLauncher = isEnabled; break; + case ModuleType.PowerAccent: generalSettingsConfig.Enabled.PowerAccent = isEnabled; break; + case ModuleType.RegistryPreview: generalSettingsConfig.Enabled.RegistryPreview = isEnabled; break; + case ModuleType.MeasureTool: generalSettingsConfig.Enabled.MeasureTool = isEnabled; break; + case ModuleType.ShortcutGuide: generalSettingsConfig.Enabled.ShortcutGuide = isEnabled; break; + case ModuleType.PowerOCR: generalSettingsConfig.Enabled.PowerOCR = isEnabled; break; + } + } + + public static GpoRuleConfigured GetModuleGpoConfiguration(ModuleType moduleType) + { + switch (moduleType) + { + case ModuleType.AlwaysOnTop: return GPOWrapper.GetConfiguredAlwaysOnTopEnabledValue(); + case ModuleType.Awake: return GPOWrapper.GetConfiguredAwakeEnabledValue(); + case ModuleType.ColorPicker: return GPOWrapper.GetConfiguredColorPickerEnabledValue(); + case ModuleType.CropAndLock: return GPOWrapper.GetConfiguredCropAndLockEnabledValue(); + case ModuleType.EnvironmentVariables: return GPOWrapper.GetConfiguredEnvironmentVariablesEnabledValue(); + case ModuleType.FancyZones: return GPOWrapper.GetConfiguredFancyZonesEnabledValue(); + case ModuleType.FileLocksmith: return GPOWrapper.GetConfiguredFileLocksmithEnabledValue(); + case ModuleType.FindMyMouse: return GPOWrapper.GetConfiguredFindMyMouseEnabledValue(); + case ModuleType.Hosts: return GPOWrapper.GetConfiguredHostsFileEditorEnabledValue(); + case ModuleType.ImageResizer: return GPOWrapper.GetConfiguredImageResizerEnabledValue(); + case ModuleType.KeyboardManager: return GPOWrapper.GetConfiguredKeyboardManagerEnabledValue(); + case ModuleType.MouseHighlighter: return GPOWrapper.GetConfiguredMouseHighlighterEnabledValue(); + case ModuleType.MouseJump: return GPOWrapper.GetConfiguredMouseJumpEnabledValue(); + case ModuleType.MousePointerCrosshairs: return GPOWrapper.GetConfiguredMousePointerCrosshairsEnabledValue(); + case ModuleType.MouseWithoutBorders: return GPOWrapper.GetConfiguredMouseWithoutBordersEnabledValue(); + case ModuleType.PastePlain: return GPOWrapper.GetConfiguredPastePlainEnabledValue(); + case ModuleType.Peek: return GPOWrapper.GetConfiguredPeekEnabledValue(); + case ModuleType.PowerRename: return GPOWrapper.GetConfiguredPowerRenameEnabledValue(); + case ModuleType.PowerLauncher: return GPOWrapper.GetConfiguredPowerLauncherEnabledValue(); + case ModuleType.PowerAccent: return GPOWrapper.GetConfiguredQuickAccentEnabledValue(); + case ModuleType.RegistryPreview: return GPOWrapper.GetConfiguredRegistryPreviewEnabledValue(); + case ModuleType.MeasureTool: return GPOWrapper.GetConfiguredScreenRulerEnabledValue(); + case ModuleType.ShortcutGuide: return GPOWrapper.GetConfiguredShortcutGuideEnabledValue(); + case ModuleType.PowerOCR: return GPOWrapper.GetConfiguredTextExtractorEnabledValue(); + default: return GpoRuleConfigured.Unavailable; + } + } + + public static Color GetModuleAccentColor(ModuleType moduleType) + { + return moduleType switch + { + ModuleType.AlwaysOnTop => Color.FromArgb(255, 74, 196, 242), // #4ac4f2 + ModuleType.Awake => Color.FromArgb(255, 40, 177, 233), // #28b1e9 + ModuleType.ColorPicker => Color.FromArgb(255, 7, 129, 211), // #0781d3 + ModuleType.CropAndLock => Color.FromArgb(255, 32, 166, 228), // #20a6e4 + ModuleType.EnvironmentVariables => Color.FromArgb(255, 16, 132, 208), // #1084d0 + ModuleType.FancyZones => Color.FromArgb(255, 65, 209, 247), // #41d1f7 + ModuleType.FileLocksmith => Color.FromArgb(255, 245, 161, 20), // #f5a114 + ModuleType.FindMyMouse => Color.FromArgb(255, 104, 109, 112), // #686d70 + ModuleType.Hosts => Color.FromArgb(255, 16, 132, 208), // #1084d0 + ModuleType.ImageResizer => Color.FromArgb(255, 85, 207, 248), // #55cff8 + ModuleType.KeyboardManager => Color.FromArgb(255, 224, 231, 238), // #e0e7ee + ModuleType.MouseHighlighter => Color.FromArgb(255, 17, 126, 199), // #117ec7 + ModuleType.MouseJump => Color.FromArgb(255, 240, 240, 239), // #f0f0ef + ModuleType.MousePointerCrosshairs => Color.FromArgb(255, 25, 115, 182), // #1973b6 + ModuleType.MouseWithoutBorders => Color.FromArgb(255, 31, 164, 227), // #1fa4e3 + ModuleType.PastePlain => Color.FromArgb(255, 243, 156, 16), // #f39c10 + ModuleType.Peek => Color.FromArgb(255, 255, 214, 103), // #ffd667 + ModuleType.PowerRename => Color.FromArgb(255, 43, 186, 243), // #2bbaf3 + ModuleType.PowerLauncher => Color.FromArgb(255, 51, 191, 240), // #33bff0 + ModuleType.PowerAccent => Color.FromArgb(255, 84, 89, 92), // #54595c + ModuleType.RegistryPreview => Color.FromArgb(255, 17, 80, 138), // #11508a + ModuleType.MeasureTool => Color.FromArgb(255, 135, 144, 153), // #879099 + ModuleType.ShortcutGuide => Color.FromArgb(255, 193, 202, 209), // #c1cad1 + ModuleType.PowerOCR => Color.FromArgb(255, 24, 153, 224), // #1899e0 + _ => Color.FromArgb(255, 255, 255, 255), // never called, all values listed above + }; + } + + public static System.Type GetModulePageType(ModuleType moduleType) + { + return moduleType switch + { + ModuleType.AlwaysOnTop => typeof(AlwaysOnTopPage), + ModuleType.Awake => typeof(AwakePage), + ModuleType.ColorPicker => typeof(ColorPickerPage), + ModuleType.CropAndLock => typeof(CropAndLockPage), + ModuleType.EnvironmentVariables => typeof(EnvironmentVariablesPage), + ModuleType.FancyZones => typeof(FancyZonesPage), + ModuleType.FileLocksmith => typeof(FileLocksmithPage), + ModuleType.FindMyMouse => typeof(MouseUtilsPage), + ModuleType.Hosts => typeof(HostsPage), + ModuleType.ImageResizer => typeof(ImageResizerPage), + ModuleType.KeyboardManager => typeof(KeyboardManagerPage), + ModuleType.MouseHighlighter => typeof(MouseUtilsPage), + ModuleType.MouseJump => typeof(MouseUtilsPage), + ModuleType.MousePointerCrosshairs => typeof(MouseUtilsPage), + ModuleType.MouseWithoutBorders => typeof(MouseWithoutBordersPage), + ModuleType.PastePlain => typeof(PastePlainPage), + ModuleType.Peek => typeof(PeekPage), + ModuleType.PowerRename => typeof(PowerRenamePage), + ModuleType.PowerLauncher => typeof(PowerLauncherPage), + ModuleType.PowerAccent => typeof(PowerAccentPage), + ModuleType.RegistryPreview => typeof(RegistryPreviewPage), + ModuleType.MeasureTool => typeof(MeasureToolPage), + ModuleType.ShortcutGuide => typeof(ShortcutGuidePage), + ModuleType.PowerOCR => typeof(PowerOcrPage), + _ => typeof(DashboardPage), // never called, all values listed above + }; + } + } +} diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/AlphaColorPickerButton.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/AlphaColorPickerButton.xaml index 0eaf6916b4..7571434778 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/AlphaColorPickerButton.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/AlphaColorPickerButton.xaml @@ -26,7 +26,7 @@ - + - + 0); } private void EnableKeys() @@ -408,6 +410,10 @@ namespace Microsoft.PowerToys.Settings.UI.Controls c.Keys = null; c.Keys = HotkeySettings.GetKeysList(); + // 92 means the Win key. The logic is: warning should be visible if the shortcut contains Alt AND contains Ctrl AND NOT contains Win. + // Additional key must be present, as this is a valid, previously used shortcut shown at dialog open. Check for presence of non-modifier-key is not necessary therefore + c.IsWarningAltGr = c.Keys.Contains("Ctrl") && c.Keys.Contains("Alt") && !c.Keys.Contains(92); + shortcutDialog.XamlRoot = this.XamlRoot; shortcutDialog.RequestedTheme = this.ActualTheme; await shortcutDialog.ShowAsync(); diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutDialogContentControl.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutDialogContentControl.xaml index 899f08f746..e017777d37 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutDialogContentControl.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Controls/ShortcutControl/ShortcutDialogContentControl.xaml @@ -59,6 +59,12 @@ IsTabStop="{Binding ElementName=ShortcutContentControl, Path=IsError, Mode=OneWay}" Severity="Error" /> + (bool)GetValue(IsWarningAltGrProperty); + set => SetValue(IsWarningAltGrProperty, value); + } + + public static readonly DependencyProperty IsWarningAltGrProperty = DependencyProperty.Register("IsWarningAltGr", typeof(bool), typeof(ShortcutDialogContentControl), new PropertyMetadata(false)); } } diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml.cs index ad4cbb4b45..602a390dd4 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/Flyout/LaunchPage.xaml.cs @@ -5,6 +5,7 @@ using System; using System.Threading; using global::Windows.System; using interop; +using ManagedCommon; using Microsoft.PowerToys.Settings.UI.Controls; using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Settings.UI.Library.Telemetry.Events; @@ -37,16 +38,16 @@ namespace Microsoft.PowerToys.Settings.UI.Flyout // Closing manually the flyout to workaround focus gain problems App.GetFlyoutWindow()?.Hide(); - switch ((string)selectedModuleBtn.Tag) + switch ((ModuleType)selectedModuleBtn.Tag) { - case "ColorPicker": // Launch ColorPicker + case ModuleType.ColorPicker: // Launch ColorPicker using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowColorPickerSharedEvent())) { eventHandle.Set(); } break; - case "EnvironmentVariables": // Launch Environment Variables + case ModuleType.EnvironmentVariables: // Launch Environment Variables { bool launchAdmin = SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Properties.LaunchAdministrator; string eventName = !App.IsElevated && launchAdmin @@ -61,7 +62,7 @@ namespace Microsoft.PowerToys.Settings.UI.Flyout break; - case "FancyZones": // Launch FancyZones Editor + case ModuleType.FancyZones: // Launch FancyZones Editor using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.FZEToggleEvent())) { eventHandle.Set(); @@ -69,7 +70,7 @@ namespace Microsoft.PowerToys.Settings.UI.Flyout break; - case "Hosts": // Launch Hosts + case ModuleType.Hosts: // Launch Hosts { bool launchAdmin = SettingsRepository.GetInstance(new SettingsUtils()).SettingsConfig.Properties.LaunchAdministrator; string eventName = !App.IsElevated && launchAdmin @@ -84,14 +85,14 @@ namespace Microsoft.PowerToys.Settings.UI.Flyout break; - case "RegistryPreview": // Launch Registry Preview + case ModuleType.RegistryPreview: // Launch Registry Preview using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.RegistryPreviewTriggerEvent())) { eventHandle.Set(); } break; - case "MeasureTool": // Launch Screen Ruler + case ModuleType.MeasureTool: // Launch Screen Ruler using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.MeasureToolTriggerEvent())) { eventHandle.Set(); @@ -99,7 +100,7 @@ namespace Microsoft.PowerToys.Settings.UI.Flyout break; - case "PowerLauncher": // Launch Run + case ModuleType.PowerLauncher: // Launch Run using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.PowerLauncherSharedEvent())) { eventHandle.Set(); @@ -107,7 +108,7 @@ namespace Microsoft.PowerToys.Settings.UI.Flyout break; - case "PowerOCR": // Launch Text Extractor + case ModuleType.PowerOCR: // Launch Text Extractor using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShowPowerOCRSharedEvent())) { eventHandle.Set(); @@ -115,7 +116,7 @@ namespace Microsoft.PowerToys.Settings.UI.Flyout break; - case "ShortcutGuide": // Launch Shortcut Guide + case ModuleType.ShortcutGuide: // Launch Shortcut Guide using (var eventHandle = new EventWaitHandle(false, EventResetMode.AutoReset, Constants.ShortcutGuideTriggerEvent())) { eventHandle.Set(); @@ -130,7 +131,7 @@ namespace Microsoft.PowerToys.Settings.UI.Flyout if (moduleRun) { - PowerToysTelemetry.Log.WriteEvent(new TrayFlyoutModuleRunEvent() { ModuleName = (string)selectedModuleBtn.Tag }); + PowerToysTelemetry.Log.WriteEvent(new TrayFlyoutModuleRunEvent() { ModuleName = ((ModuleType)selectedModuleBtn.Tag).ToString() }); } } diff --git a/src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml.cs b/src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml.cs index 78216278ae..603f0f0364 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml.cs +++ b/src/settings-ui/Settings.UI/SettingsXAML/MainWindow.xaml.cs @@ -81,92 +81,15 @@ namespace Microsoft.PowerToys.Settings.UI }); // open main window - ShellPage.SetUpdatingGeneralSettingsCallback((string module, bool isEnabled) => + ShellPage.SetUpdatingGeneralSettingsCallback((ModuleType moduleType, bool isEnabled) => { SettingsRepository repository = SettingsRepository.GetInstance(new SettingsUtils()); GeneralSettings generalSettingsConfig = repository.SettingsConfig; - bool needToUpdate = false; - switch (module) - { - case "AlwaysOnTop": - needToUpdate = generalSettingsConfig.Enabled.AlwaysOnTop != isEnabled; - generalSettingsConfig.Enabled.AlwaysOnTop = isEnabled; break; - case "Awake": - needToUpdate = generalSettingsConfig.Enabled.Awake != isEnabled; - generalSettingsConfig.Enabled.Awake = isEnabled; break; - case "ColorPicker": - needToUpdate = generalSettingsConfig.Enabled.ColorPicker != isEnabled; - generalSettingsConfig.Enabled.ColorPicker = isEnabled; break; - case "CropAndLock": - needToUpdate = generalSettingsConfig.Enabled.CropAndLock != isEnabled; - generalSettingsConfig.Enabled.CropAndLock = isEnabled; break; - case "EnvironmentVariables": - needToUpdate = generalSettingsConfig.Enabled.EnvironmentVariables != isEnabled; - generalSettingsConfig.Enabled.EnvironmentVariables = isEnabled; break; - case "FancyZones": - needToUpdate = generalSettingsConfig.Enabled.FancyZones != isEnabled; - generalSettingsConfig.Enabled.FancyZones = isEnabled; break; - case "FileLocksmith": - needToUpdate = generalSettingsConfig.Enabled.FileLocksmith != isEnabled; - generalSettingsConfig.Enabled.FileLocksmith = isEnabled; break; - case "FindMyMouse": - needToUpdate = generalSettingsConfig.Enabled.FindMyMouse != isEnabled; - generalSettingsConfig.Enabled.FindMyMouse = isEnabled; break; - case "Hosts": - needToUpdate = generalSettingsConfig.Enabled.Hosts != isEnabled; - generalSettingsConfig.Enabled.Hosts = isEnabled; break; - case "ImageResizer": - needToUpdate = generalSettingsConfig.Enabled.ImageResizer != isEnabled; - generalSettingsConfig.Enabled.ImageResizer = isEnabled; break; - case "KeyboardManager": - needToUpdate = generalSettingsConfig.Enabled.KeyboardManager != isEnabled; - generalSettingsConfig.Enabled.KeyboardManager = isEnabled; break; - case "MouseHighlighter": - needToUpdate = generalSettingsConfig.Enabled.MouseHighlighter != isEnabled; - generalSettingsConfig.Enabled.MouseHighlighter = isEnabled; break; - case "MouseJump": - needToUpdate = generalSettingsConfig.Enabled.MouseJump != isEnabled; - generalSettingsConfig.Enabled.MouseJump = isEnabled; break; - case "MousePointerCrosshairs": - needToUpdate = generalSettingsConfig.Enabled.MousePointerCrosshairs != isEnabled; - generalSettingsConfig.Enabled.MousePointerCrosshairs = isEnabled; break; - case "MouseWithoutBorders": - needToUpdate = generalSettingsConfig.Enabled.MouseWithoutBorders != isEnabled; - generalSettingsConfig.Enabled.MouseWithoutBorders = isEnabled; break; - case "PastePlain": - needToUpdate = generalSettingsConfig.Enabled.PastePlain != isEnabled; - generalSettingsConfig.Enabled.PastePlain = isEnabled; break; - case "Peek": - needToUpdate = generalSettingsConfig.Enabled.Peek != isEnabled; - generalSettingsConfig.Enabled.Peek = isEnabled; break; - case "PowerRename": - needToUpdate = generalSettingsConfig.Enabled.PowerRename != isEnabled; - generalSettingsConfig.Enabled.PowerRename = isEnabled; break; - case "PowerLauncher": - needToUpdate = generalSettingsConfig.Enabled.PowerLauncher != isEnabled; - generalSettingsConfig.Enabled.PowerLauncher = isEnabled; break; - case "PowerAccent": - needToUpdate = generalSettingsConfig.Enabled.PowerAccent != isEnabled; - generalSettingsConfig.Enabled.PowerAccent = isEnabled; break; - case "RegistryPreview": - needToUpdate = generalSettingsConfig.Enabled.RegistryPreview != isEnabled; - generalSettingsConfig.Enabled.RegistryPreview = isEnabled; break; - case "MeasureTool": - needToUpdate = generalSettingsConfig.Enabled.MeasureTool != isEnabled; - generalSettingsConfig.Enabled.MeasureTool = isEnabled; break; - case "ShortcutGuide": - needToUpdate = generalSettingsConfig.Enabled.ShortcutGuide != isEnabled; - generalSettingsConfig.Enabled.ShortcutGuide = isEnabled; break; - case "PowerOCR": - needToUpdate = generalSettingsConfig.Enabled.PowerOCR != isEnabled; - generalSettingsConfig.Enabled.PowerOCR = isEnabled; break; - case "VideoConference": - needToUpdate = generalSettingsConfig.Enabled.VideoConference != isEnabled; - generalSettingsConfig.Enabled.VideoConference = isEnabled; break; - } + bool needToUpdate = ModuleHelper.GetIsModuleEnabled(generalSettingsConfig, moduleType) != isEnabled; if (needToUpdate) { + ModuleHelper.SetIsModuleEnabled(generalSettingsConfig, moduleType, isEnabled); var outgoing = new OutGoingGeneralSettings(generalSettingsConfig); this.DispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Normal, () => { diff --git a/src/settings-ui/Settings.UI/SettingsXAML/Styles/Button.xaml b/src/settings-ui/Settings.UI/SettingsXAML/Styles/Button.xaml index 4c470915d9..a64117889c 100644 --- a/src/settings-ui/Settings.UI/SettingsXAML/Styles/Button.xaml +++ b/src/settings-ui/Settings.UI/SettingsXAML/Styles/Button.xaml @@ -1,22 +1,65 @@  - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +