diff --git a/.github/ISSUE_TEMPLATE/bug-report.yaml b/.github/ISSUE_TEMPLATE/bug-report.yaml index 87bb5045a16..a54671d06db 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yaml +++ b/.github/ISSUE_TEMPLATE/bug-report.yaml @@ -41,25 +41,26 @@ body: - type: input attributes: label: Flow Launcher Version - description: Go to "Settings" => "About". - value: v1.8.3 + description: Go to "Settings" => "About". If you are using a prerelease version please append the build number. + placeholder: "Example: 1.11.0" + validations: + required: true - type: input attributes: label: Windows Build Number - description: Run "ver" at CMD (command prompt). - value: 10.0.19043.1288 + description: Run "ver" at CMD (command prompt) or go to Windows Settings -> Systems -> About. + placeholder: "Example: 10.0.19043.1288" + validations: + required: true + - type: textarea id: logs attributes: label: Error Log description: > - Log file place: - - - The latest version place: `%AppData%\FlowLauncher\Logs\\.txt` - - - For portable mode: `%LocalAppData%\FlowLauncher\\UserData\Logs\\.txt` + From flow type 'open log location' and find log file with the corresponding date containing the error info. value: >
diff --git a/.github/actions/spelling/README.md b/.github/actions/spelling/README.md new file mode 100644 index 00000000000..1f699f3de3d --- /dev/null +++ b/.github/actions/spelling/README.md @@ -0,0 +1,17 @@ +# check-spelling/check-spelling configuration + +File | Purpose | Format | Info +-|-|-|- +[dictionary.txt](dictionary.txt) | Replacement dictionary (creating this file will override the default dictionary) | one word per line | [dictionary](https://github.com/check-spelling/check-spelling/wiki/Configuration#dictionary) +[allow.txt](allow.txt) | Add words to the dictionary | one word per line (only letters and `'`s allowed) | [allow](https://github.com/check-spelling/check-spelling/wiki/Configuration#allow) +[reject.txt](reject.txt) | Remove words from the dictionary (after allow) | grep pattern matching whole dictionary words | [reject](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-reject) +[excludes.txt](excludes.txt) | Files to ignore entirely | perl regular expression | [excludes](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-excludes) +[only.txt](only.txt) | Only check matching files (applied after excludes) | perl regular expression | [only](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-only) +[patterns.txt](patterns.txt) | Patterns to ignore from checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns) +[candidate.patterns](candidate.patterns) | Patterns that might be worth adding to [patterns.txt](patterns.txt) | perl regular expression with optional comment block introductions (all matches will be suggested) | [candidates](https://github.com/check-spelling/check-spelling/wiki/Feature:-Suggest-patterns) +[line_forbidden.patterns](line_forbidden.patterns) | Patterns to flag in checked lines | perl regular expression (order matters, first match wins) | [patterns](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-patterns) +[expect.txt](expect.txt) | Expected words that aren't in the dictionary | one word per line (sorted, alphabetically) | [expect](https://github.com/check-spelling/check-spelling/wiki/Configuration#expect) +[advice.md](advice.md) | Supplement for GitHub comment when unrecognized words are found | GitHub Markdown | [advice](https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples%3A-advice) + +Note: you can replace any of these files with a directory by the same name (minus the suffix) +and then include multiple files inside that directory (with that suffix) to merge multiple files together. diff --git a/.github/actions/spelling/advice.md b/.github/actions/spelling/advice.md new file mode 100644 index 00000000000..1004eeaa604 --- /dev/null +++ b/.github/actions/spelling/advice.md @@ -0,0 +1,25 @@ + +
If the flagged items are :exploding_head: false positives + +If items relate to a ... +* binary file (or some other file you wouldn't want to check at all). + + Please add a file path to the `excludes.txt` file matching the containing file. + + File paths are Perl 5 Regular Expressions - you can [test]( +https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your files. + + `^` refers to the file's path from the root of the repository, so `^README\.md$` would exclude [README.md]( +../tree/HEAD/README.md) (on whichever branch you're using). + +* well-formed pattern. + + 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]( +https://www.regexplanet.com/advanced/perl/) yours before committing to verify it will match your lines. + + Note that patterns can't match multiline strings. + +
diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt new file mode 100644 index 00000000000..494d4de9377 --- /dev/null +++ b/.github/actions/spelling/allow.txt @@ -0,0 +1,4 @@ +github +https +ssh +ubuntu diff --git a/.github/actions/spelling/candidate.patterns b/.github/actions/spelling/candidate.patterns new file mode 100644 index 00000000000..d244bb89133 --- /dev/null +++ b/.github/actions/spelling/candidate.patterns @@ -0,0 +1,522 @@ +# 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.*$ + +# patch hunk comments +^\@\@ -\d+(?:,\d+|) \+\d+(?:,\d+|) \@\@ .* +# git index header +index [0-9a-z]{7,40}\.\.[0-9a-z]{7,40} + +# cid urls +(['"])cid:.*?\g{-1} + +# data url in parens +\(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 url +data:[-a-zA-Z=;:/0-9+]*,\S* + +# mailto urls +mailto:[-a-zA-Z=;:/?%&0-9+@.]{3,} + +# magnet urls +magnet:[?=:\w]+ + +# magnet urls +"magnet:[^"]+" + +# obs: +"obs:[^"]*" + +# The `\b` here means a break, it's the fancy way to handle urls, but it makes things harder to read +# In this examples content, I'm using a number of different ways to match things to show various approaches +# asciinema +\basciinema\.org/a/[0-9a-zA-Z]+ + +# apple +\bdeveloper\.apple\.com/[-\w?=/]+ +# Apple music +\bembed\.music\.apple\.com/fr/playlist/usr-share/[-\w.]+ + +# appveyor api +\bci\.appveyor\.com/api/projects/status/[0-9a-z]+ +# appveyor project +\bci\.appveyor\.com/project/(?:[^/\s"]*/){2}builds?/\d+/job/[0-9a-z]+ + +# Amazon + +# Amazon +\bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|) +# AWS S3 +\b\w*\.s3[^.]*\.amazonaws\.com/[-\w/&#%_?:=]* +# AWS execute-api +\b[0-9a-z]{10}\.execute-api\.[-0-9a-z]+\.amazonaws\.com\b +# AWS ELB +\b\w+\.[-0-9a-z]+\.elb\.amazonaws\.com\b +# AWS SNS +\bsns\.[-0-9a-z]+.amazonaws\.com/[-\w/&#%_?:=]* +# AWS VPC +vpc-\w+ + +# While you could try to match `http://` and `https://` by using `s?` in `https?://`, sometimes there +# YouTube url +\b(?:(?:www\.|)youtube\.com|youtu.be)/(?:channel/|embed/|user/|playlist\?list=|watch\?v=|v/|)[-a-zA-Z0-9?&=_%]* +# YouTube music +\bmusic\.youtube\.com/youtubei/v1/browse(?:[?&]\w+=[-a-zA-Z0-9?&=_]*) +# YouTube tag +<\s*youtube\s+id=['"][-a-zA-Z0-9?_]*['"] +# YouTube image +\bimg\.youtube\.com/vi/[-a-zA-Z0-9?&=_]* +# Google Accounts +\baccounts.google.com/[-_/?=.:;+%&0-9a-zA-Z]* +# Google Analytics +\bgoogle-analytics\.com/collect.[-0-9a-zA-Z?%=&_.~]* +# Google APIs +\bgoogleapis\.(?:com|dev)/[a-z]+/(?:v\d+/|)[a-z]+/[-@:./?=\w+|&]+ +# Google Storage +\b[-a-zA-Z0-9.]*\bstorage\d*\.googleapis\.com(?:/\S*|) +# Google Calendar +\bcalendar\.google\.com/calendar(?:/u/\d+|)/embed\?src=[@./?=\w&%]+ +\w+\@group\.calendar\.google\.com\b +# Google DataStudio +\bdatastudio\.google\.com/(?:(?:c/|)u/\d+/|)(?:embed/|)(?:open|reporting|datasources|s)/[-0-9a-zA-Z]+(?:/page/[-0-9a-zA-Z]+|) +# The leading `/` here is as opposed to the `\b` above +# ... a short way to match `https://` or `http://` since most urls have one of those prefixes +# Google Docs +/docs\.google\.com/[a-z]+/(?:ccc\?key=\w+|(?:u/\d+|d/(?:e/|)[0-9a-zA-Z_-]+/)?(?:edit\?[-\w=#.]*|/\?[\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]+)* +# Google Maps +\bmaps\.google\.com/maps\?[\w&;=]* +# Google themes +themes\.googleusercontent\.com/static/fonts/[^/\s"]+/v\d+/[^.]+. +# Google CDN +\bclients2\.google(?:usercontent|)\.com[-0-9a-zA-Z/.]* +# Goo.gl +/goo\.gl/[a-zA-Z0-9]+ +# Google Chrome Store +\bchrome\.google\.com/webstore/detail/[-\w]*(?:/\w*|) +# Google Books +\bgoogle\.(?:\w{2,4})/books(?:/\w+)*\?[-\w\d=&#.]* +# Google Fonts +\bfonts\.(?:googleapis|gstatic)\.com/[-/?=:;+&0-9a-zA-Z]* +# Google Forms +\bforms\.gle/\w+ +# Google Scholar +\bscholar\.google\.com/citations\?user=[A-Za-z0-9_]+ +# Google Colab Research Drive +\bcolab\.research\.google\.com/drive/[-0-9a-zA-Z_?=]* + +# GitHub SHAs (api) +\bapi.github\.com/repos(?:/[^/\s"]+){3}/[0-9a-f]+\b +# GitHub SHAs (markdown) +(?:\[`?[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 wiki +\bgithub\.com/(?:[^/]+/){2}wiki/(?:(?:[^/]+/|)_history|[^/]+(?:/_compare|)/[0-9a-f.]{40,})\b +# githubusercontent +/[-a-z0-9]+\.githubusercontent\.com/[-a-zA-Z0-9?&=_\/.]* +# githubassets +\bgithubassets.com/[0-9a-f]+(?:[-/\w.]+) +# gist github +\bgist\.github\.com/[^/\s"]+/[0-9a-f]+ +# git.io +\bgit\.io/[0-9a-zA-Z]+ +# GitHub JSON +"node_id": "[-a-zA-Z=;:/0-9+]*" +# Contributor +\[[^\]]+\]\(https://github\.com/[^/\s"]+\) +# GHSA +GHSA(?:-[0-9a-z]{4}){3} + +# GitLab commit +\bgitlab\.[^/\s"]*/\S+/\S+/commit/[0-9a-f]{7,16}#[0-9a-f]{40}\b +# GitLab merge requests +\bgitlab\.[^/\s"]*/\S+/\S+/-/merge_requests/\d+/diffs#[0-9a-f]{40}\b +# GitLab uploads +\bgitlab\.[^/\s"]*/uploads/[-a-zA-Z=;:/0-9+]* +# GitLab commits +\bgitlab\.[^/\s"]*/(?:[^/\s"]+/){2}commits?/[0-9a-f]+\b + +# binanace +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]+ +# bitbucket repositories commits +\bapi\.bitbucket\.org/\d+\.\d+/repositories/(?:[^/\s"]+/){2}commits?/[0-9a-f]+ +# bitbucket commits +\bbitbucket\.org/(?:[^/\s"]+/){2}commits?/[0-9a-f]+ + +# bit.ly +\bbit\.ly/\w+ + +# bitrise +\bapp\.bitrise\.io/app/[0-9a-f]*/[\w.?=&]* + +# bootstrapcdn.com +\bbootstrapcdn\.com/[-./\w]+ + +# cdn.cloudflare.com +\bcdnjs\.cloudflare\.com/[./\w]+ + +# circleci +\bcircleci\.com/gh(?:/[^/\s"]+){1,5}.[a-z]+\?[-0-9a-zA-Z=&]+ + +# gitter +\bgitter\.im(?:/[^/\s"]+){2}\?at=[0-9a-f]+ + +# gravatar +\bgravatar\.com/avatar/[0-9a-f]+ + +# ibm +[a-z.]*ibm\.com/[-_#=:%!?~.\\/\d\w]* + +# imgur +\bimgur\.com/[^.]+ + +# Internet Archive +\barchive\.org/web/\d+/(?:[-\w.?,'/\\+&%$#_:]*) + +# discord +/discord(?:app\.com|\.gg)/(?:invite/)?[a-zA-Z0-9]{7,} + +# Disqus +\bdisqus\.com/[-\w/%.()!?&=_]* + +# medium link +\blink\.medium\.com/[a-zA-Z0-9]+ +# medium +\bmedium\.com/\@?[^/\s"]+/[-\w]+ + +# microsoft +\b(?:https?://|)(?:(?:download\.visualstudio|docs|msdn2?|research)\.microsoft|blogs\.msdn)\.com/[-_a-zA-Z0-9()=./%]* +# powerbi +\bapp\.powerbi\.com/reportEmbed/[^"' ]* +# vs devops +\bvisualstudio.com(?::443|)/[-\w/?=%&.]* +# microsoft store +\bmicrosoft\.com/store/apps/\w+ + +# mvnrepository.com +\bmvnrepository\.com/[-0-9a-z./]+ + +# now.sh +/[0-9a-z-.]+\.now\.sh\b + +# oracle +\bdocs\.oracle\.com/[-0-9a-zA-Z./_?#&=]* + +# chromatic.com +/\S+.chromatic.com\S*[")] + +# codacy +\bapi\.codacy\.com/project/badge/Grade/[0-9a-f]+ + +# compai +\bcompai\.pub/v1/png/[0-9a-f]+ + +# mailgun api +\.api\.mailgun\.net/v3/domains/[0-9a-z]+\.mailgun.org/messages/[0-9a-zA-Z=@]* +# mailgun +\b[0-9a-z]+.mailgun.org + +# /message-id/ +/message-id/[-\w@./%]+ + +# Reddit +\breddit\.com/r/[/\w_]* + +# requestb.in +\brequestb\.in/[0-9a-z]+ + +# sched +\b[a-z0-9]+\.sched\.com\b + +# Slack url +slack://[a-zA-Z0-9?&=]+ +# Slack +\bslack\.com/[-0-9a-zA-Z/_~?&=.]* +# Slack edge +\bslack-edge\.com/[-a-zA-Z0-9?&=%./]+ +# Slack images +\bslack-imgs\.com/[-a-zA-Z0-9?&=%.]+ + +# shields.io +\bshields\.io/[-\w/%?=&.:+;,]* + +# stackexchange -- https://stackexchange.com/feeds/sites +\b(?:askubuntu|serverfault|stack(?:exchange|overflow)|superuser).com/(?:questions/\w+/[-\w]+|a/) + +# Sentry +[0-9a-f]{32}\@o\d+\.ingest\.sentry\.io\b + +# Twitter markdown +\[\@[^[/\]:]*?\]\(https://twitter.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|)\) +# Twitter hashtag +\btwitter\.com/hashtag/[\w?_=&]* +# Twitter status +\btwitter\.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|) +# Twitter profile images +\btwimg\.com/profile_images/[_\w./]* +# Twitter media +\btwimg\.com/media/[-_\w./?=]* +# Twitter link shortened +\bt\.co/\w+ + +# facebook +\bfburl\.com/[0-9a-z_]+ +# facebook CDN +\bfbcdn\.net/[\w/.,]* +# facebook watch +\bfb\.watch/[0-9A-Za-z]+ + +# dropbox +\bdropbox\.com/sh?/[^/\s"]+/[-0-9A-Za-z_.%?=&;]+ + +# ipfs protocol +ipfs://[0-9a-z]* +# ipfs url +/ipfs/[0-9a-z]* + +# w3 +\bw3\.org/[-0-9a-zA-Z/#.]+ + +# loom +\bloom\.com/embed/[0-9a-f]+ + +# regex101 +\bregex101\.com/r/[^/\s"]+/\d+ + +# figma +\bfigma\.com/file(?:/[0-9a-zA-Z]+/)+ + +# freecodecamp.org +\bfreecodecamp\.org/[-\w/.]+ + +# image.tmdb.org +\bimage\.tmdb\.org/[/\w.]+ + +# mermaid +\bmermaid\.ink/img/[-\w]+|\bmermaid-js\.github\.io/mermaid-live-editor/#/edit/[-\w]+ + +# Wikipedia +\ben\.wikipedia\.org/wiki/[-\w%.#]+ + +# gitweb +[^"\s]+/gitweb/\S+;h=[0-9a-f]+ + +# HyperKitty lists +/archives/list/[^@/]+\@[^/\s"]*/message/[^/\s"]*/ + +# lists +/thread\.html/[^"\s]+ + +# list-management +\blist-manage\.com/subscribe(?:[?&](?:u|id)=[0-9a-f]+)+ + +# kubectl.kubernetes.io/last-applied-configuration +"kubectl.kubernetes.io/last-applied-configuration": ".*" + +# pgp +\bgnupg\.net/pks/lookup[?&=0-9a-zA-Z]* + +# Spotify +\bopen\.spotify\.com/embed/playlist/\w+ + +# Mastodon +\bmastodon\.[-a-z.]*/(?:media/|\@)[?&=0-9a-zA-Z_]* + +# scastie +\bscastie\.scala-lang\.org/[^/]+/\w+ + +# images.unsplash.com +\bimages\.unsplash\.com/(?:(?:flagged|reserve)/|)[-\w./%?=%&.;]+ + +# pastebin +\bpastebin\.com/[\w/]+ + +# heroku +\b\w+\.heroku\.com/source/archive/\w+ + +# quip +\b\w+\.quip\.com/\w+(?:(?:#|/issues/)\w+)? + +# badgen.net +\bbadgen\.net/badge/[^")\]'\s]+ + +# statuspage.io +\w+\.statuspage\.io\b + +# media.giphy.com +\bmedia\.giphy\.com/media/[^/]+/[\w.?&=]+ + +# tinyurl +\btinyurl\.com/\w+ + +# getopts +\bgetopts\s+(?:"[^"]+"|'[^']+') + +# ANSI color codes +(?:\\(?:u00|x)1b|\x1b)\[\d+(?:;\d+|)m + +# URL escaped characters +\%[0-9A-F][A-F] +# 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) +0x[0-9a-fA-F](?:\.[0-9a-fA-F]*|)[pP] +# Punycode +\bxn--[-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} +# hex runs +\b[0-9a-fA-F]{16,}\b +# hex in url queries +=[0-9a-fA-F]*?(?:[A-F]{3,}|[a-f]{3,})[0-9a-fA-F]*?& +# ssh +(?:ssh-\S+|-nistp256) [-a-zA-Z=;:/0-9+]{12,} + +# PGP +\b(?:[0-9A-F]{4} ){9}[0-9A-F]{4}\b +# GPG keys +\b(?:[0-9A-F]{4} ){5}(?: [0-9A-F]{4}){5}\b +# Well known gpg keys +.well-known/openpgpkey/[\w./]+ + +# 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 +# integrity +integrity="sha\d+-[-a-zA-Z=;:/0-9+]{40,}" + +# https://www.gnu.org/software/groff/manual/groff.html +# man troff content +\\f[BCIPR] +# ' +\\\(aq + +# .desktop mime types +^MimeTypes?=.*$ +# .desktop localized entries +^[A-Z][a-z]+\[[a-z]+\]=.*$ +# Localized .desktop content +Name\[[^\]]+\]=.* + +# IServiceProvider +\bI(?=(?:[A-Z][a-z]{2,})+\b) + +# crypt +"\$2[ayb]\$.{56}" + +# scrypt / argon +\$(?:scrypt|argon\d+[di]*)\$\S+ + +# Input to GitHub JSON +content: "[-a-zA-Z=;:/0-9+]*=" + +# Python stringprefix / binaryprefix +# 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 +(?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 (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 +\bset(?:\s+-[abefimouxE]{1,2})*\s+-[abefimouxE]{3,}(?:\s+-[abefimouxE]+)* +# tar arguments +\b(?:\\n|)g?tar(?:\.exe|)(?:(?:\s+--[-a-zA-Z]+|\s+-[a-zA-Z]+|\s[ABGJMOPRSUWZacdfh-pr-xz]+\b)(?:=[^ ]*|))+ +# tput arguments -- https://man7.org/linux/man-pages/man5/terminfo.5.html -- technically they can be more than 5 chars long... +\btput\s+(?:(?:-[SV]|-T\s*\w+)\s+)*\w{3,5}\b +# macOS temp folders +/var/folders/\w\w/[+\w]+/(?:T|-Caches-)/ diff --git a/.github/actions/spelling/excludes.txt b/.github/actions/spelling/excludes.txt new file mode 100644 index 00000000000..224014ebac5 --- /dev/null +++ b/.github/actions/spelling/excludes.txt @@ -0,0 +1,73 @@ +# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-excludes +(?:^|/)(?i)COPYRIGHT +(?:^|/)(?i)LICEN[CS]E +(?:^|/)3rdparty/ +(?:^|/)go\.sum$ +(?:^|/)package(?:-lock|)\.json$ +(?:^|/)vendor/ +\.a$ +\.ai$ +\.avi$ +\.bmp$ +\.bz2$ +\.class$ +\.crt$ +\.dll$ +\.docx?$ +\.drawio$ +\.DS_Store$ +\.eot$ +\.exe$ +\.gif$ +\.gitattributes$ +\.gitignore$ +\.graffle$ +\.gz$ +\.icns$ +\.ico$ +\.jar$ +\.jks$ +\.jpe?g$ +\.key$ +\.lib$ +\.lock$ +\.map$ +\.min\.. +\.mod$ +\.mp[34]$ +\.o$ +\.ocf$ +\.otf$ +\.pdf$ +\.pem$ +\.png$ +\.psd$ +\.pyc$ +\.s$ +\.svgz?$ +\.tar$ +\.tiff?$ +\.ttf$ +\.wav$ +\.webm$ +\.webp$ +\.woff2?$ +\.xlsx?$ +\.zip$ +^\.github/actions/spelling/ +^\Q.github/workflows/spelling.yml\E$ +# Custom +(?:^|/)Languages/(?!en\.xaml) +Scripts/ +\.resx$ +^\QPlugins/Flow.Launcher.Plugin.WindowsSettings/WindowsSettings.json\E$ +^\QPlugins/Flow.Launcher.Plugin.WebSearch/setting.json\E$ +(?:^|/)FodyWeavers\.xml +.editorconfig +ignore$ +\.ps1$ +\.yml$ +\.sln$ +\.csproj$ +\.DotSettings$ +\.targets$ diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt new file mode 100644 index 00000000000..0df5228ba4a --- /dev/null +++ b/.github/actions/spelling/expect.txt @@ -0,0 +1,90 @@ +crowdin +DWM +workflows +wpf +actionkeyword +stackoverflow +Wox +flowlauncher +Fody +stackoverflow +IContext +IShell +IPlugin +appveyor +netflix +youtube +appdata +Prioritise +Segoe +Google +Customise +UWP +uwp +Bokmal +Bokm +uninstallation +uninstalling +voidtools +fullscreen +hotkeys +totalcmd +lnk +amazonaws +mscorlib +pythonw +dotnet +winget +jjw24 +wolframalpha +gmail +duckduckgo +facebook +findicon +baidu +pls +websearch +qianlifeng +userdata +srchadmin +EWX +dlgtext +CMD +appref-ms +appref +TSource +runas +dpi +popup +ptr +pluginindicator +TobiasSekan +Img +img +resx +bak +tmp +directx +mvvm +dlg +ddd +dddd +clearlogfolder +ACCENT_ENABLE_TRANSPARENTGRADIENT +ACCENT_ENABLE_BLURBEHIND +WCA_ACCENT_POLICY +HGlobal +dopusrt +firefox +msedge +svgc +ime +zindex +txb +btn +otf +searchplugin +Noresult +wpftk +mkv +flac diff --git a/.github/actions/spelling/line_forbidden.patterns b/.github/actions/spelling/line_forbidden.patterns new file mode 100644 index 00000000000..7341d9b7363 --- /dev/null +++ b/.github/actions/spelling/line_forbidden.patterns @@ -0,0 +1,62 @@ +# reject `m_data` as there's a certain OS which has evil defines that break things if it's used elsewhere +# \bm_data\b + +# If you have a framework that uses `it()` for testing and `fit()` for debugging a specific test, +# you might not want to check in code where you were debugging w/ `fit()`, in which case, you might want +# to use this: +#\bfit\( + +# s.b. GitHub +#\bGithub\b + +# s.b. GitLab +\bGitlab\b + +# s.b. JavaScript +\bJavascript\b + +# s.b. Microsoft +\bMicroSoft\b + +# s.b. another +\ban[- ]other\b + +# s.b. greater than +\bgreater then\b + +# s.b. into +\sin to\s + +# s.b. opt-in +\sopt in\s + +# s.b. less than +\bless then\b + +# s.b. otherwise +\bother[- ]wise\b + +# s.b. nonexistent +\bnon existing\b +\b[Nn]o[nt][- ]existent\b + +# s.b. preexisting +[Pp]re[- ]existing + +# s.b. preempt +[Pp]re[- ]empt\b + +# s.b. preemptively +[Pp]re[- ]emptively + +# s.b. reentrancy +[Rr]e[- ]entrancy + +# s.b. reentrant +[Rr]e[- ]entrant + +# s.b. workaround(s) +\bwork[- ]arounds?\b + +# Reject duplicate words +\s([A-Z]{3,}|[A-Z][a-z]{2,}|[a-z]{3,})\s\g{-1}\s diff --git a/.github/actions/spelling/patterns.txt b/.github/actions/spelling/patterns.txt new file mode 100644 index 00000000000..2f4ff418da0 --- /dev/null +++ b/.github/actions/spelling/patterns.txt @@ -0,0 +1,117 @@ +# See https://github.com/check-spelling/check-spelling/wiki/Configuration-Examples:-patterns + +# 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 + +# acceptable duplicates +# ls directory listings +[-bcdlpsw](?:[-r][-w][-sx]){3}\s+\d+\s+(\S+)\s+\g{-1}\s+\d+\s+ +# C types and repeated CSS values +\s(center|div|inherit|long|LONG|none|normal|solid|thin|transparent|very)(?: \g{-1})+\s +# 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 + +# Commit message -- Signed-off-by and friends +^\s*(?:(?:Based-on-patch|Co-authored|Helped|Mentored|Reported|Reviewed|Signed-off)-by|Thanks-to): (?:[^<]*<[^>]*>|[^<]*)\s*$ + +# Autogenerated revert commit message +^This reverts commit [0-9a-f]{40}\.$ + +# ignore long runs of a single character: +\b([A-Za-z])\g{-1}{3,}\b + +# Automatically suggested patterns +# hit-count: 360 file-count: 108 +# IServiceProvider +\bI(?=(?:[A-Z][a-z]{2,})+\b) + +# hit-count: 297 file-count: 18 +# uuid: +\b[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\b + +# hit-count: 138 file-count: 27 +# 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 + +# hit-count: 93 file-count: 28 +# hex runs +\b[0-9a-fA-F]{16,}\b + +# hit-count: 52 file-count: 3 +# githubusercontent +/[-a-z0-9]+\.githubusercontent\.com/[-a-zA-Z0-9?&=_\/.]* + +/[-a-z0-9]+\.github\.com/[-a-zA-Z0-9?&=_\/.]* + +# hit-count: 24 file-count: 12 +# GitHub SHAs (markdown) +(?:\[`?[0-9a-f]+`?\]\(https:/|)/(?:www\.|)github\.com(?:/[^/\s"]+){2,}(?:/[^/\s")]+)(?:[0-9a-f]+(?:[-0-9a-zA-Z/#.]*|)\b|) + +# hit-count: 11 file-count: 10 +# stackexchange -- https://stackexchange.com/feeds/sites +\b(?:askubuntu|serverfault|stack(?:exchange|overflow)|superuser).com/(?:questions/\w+/[-\w]+|a/) + +# hit-count: 11 file-count: 8 +# microsoft +\b(?:https?://|)(?:(?:download\.visualstudio|docs|msdn2?|research)\.microsoft|blogs\.msdn)\.com/[-_a-zA-Z0-9()=./%]* + +# hit-count: 2 file-count: 2 +# Twitter status +\btwitter\.com/[^/\s"')]*(?:/status/\d+(?:\?[-_0-9a-zA-Z&=]*|)|) + +# hit-count: 2 file-count: 1 +# discord +/discord(?:app\.com|\.gg)/(?:invite/)?[a-zA-Z0-9]{7,} + +# hit-count: 1 file-count: 1 +# appveyor api +\bci\.appveyor\.com/api/projects/status/[0-9a-z]+ + +# hit-count: 1 file-count: 1 +# gist github +\bgist\.github\.com/[^/\s"]+/[0-9a-f]+ + +# Automatically suggested patterns +# hit-count: 21 file-count: 21 +# w3 +\bw3\.org/[-0-9a-zA-Z/#.]+ + +# hit-count: 6 file-count: 1 +# shields.io +\bshields\.io/[-\w/%?=&.:+;,]* + +# hit-count: 3 file-count: 2 +# While you could try to match `http://` and `https://` by using `s?` in `https?://`, sometimes there +# YouTube url +\b(?:(?:www\.|)youtube\.com|youtu.be)/(?:channel/|embed/|user/|playlist\?list=|watch\?v=|v/|)[-a-zA-Z0-9?&=_%]* + +# hit-count: 2 file-count: 2 +# Google Maps +\bmaps\.google\.com/maps\?[\w&;=]* + +# hit-count: 2 file-count: 2 +# Contributor +\[[^\]]+\]\(https://github\.com/[^/\s"]+\) + +# hit-count: 2 file-count: 1 +# URL escaped characters +\%[0-9A-F][A-F] + +# hit-count: 1 file-count: 1 +# Compiler flags +(?:^|[\t ,"'`=(])-[DPWXYLlf](?=[A-Z]{2,}|[A-Z][a-z]|[a-z]{2,}) + +# Localization keys +#x:Key="[^"]+" +#{DynamicResource [^"]+} + +# html tag +<\w+[^>]*> +]*> + +#http/https +(?:\b(?:https?|ftp|file)://)[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|] diff --git a/.github/actions/spelling/reject.txt b/.github/actions/spelling/reject.txt new file mode 100644 index 00000000000..b5a6d36809f --- /dev/null +++ b/.github/actions/spelling/reject.txt @@ -0,0 +1,10 @@ +^attache$ +benefitting +occurences? +^dependan.* +^oer$ +Sorce +^[Ss]pae.* +^untill$ +^untilling$ +^wether.* diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml new file mode 100644 index 00000000000..df962a67552 --- /dev/null +++ b/.github/workflows/spelling.yml @@ -0,0 +1,160 @@ +name: Check Spelling + +# Comment management is handled through a secondary job, for details see: +# 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 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 +# or collapse a comment (in the case where it had previously made a comment and now no longer needs to show a comment) +# it needs `pull-requests: write` in order to manipulate those comments. + +# Updating pull request branches is managed via comment handling. +# For details, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Update-expect-list +# +# These elements work together to make it happen: +# +# `on.issue_comment` +# This event listens to comments by users asking to update the metadata. +# +# `jobs.update` +# This job runs in response to an issue_comment and will push a new commit +# to update the spelling metadata. +# +# `with.experimental_apply_changes_via_bot` +# Tells the action to support and generate messages that enable it +# to make a commit to update the spelling metadata. +# +# `with.ssh_key` +# In order to trigger workflows when the commit is made, you can provide a +# secret (typically, a write-enabled github deploy key). +# +# For background, see: https://github.com/check-spelling/check-spelling/wiki/Feature:-Update-with-deploy-key + +on: +# push: +# branches: +# - '**' +# - '!l10n_dev' +# tags-ignore: +# - "**" + pull_request_target: + branches: + - '**' + # - '!l10n_dev' + tags-ignore: + - "**" + types: + - 'opened' + - 'reopened' + - 'synchronize' + # issue_comment: + # types: + # - 'created' + +jobs: + spelling: + name: Check Spelling + permissions: + 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.head_ref != 'l10n_dev') + 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 + cancel-in-progress: false + steps: + - name: check-spelling + id: spelling + uses: check-spelling/check-spelling@main + with: + suppress_push_for_open_pull_request: 1 + checkout: true + check_file_names: 1 + spell_check_this: check-spelling/spell-check-this@main + post_comment: 0 + use_magic_file: 1 + experimental_apply_changes_via_bot: 1 + use_sarif: 0 # to show in pr page + extra_dictionary_limit: 10 + check_commit_messages: commits title description + only_check_changed_files: 1 + check_extra_dictionaries: '' + quit_without_error: true + extra_dictionaries: + cspell:software-terms/src/software-terms.txt + cspell:win32/src/win32.txt + cspell:php/php.txt + cspell:filetypes/filetypes.txt + cspell:csharp/csharp.txt + cspell:dotnet/dotnet.txt + cspell:python/src/python/python-lib.txt + cspell:aws/aws.txt + cspell:companies/src/companies.txt + warnings: + binary-file,deprecated-feature,large-file,limited-references,noisy-file,non-alpha-in-dictionary,unexpected-line-ending,whitespace-in-dictionary,minified-file,unsupported-configuration,unrecognized-spelling,no-newline-at-eof + + + +# comment-push: +# name: Report (Push) +# # If your workflow isn't running on push, you can remove this job +# runs-on: ubuntu-latest +# needs: spelling +# permissions: +# contents: write +# if: (success() || failure()) && needs.spelling.outputs.followup && github.event_name == 'push' +# steps: +# - name: comment +# uses: check-spelling/check-spelling@main +# with: +# checkout: true +# spell_check_this: check-spelling/spell-check-this@main +# task: ${{ needs.spelling.outputs.followup }} + + comment-pr: + name: Report (PR) + # If you workflow isn't running on pull_request*, you can remove this job + runs-on: ubuntu-latest + needs: spelling + permissions: + pull-requests: write + if: (success() || failure()) && needs.spelling.outputs.followup && contains(github.event_name, 'pull_request') + steps: + - name: comment + uses: check-spelling/check-spelling@main + with: + checkout: true + spell_check_this: check-spelling/spell-check-this@main + task: ${{ needs.spelling.outputs.followup }} + experimental_apply_changes_via_bot: 1 + + # update: + # name: Update PR + # permissions: + # contents: write + # pull-requests: write + # actions: read + # runs-on: ubuntu-latest + # if: ${{ + # 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@main + # with: + # experimental_apply_changes_via_bot: 1 + # checkout: true + # ssh_key: "${{ secrets.CHECK_SPELLING }}" diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 052e1d225f3..5ec4b82c6e7 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -13,7 +13,7 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@v4 + - uses: actions/stale@v7 with: stale-issue-message: 'This issue is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 5 days.' days-before-stale: 45 diff --git a/Flow.Launcher.Core/Configuration/Portable.cs b/Flow.Launcher.Core/Configuration/Portable.cs index bd77ea7cf08..b58154dcb23 100644 --- a/Flow.Launcher.Core/Configuration/Portable.cs +++ b/Flow.Launcher.Core/Configuration/Portable.cs @@ -47,7 +47,7 @@ public void DisablePortableMode() } catch (Exception e) { - Log.Exception("|Portable.DisablePortableMode|Error occured while disabling portable mode", e); + Log.Exception("|Portable.DisablePortableMode|Error occurred while disabling portable mode", e); } } @@ -71,7 +71,7 @@ public void EnablePortableMode() } catch (Exception e) { - Log.Exception("|Portable.EnablePortableMode|Error occured while enabling portable mode", e); + Log.Exception("|Portable.EnablePortableMode|Error occurred while enabling portable mode", e); } } @@ -187,7 +187,7 @@ public bool CanUpdatePortability() if (roamingLocationExists && portableLocationExists) { MessageBox.Show(string.Format("Flow Launcher detected your user data exists both in {0} and " + - "{1}. {2}{2}Please delete {1} in order to proceed. No changes have occured.", + "{1}. {2}{2}Please delete {1} in order to proceed. No changes have occurred.", DataLocation.PortableDataPath, DataLocation.RoamingDataPath, Environment.NewLine)); return false; diff --git a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs index 28d57501baa..f3b2eed87bc 100644 --- a/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs +++ b/Flow.Launcher.Core/Plugin/JsonRPCPlugin.cs @@ -259,9 +259,16 @@ protected async Task ExecuteAsync(ProcessStartInfo startInfo, Cancellati await using var registeredEvent = token.Register(() => { - if (!process.HasExited) - process.Kill(); - sourceBuffer.Dispose(); + try + { + if (!process.HasExited) + process.Kill(); + sourceBuffer.Dispose(); + } + catch (Exception e) + { + Log.Exception("|JsonRPCPlugin.ExecuteAsync|Exception when kill process", e); + } }); try @@ -288,7 +295,7 @@ protected async Task ExecuteAsync(ProcessStartInfo startInfo, Cancellati } sourceBuffer.Seek(0, SeekOrigin.Begin); - + return sourceBuffer; } @@ -376,7 +383,8 @@ public Control CreateSettingPanel() sep.SetResourceReference(Separator.BackgroundProperty, "Color03B"); /* for theme change */ var panel = new StackPanel { - Orientation = Orientation.Vertical, VerticalAlignment = VerticalAlignment.Center, + Orientation = Orientation.Vertical, + VerticalAlignment = VerticalAlignment.Center, Margin = settingLabelPanelMargin }; RowDefinition gridRow = new RowDefinition(); @@ -390,8 +398,10 @@ public Control CreateSettingPanel() }; var desc = new TextBlock() { - Text = attribute.Description, FontSize = 12, - VerticalAlignment = VerticalAlignment.Center,Margin = settingDescMargin, + Text = attribute.Description, + FontSize = 12, + VerticalAlignment = VerticalAlignment.Center, + Margin = settingDescMargin, TextWrapping = TextWrapping.WrapWithOverflow }; desc.SetResourceReference(TextBlock.ForegroundProperty, "Color04B"); @@ -405,7 +415,7 @@ public Control CreateSettingPanel() panel.Children.Add(name); panel.Children.Add(desc); } - + Grid.SetColumn(panel, 0); Grid.SetRow(panel, rowCount); @@ -420,20 +430,20 @@ public Control CreateSettingPanel() { Text = attribute.Description.Replace("\\r\\n", "\r\n"), Margin = settingTextBlockMargin, - Padding = new Thickness(0,0,0,0), + Padding = new Thickness(0, 0, 0, 0), HorizontalAlignment = System.Windows.HorizontalAlignment.Left, TextAlignment = TextAlignment.Left, TextWrapping = TextWrapping.Wrap }; - Grid.SetColumn(contentControl, 0); - Grid.SetColumnSpan(contentControl, 2); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - break; + Grid.SetColumn(contentControl, 0); + Grid.SetColumnSpan(contentControl, 2); + Grid.SetRow(contentControl, rowCount); + if (rowCount != 0) + mainPanel.Children.Add(sep); + Grid.SetRow(sep, rowCount); + Grid.SetColumn(sep, 0); + Grid.SetColumnSpan(sep, 2); + break; } case "input": { @@ -449,50 +459,49 @@ public Control CreateSettingPanel() Settings[attribute.Name] = textBox.Text; }; contentControl = textBox; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - break; + Grid.SetColumn(contentControl, 1); + Grid.SetRow(contentControl, rowCount); + if (rowCount != 0) + mainPanel.Children.Add(sep); + Grid.SetRow(sep, rowCount); + Grid.SetColumn(sep, 0); + Grid.SetColumnSpan(sep, 2); + break; } case "inputWithFileBtn": + { + var textBox = new TextBox() { - var textBox = new TextBox() - { - Margin = new Thickness(10, 0, 0, 0), - Text = Settings[attribute.Name] as string ?? string.Empty, - HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, - ToolTip = attribute.Description - }; - textBox.TextChanged += (_, _) => - { - Settings[attribute.Name] = textBox.Text; - }; - var Btn = new System.Windows.Controls.Button() - { - Margin = new Thickness(10,0,0,0), - Content = "Browse" - }; - var dockPanel = new DockPanel() - { - Margin = settingControlMargin - }; - DockPanel.SetDock(Btn, Dock.Right); - dockPanel.Children.Add(Btn); - dockPanel.Children.Add(textBox); - contentControl = dockPanel; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - break; - } + Margin = new Thickness(10, 0, 0, 0), + Text = Settings[attribute.Name] as string ?? string.Empty, + HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, + ToolTip = attribute.Description + }; + textBox.TextChanged += (_, _) => + { + Settings[attribute.Name] = textBox.Text; + }; + var Btn = new System.Windows.Controls.Button() + { + Margin = new Thickness(10, 0, 0, 0), Content = "Browse" + }; + var dockPanel = new DockPanel() + { + Margin = settingControlMargin + }; + DockPanel.SetDock(Btn, Dock.Right); + dockPanel.Children.Add(Btn); + dockPanel.Children.Add(textBox); + contentControl = dockPanel; + Grid.SetColumn(contentControl, 1); + Grid.SetRow(contentControl, rowCount); + if (rowCount != 0) + mainPanel.Children.Add(sep); + Grid.SetRow(sep, rowCount); + Grid.SetColumn(sep, 0); + Grid.SetColumnSpan(sep, 2); + break; + } case "textarea": { var textBox = new TextBox() @@ -511,14 +520,14 @@ public Control CreateSettingPanel() Settings[attribute.Name] = ((TextBox)sender).Text; }; contentControl = textBox; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - break; + Grid.SetColumn(contentControl, 1); + Grid.SetRow(contentControl, rowCount); + if (rowCount != 0) + mainPanel.Children.Add(sep); + Grid.SetRow(sep, rowCount); + Grid.SetColumn(sep, 0); + Grid.SetColumnSpan(sep, 2); + break; } case "passwordBox": { @@ -535,14 +544,14 @@ public Control CreateSettingPanel() Settings[attribute.Name] = ((PasswordBox)sender).Password; }; contentControl = passwordBox; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - break; + Grid.SetColumn(contentControl, 1); + Grid.SetRow(contentControl, rowCount); + if (rowCount != 0) + mainPanel.Children.Add(sep); + Grid.SetRow(sep, rowCount); + Grid.SetColumn(sep, 0); + Grid.SetColumnSpan(sep, 2); + break; } case "dropdown": { @@ -559,14 +568,14 @@ public Control CreateSettingPanel() Settings[attribute.Name] = (string)((System.Windows.Controls.ComboBox)sender).SelectedItem; }; contentControl = comboBox; - Grid.SetColumn(contentControl, 1); - Grid.SetRow(contentControl, rowCount); - if (rowCount != 0) - mainPanel.Children.Add(sep); - Grid.SetRow(sep, rowCount); - Grid.SetColumn(sep, 0); - Grid.SetColumnSpan(sep, 2); - break; + Grid.SetColumn(contentControl, 1); + Grid.SetRow(contentControl, rowCount); + if (rowCount != 0) + mainPanel.Children.Add(sep); + Grid.SetRow(sep, rowCount); + Grid.SetColumn(sep, 0); + Grid.SetColumnSpan(sep, 2); + break; } case "checkbox": var checkBox = new CheckBox @@ -592,13 +601,11 @@ public Control CreateSettingPanel() case "hyperlink": var hyperlink = new Hyperlink { - ToolTip = attribute.Description, - NavigateUri = attribute.url + ToolTip = attribute.Description, NavigateUri = attribute.url }; var linkbtn = new System.Windows.Controls.Button { - HorizontalAlignment = System.Windows.HorizontalAlignment.Right, - Margin = settingControlMargin + HorizontalAlignment = System.Windows.HorizontalAlignment.Right, Margin = settingControlMargin }; linkbtn.Content = attribute.urlLabel; @@ -619,7 +626,7 @@ public Control CreateSettingPanel() mainPanel.Children.Add(panel); mainPanel.Children.Add(contentControl); rowCount++; - + } return settingWindow; } diff --git a/Flow.Launcher.Core/Resource/Theme.cs b/Flow.Launcher.Core/Resource/Theme.cs index 872c4543edb..96338cf6a1a 100644 --- a/Flow.Launcher.Core/Resource/Theme.cs +++ b/Flow.Launcher.Core/Resource/Theme.cs @@ -36,7 +36,7 @@ public Theme() { _themeDirectories.Add(DirectoryPath); _themeDirectories.Add(UserDirectoryPath); - MakesureThemeDirectoriesExist(); + MakeSureThemeDirectoriesExist(); var dicts = Application.Current.Resources.MergedDictionaries; _oldResource = dicts.First(d => @@ -55,20 +55,17 @@ public Theme() _oldTheme = Path.GetFileNameWithoutExtension(_oldResource.Source.AbsolutePath); } - private void MakesureThemeDirectoriesExist() + private void MakeSureThemeDirectoriesExist() { - foreach (string dir in _themeDirectories) + foreach (var dir in _themeDirectories.Where(dir => !Directory.Exists(dir))) { - if (!Directory.Exists(dir)) + try { - try - { - Directory.CreateDirectory(dir); - } - catch (Exception e) - { - Log.Exception($"|Theme.MakesureThemeDirectoriesExist|Exception when create directory <{dir}>", e); - } + Directory.CreateDirectory(dir); + } + catch (Exception e) + { + Log.Exception($"|Theme.MakesureThemeDirectoriesExist|Exception when create directory <{dir}>", e); } } } @@ -82,13 +79,14 @@ public bool ChangeTheme(string theme) { if (string.IsNullOrEmpty(path)) throw new DirectoryNotFoundException("Theme path can't be found <{path}>"); - - Settings.Theme = theme; - + // reload all resources even if the theme itself hasn't changed in order to pickup changes // to things like fonts - UpdateResourceDictionary(GetResourceDictionary()); + UpdateResourceDictionary(GetResourceDictionary(theme)); + + Settings.Theme = theme; + //always allow re-loading default theme, in case of failure of switching to a new theme from default theme if (_oldTheme != theme || theme == defaultTheme) { @@ -134,9 +132,9 @@ private void UpdateResourceDictionary(ResourceDictionary dictionaryToUpdate) _oldResource = dictionaryToUpdate; } - private ResourceDictionary CurrentThemeResourceDictionary() + private ResourceDictionary GetThemeResourceDictionary(string theme) { - var uri = GetThemePath(Settings.Theme); + var uri = GetThemePath(theme); var dict = new ResourceDictionary { Source = new Uri(uri, UriKind.Absolute) @@ -145,10 +143,12 @@ private ResourceDictionary CurrentThemeResourceDictionary() return dict; } - public ResourceDictionary GetResourceDictionary() + private ResourceDictionary CurrentThemeResourceDictionary() => GetThemeResourceDictionary(Settings.Theme); + + public ResourceDictionary GetResourceDictionary(string theme) { - var dict = CurrentThemeResourceDictionary(); - + var dict = GetThemeResourceDictionary(theme); + if (dict["QueryBoxStyle"] is Style queryBoxStyle && dict["QuerySuggestionBoxStyle"] is Style querySuggestionBoxStyle) { @@ -200,6 +200,11 @@ public ResourceDictionary GetResourceDictionary() return dict; } + private ResourceDictionary GetCurrentResourceDictionary( ) + { + return GetResourceDictionary(Settings.Theme); + } + public List LoadAvailableThemes() { List themes = new List(); @@ -229,7 +234,7 @@ private string GetThemePath(string themeName) public void AddDropShadowEffectToCurrentTheme() { - var dict = GetResourceDictionary(); + var dict = GetCurrentResourceDictionary(); var windowBorderStyle = dict["WindowBorderStyle"] as Style; @@ -273,7 +278,7 @@ public void AddDropShadowEffectToCurrentTheme() public void RemoveDropShadowEffectFromCurrentTheme() { - var dict = CurrentThemeResourceDictionary(); + var dict = GetCurrentResourceDictionary(); var windowBorderStyle = dict["WindowBorderStyle"] as Style; var effectSetter = windowBorderStyle.Setters.FirstOrDefault(setterBase => setterBase is Setter setter && setter.Property == Border.EffectProperty) as Setter; diff --git a/Flow.Launcher.Core/Updater.cs b/Flow.Launcher.Core/Updater.cs index 91de8298c8e..3f64b273e4c 100644 --- a/Flow.Launcher.Core/Updater.cs +++ b/Flow.Launcher.Core/Updater.cs @@ -33,7 +33,7 @@ public Updater(string gitHubRepository) public async Task UpdateAppAsync(IPublicAPI api, bool silentUpdate = true) { - await UpdateLock.WaitAsync(); + await UpdateLock.WaitAsync().ConfigureAwait(false); try { if (!silentUpdate) @@ -88,9 +88,13 @@ public async Task UpdateAppAsync(IPublicAPI api, bool silentUpdate = true) UpdateManager.RestartApp(Constant.ApplicationFileName); } } - catch (Exception e) when (e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException) + catch (Exception e) { - Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e); + if ((e is HttpRequestException or WebException or SocketException || e.InnerException is TimeoutException)) + Log.Exception($"|Updater.UpdateApp|Check your connection and proxy settings to github-cloud.s3.amazonaws.com.", e); + else + Log.Exception($"|Updater.UpdateApp|Error Occurred", e); + if (!silentUpdate) api.ShowMsg(api.GetTranslation("update_flowlauncher_fail"), api.GetTranslation("update_flowlauncher_check_connection")); diff --git a/Flow.Launcher.Infrastructure/FileExplorerHelper.cs b/Flow.Launcher.Infrastructure/FileExplorerHelper.cs new file mode 100644 index 00000000000..76695a4e31e --- /dev/null +++ b/Flow.Launcher.Infrastructure/FileExplorerHelper.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Flow.Launcher.Infrastructure +{ + public static class FileExplorerHelper + { + /// + /// Gets the path of the file explorer that is currently in the foreground + /// + public static string GetActiveExplorerPath() + { + var explorerWindow = GetActiveExplorer(); + string locationUrl = explorerWindow?.LocationURL; + return !string.IsNullOrEmpty(locationUrl) ? new Uri(locationUrl).LocalPath + "\\" : null; + } + + /// + /// Gets the file explorer that is currently in the foreground + /// + private static dynamic GetActiveExplorer() + { + Type type = Type.GetTypeFromProgID("Shell.Application"); + if (type == null) return null; + dynamic shell = Activator.CreateInstance(type); + if (shell == null) + { + return null; + } + + var explorerWindows = new List(); + var openWindows = shell.Windows(); + for (int i = 0; i < openWindows.Count; i++) + { + var window = openWindows.Item(i); + if (window == null) continue; + + // find the desired window and make sure that it is indeed a file explorer + // we don't want the Internet Explorer or the classic control panel + // ToLower() is needed, because Windows can report the path as "C:\\Windows\\Explorer.EXE" + if (Path.GetFileName((string)window.FullName)?.ToLower() == "explorer.exe") + { + explorerWindows.Add(window); + } + } + + if (explorerWindows.Count == 0) return null; + + var zOrders = GetZOrder(explorerWindows); + + return explorerWindows.Zip(zOrders).MinBy(x => x.Second).First; + } + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam); + + private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); + + /// + /// Gets the z-order for one or more windows atomically with respect to each other. In Windows, smaller z-order is higher. If the window is not top level, the z order is returned as -1. + /// + private static IEnumerable GetZOrder(List hWnds) + { + var z = new int[hWnds.Count]; + for (var i = 0; i < hWnds.Count; i++) z[i] = -1; + + var index = 0; + var numRemaining = hWnds.Count; + EnumWindows((wnd, _) => + { + var searchIndex = hWnds.FindIndex(x => x.HWND == wnd.ToInt32()); + if (searchIndex != -1) + { + z[searchIndex] = index; + numRemaining--; + if (numRemaining == 0) return false; + } + index++; + return true; + }, IntPtr.Zero); + + return z; + } + } +} diff --git a/Flow.Launcher.Infrastructure/Helper.cs b/Flow.Launcher.Infrastructure/Helper.cs index faa4c93b513..db575de9004 100644 --- a/Flow.Launcher.Infrastructure/Helper.cs +++ b/Flow.Launcher.Infrastructure/Helper.cs @@ -1,4 +1,6 @@ -using System; +#nullable enable + +using System; using System.IO; using System.Runtime.CompilerServices; using System.Text.Json; @@ -16,7 +18,7 @@ static Helper() /// /// http://www.yinwang.org/blog-cn/2015/11/21/programming-philosophy /// - public static T NonNull(this T obj) + public static T NonNull(this T? obj) { if (obj == null) { diff --git a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs index 5bd97714c15..b92bc020724 100644 --- a/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs +++ b/Flow.Launcher.Infrastructure/Hotkey/HotkeyModel.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Windows.Input; +using System.Windows.Navigation; namespace Flow.Launcher.Infrastructure.Hotkey { @@ -11,10 +12,10 @@ public class HotkeyModel public bool Shift { get; set; } public bool Win { get; set; } public bool Ctrl { get; set; } - public Key CharKey { get; set; } + public Key CharKey { get; set; } = Key.None; - Dictionary specialSymbolDictionary = new Dictionary + private static readonly Dictionary specialSymbolDictionary = new Dictionary { {Key.Space, "Space"}, {Key.Oem3, "~"} @@ -27,19 +28,19 @@ public ModifierKeys ModifierKeys ModifierKeys modifierKeys = ModifierKeys.None; if (Alt) { - modifierKeys = ModifierKeys.Alt; + modifierKeys |= ModifierKeys.Alt; } if (Shift) { - modifierKeys = modifierKeys | ModifierKeys.Shift; + modifierKeys |= ModifierKeys.Shift; } if (Win) { - modifierKeys = modifierKeys | ModifierKeys.Windows; + modifierKeys |= ModifierKeys.Windows; } if (Ctrl) { - modifierKeys = modifierKeys | ModifierKeys.Control; + modifierKeys |= ModifierKeys.Control; } return modifierKeys; } @@ -86,7 +87,7 @@ private void Parse(string hotkeyString) Ctrl = true; keys.Remove("Ctrl"); } - if (keys.Count > 0) + if (keys.Count == 1) { string charKey = keys[0]; KeyValuePair? specialSymbolPair = specialSymbolDictionary.FirstOrDefault(pair => pair.Value == charKey); @@ -110,36 +111,75 @@ private void Parse(string hotkeyString) public override string ToString() { - string text = string.Empty; + List keys = new List(); if (Ctrl) { - text += "Ctrl + "; + keys.Add("Ctrl"); } if (Alt) { - text += "Alt + "; + keys.Add("Alt"); } if (Shift) { - text += "Shift + "; + keys.Add("Shift"); } if (Win) { - text += "Win + "; + keys.Add("Win"); } if (CharKey != Key.None) { - text += specialSymbolDictionary.ContainsKey(CharKey) + keys.Add(specialSymbolDictionary.ContainsKey(CharKey) ? specialSymbolDictionary[CharKey] - : CharKey.ToString(); + : CharKey.ToString()); } - else if (!string.IsNullOrEmpty(text)) + return string.Join(" + ", keys); + } + + public bool Validate() + { + switch (CharKey) + { + case Key.LeftAlt: + case Key.RightAlt: + case Key.LeftCtrl: + case Key.RightCtrl: + case Key.LeftShift: + case Key.RightShift: + case Key.LWin: + case Key.RWin: + return false; + default: + if (ModifierKeys == ModifierKeys.None) + { + return !((CharKey >= Key.A && CharKey <= Key.Z) || + (CharKey >= Key.D0 && CharKey <= Key.D9) || + (CharKey >= Key.NumPad0 && CharKey <= Key.NumPad9)); + } + else + { + return CharKey != Key.None; + } + } + } + + public override bool Equals(object obj) + { + if (obj is HotkeyModel other) { - text = text.Remove(text.Length - 3); + return ModifierKeys == other.ModifierKeys && CharKey == other.CharKey; } + else + { + return false; + } + } - return text; + public override int GetHashCode() + { + return HashCode.Combine(ModifierKeys, CharKey); } } } diff --git a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs index 0083ccb87b0..500b0829ee2 100644 --- a/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/JsonStorage.cs @@ -1,4 +1,5 @@ -using System; +#nullable enable +using System; using System.Globalization; using System.IO; using System.Text.Json; @@ -11,62 +12,73 @@ namespace Flow.Launcher.Infrastructure.Storage /// public class JsonStorage where T : new() { - protected T _data; + protected T? Data; // need a new directory name public const string DirectoryName = "Settings"; public const string FileSuffix = ".json"; - public string FilePath { get; set; } - public string DirectoryPath { get; set; } + protected string FilePath { get; init; } = null!; + private string TempFilePath => $"{FilePath}.tmp"; + private string BackupFilePath => $"{FilePath}.bak"; + protected string DirectoryPath { get; init; } = null!; public T Load() { + string? serialized = null; + if (File.Exists(FilePath)) { - var serialized = File.ReadAllText(FilePath); - if (!string.IsNullOrWhiteSpace(serialized)) + serialized = File.ReadAllText(FilePath); + } + + if (!string.IsNullOrEmpty(serialized)) + { + try { - Deserialize(serialized); + Data = JsonSerializer.Deserialize(serialized)?? TryLoadBackup() ?? LoadDefault(); } - else + catch (JsonException) { - LoadDefault(); + Data = TryLoadBackup() ?? LoadDefault(); } } else { - LoadDefault(); + Data = TryLoadBackup() ?? LoadDefault(); } - return _data.NonNull(); + return Data.NonNull(); } - private void Deserialize(string serialized) + private T LoadDefault() { - try - { - _data = JsonSerializer.Deserialize(serialized); - } - catch (JsonException e) + if (File.Exists(FilePath)) { - LoadDefault(); - Log.Exception($"|JsonStorage.Deserialize|Deserialize error for json <{FilePath}>", e); + BackupOriginFile(); } - if (_data == null) - { - LoadDefault(); - } + return new T(); } - private void LoadDefault() + private T? TryLoadBackup() { - if (File.Exists(FilePath)) + if (!File.Exists(BackupFilePath)) + return default; + + try { - BackupOriginFile(); + var data = JsonSerializer.Deserialize(File.ReadAllText(BackupFilePath)); + if (data != null) + { + Log.Info($"|JsonStorage.Load|Failed to load settings.json, {BackupFilePath} restored successfully"); + File.Replace(BackupFilePath, FilePath, null); + return data; + } + return default; + } + catch (JsonException) + { + return default; } - - _data = new T(); - Save(); } private void BackupOriginFile() @@ -82,13 +94,14 @@ private void BackupOriginFile() public void Save() { - string serialized = JsonSerializer.Serialize(_data, new JsonSerializerOptions() { WriteIndented = true }); + string serialized = JsonSerializer.Serialize(Data, new JsonSerializerOptions + { + WriteIndented = true + }); - File.WriteAllText(FilePath, serialized); + File.WriteAllText(TempFilePath, serialized); + File.Replace(TempFilePath, FilePath, BackupFilePath); + File.Delete(TempFilePath); } } - - [Obsolete("Deprecated as of Flow Launcher v1.8.0, on 2021.06.21. " + - "This is used only for Everything plugin v1.4.9 or below backwards compatibility")] - public class JsonStrorage : JsonStorage where T : new() { } } diff --git a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs index 923a1a6b56f..abe3f55b5ad 100644 --- a/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs +++ b/Flow.Launcher.Infrastructure/Storage/PluginJsonStorage.cs @@ -18,7 +18,7 @@ public PluginJsonStorage() public PluginJsonStorage(T data) : this() { - _data = data; + Data = data; } } } diff --git a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs index a184520d081..bfd7e4b8757 100644 --- a/Flow.Launcher.Infrastructure/UserSettings/Settings.cs +++ b/Flow.Launcher.Infrastructure/UserSettings/Settings.cs @@ -13,6 +13,7 @@ namespace Flow.Launcher.Infrastructure.UserSettings public class Settings : BaseModel { private string language = "en"; + private string _theme = Constant.DefaultTheme; public string Hotkey { get; set; } = $"{KeyConstant.Alt} + {KeyConstant.Space}"; public string OpenResultModifiers { get; set; } = KeyConstant.Alt; public string ColorScheme { get; set; } = "System"; @@ -29,7 +30,18 @@ public string Language OnPropertyChanged(); } } - public string Theme { get; set; } = Constant.DefaultTheme; + public string Theme + { + get => _theme; + set + { + if (value == _theme) + return; + _theme = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(MaxResultsToShow)); + } + } public bool UseDropShadowEffect { get; set; } = false; public string QueryBoxFont { get; set; } = FontFamily.GenericSansSerif.Name; public string QueryBoxFontStyle { get; set; } @@ -192,8 +204,10 @@ public string QuerySearchPrecisionString public ObservableCollection CustomShortcuts { get; set; } = new ObservableCollection(); [JsonIgnore] - public ObservableCollection BuiltinShortcuts { get; set; } = new ObservableCollection() { - new BuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", Clipboard.GetText) + public ObservableCollection BuiltinShortcuts { get; set; } = new() + { + new BuiltinShortcutModel("{clipboard}", "shortcut_clipboard_description", Clipboard.GetText), + new BuiltinShortcutModel("{active_explorer_path}", "shortcut_active_explorer_path", FileExplorerHelper.GetActiveExplorerPath) }; public bool DontPromptUpdateMsg { get; set; } @@ -212,7 +226,7 @@ public bool HideNotifyIcon } } public bool LeaveCmdOpen { get; set; } - public bool HideWhenDeactive { get; set; } = true; + public bool HideWhenDeactivated { get; set; } = true; public SearchWindowPositions SearchWindowPosition { get; set; } = SearchWindowPositions.MouseScreenCenter; public bool IgnoreHotkeysOnFullscreen { get; set; } diff --git a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj index 571ac2a2344..fb07f8255c0 100644 --- a/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj +++ b/Flow.Launcher.Plugin/Flow.Launcher.Plugin.csproj @@ -14,10 +14,10 @@ - 3.0.1 - 3.0.1 - 3.0.1 - 3.0.1 + 3.1.0 + 3.1.0 + 3.1.0 + 3.1.0 Flow.Launcher.Plugin Flow-Launcher MIT @@ -66,7 +66,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Flow.Launcher.Plugin/Result.cs b/Flow.Launcher.Plugin/Result.cs index dadf220e38e..dc24872f5f0 100644 --- a/Flow.Launcher.Plugin/Result.cs +++ b/Flow.Launcher.Plugin/Result.cs @@ -242,7 +242,7 @@ public record PreviewInfo /// public string PreviewImagePath { get; set; } /// - /// Determines if the preview image should occupy the full width of the preveiw panel. + /// Determines if the preview image should occupy the full width of the preview panel. /// public bool IsMedia { get; set; } public string Description { get; set; } diff --git a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs index 5cb3a171a67..bd8d32ff511 100644 --- a/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs +++ b/Flow.Launcher.Plugin/SharedCommands/FilesFolders.cs @@ -1,7 +1,9 @@ using System; using System.Diagnostics; using System.IO; +#pragma warning disable IDE0005 using System.Windows; +#pragma warning restore IDE0005 namespace Flow.Launcher.Plugin.SharedCommands { @@ -206,22 +208,16 @@ public static bool IsLocationPathString(this string querySearchString) /// public static string GetPreviousExistingDirectory(Func locationExists, string path) { - var previousDirectoryPath = ""; var index = path.LastIndexOf('\\'); if (index > 0 && index < (path.Length - 1)) { - previousDirectoryPath = path.Substring(0, index + 1); - if (!locationExists(previousDirectoryPath)) - { - return ""; - } + string previousDirectoryPath = path.Substring(0, index + 1); + return locationExists(previousDirectoryPath) ? previousDirectoryPath : ""; } else { return ""; } - - return previousDirectoryPath; } /// @@ -241,5 +237,33 @@ public static string ReturnPreviousDirectoryIfIncompleteString(string path) return path; } + + /// + /// Returns if contains . + /// From https://stackoverflow.com/a/66877016 + /// + /// Parent path + /// Sub path + /// If , when and are equal, returns + /// + public static bool PathContains(string parentPath, string subPath, bool allowEqual = false) + { + var rel = Path.GetRelativePath(parentPath.EnsureTrailingSlash(), subPath); + return (rel != "." || allowEqual) + && rel != ".." + && !rel.StartsWith("../") + && !rel.StartsWith(@"..\") + && !Path.IsPathRooted(rel); + } + + /// + /// Returns path ended with "\" + /// + /// + /// + public static string EnsureTrailingSlash(this string path) + { + return path.TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar; + } } } diff --git a/Flow.Launcher.Test/FilesFoldersTest.cs b/Flow.Launcher.Test/FilesFoldersTest.cs new file mode 100644 index 00000000000..d1682605377 --- /dev/null +++ b/Flow.Launcher.Test/FilesFoldersTest.cs @@ -0,0 +1,53 @@ +using Flow.Launcher.Plugin.SharedCommands; +using NUnit.Framework; + +namespace Flow.Launcher.Test +{ + [TestFixture] + + public class FilesFoldersTest + { + // Testcases from https://stackoverflow.com/a/31941905/20703207 + // Disk + [TestCase(@"c:", @"c:\foo", true)] + [TestCase(@"c:\", @"c:\foo", true)] + // Slash + [TestCase(@"c:\foo\bar\", @"c:\foo\", false)] + [TestCase(@"c:\foo\bar", @"c:\foo\", false)] + [TestCase(@"c:\foo", @"c:\foo\bar", true)] + [TestCase(@"c:\foo\", @"c:\foo\bar", true)] + // File + [TestCase(@"c:\foo", @"c:\foo\a.txt", true)] + [TestCase(@"c:\foo", @"c:/foo/a.txt", true)] + [TestCase(@"c:\FOO\a.txt", @"c:\foo", false)] + [TestCase(@"c:\foo\a.txt", @"c:\foo\", false)] + [TestCase(@"c:\foobar\a.txt", @"c:\foo", false)] + [TestCase(@"c:\foobar\a.txt", @"c:\foo\", false)] + [TestCase(@"c:\foo\", @"c:\foo.txt", false)] + // Prefix + [TestCase(@"c:\foo", @"c:\foobar", false)] + [TestCase(@"C:\Program", @"C:\Program Files\", false)] + [TestCase(@"c:\foobar", @"c:\foo\a.txt", false)] + [TestCase(@"c:\foobar\", @"c:\foo\a.txt", false)] + // Edge case + [TestCase(@"c:\foo", @"c:\foo\..\bar\baz", false)] + [TestCase(@"c:\bar", @"c:\foo\..\bar\baz", true)] + [TestCase(@"c:\barr", @"c:\foo\..\bar\baz", false)] + // Equality + [TestCase(@"c:\foo", @"c:\foo", false)] + [TestCase(@"c:\foo\", @"c:\foo", false)] + [TestCase(@"c:\foo", @"c:\foo\", false)] + public void GivenTwoPaths_WhenCheckPathContains_ThenShouldBeExpectedResult(string parentPath, string path, bool expectedResult) + { + Assert.AreEqual(expectedResult, FilesFolders.PathContains(parentPath, path)); + } + + [TestCase(@"c:\foo", @"c:\foo", true)] + [TestCase(@"c:\foo\", @"c:\foo", true)] + [TestCase(@"c:\foo", @"c:\foo\", true)] + public void GivenTwoPathsAreTheSame_WhenCheckPathContains_ThenShouldBeTrue(string parentPath, string path, bool expectedResult) + { + Assert.AreEqual(expectedResult, FilesFolders.PathContains(parentPath, path, true)); + } + } +} diff --git a/Flow.Launcher.Test/Flow.Launcher.Test.csproj b/Flow.Launcher.Test/Flow.Launcher.Test.csproj index c67a5cf228c..31b71a1dc3a 100644 --- a/Flow.Launcher.Test/Flow.Launcher.Test.csproj +++ b/Flow.Launcher.Test/Flow.Launcher.Test.csproj @@ -48,9 +48,9 @@ - + - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Flow.Launcher.Test/Plugins/ExplorerTest.cs b/Flow.Launcher.Test/Plugins/ExplorerTest.cs index 36f0294a93e..e9d37433f4e 100644 --- a/Flow.Launcher.Test/Plugins/ExplorerTest.cs +++ b/Flow.Launcher.Test/Plugins/ExplorerTest.cs @@ -7,9 +7,11 @@ using NUnit.Framework; using System; using System.Collections.Generic; +using System.IO; using System.Runtime.Versioning; using System.Threading; using System.Threading.Tasks; +using static Flow.Launcher.Plugin.Explorer.Search.SearchManager; namespace Flow.Launcher.Test.Plugins { @@ -176,7 +178,7 @@ public void GivenQuery_WhenActionKeywordForFileContentSearchExists_ThenFileConte var searchManager = new SearchManager(new Settings(), new PluginInitContext()); // When - var result = SearchManager.IsFileContentSearch(query.ActionKeyword); + var result = searchManager.IsFileContentSearch(query.ActionKeyword); // Then Assert.IsTrue(result, @@ -193,6 +195,7 @@ public void GivenQuery_WhenActionKeywordForFileContentSearchExists_ThenFileConte [TestCase(@"c:\>*", true)] [TestCase(@"c:\>", true)] [TestCase(@"c:\SomeLocation\SomeOtherLocation\>", true)] + [TestCase(@"c:\SomeLocation\SomeOtherLocation", true)] public void WhenGivenQuerySearchString_ThenShouldIndicateIfIsLocationPathString(string querySearchString, bool expectedResult) { // When, Given @@ -393,5 +396,68 @@ public void GivenQueryWithFileTypeResult_WhenGetAutoComplete_ThenResultShouldBeE // Then Assert.AreEqual(result, expectedResult); } + + [TestCase(@"c:\foo", @"c:\foo", true)] + [TestCase(@"C:\Foo\", @"c:\foo\", true)] + [TestCase(@"c:\foo", @"c:\foo\", false)] + public void GivenTwoPaths_WhenCompared_ThenShouldBeExpectedSameOrDifferent(string path1, string path2, bool expectedResult) + { + // Given + var comparator = PathEqualityComparator.Instance; + var result1 = new Result + { + Title = Path.GetFileName(path1), + SubTitle = path1 + }; + var result2 = new Result + { + Title = Path.GetFileName(path2), + SubTitle = path2 + }; + + // When, Then + Assert.AreEqual(expectedResult, comparator.Equals(result1, result2)); + } + + [TestCase(@"c:\foo\", @"c:\foo\")] + [TestCase(@"C:\Foo\", @"c:\foo\")] + public void GivenTwoPaths_WhenComparedHasCode_ThenShouldBeSame(string path1, string path2) + { + // Given + var comparator = PathEqualityComparator.Instance; + var result1 = new Result + { + Title = Path.GetFileName(path1), + SubTitle = path1 + }; + var result2 = new Result + { + Title = Path.GetFileName(path2), + SubTitle = path2 + }; + + var hash1 = comparator.GetHashCode(result1); + var hash2 = comparator.GetHashCode(result2); + + // When, Then + Assert.IsTrue(hash1 == hash2); + } + + [TestCase(@"%appdata%", true)] + [TestCase(@"%appdata%\123", true)] + [TestCase(@"c:\foo %appdata%\", false)] + [TestCase(@"c:\users\%USERNAME%\downloads", true)] + [TestCase(@"c:\downloads", false)] + [TestCase(@"%", false)] + [TestCase(@"%%", false)] + [TestCase(@"%bla%blabla%", false)] + public void GivenPath_WhenHavingEnvironmentVariableOrNot_ThenShouldBeExpected(string path, bool expectedResult) + { + // When + var result = EnvironmentVariables.HasEnvironmentVar(path); + + // Then + Assert.AreEqual(result, expectedResult); + } } } diff --git a/Flow.Launcher/App.xaml.cs b/Flow.Launcher/App.xaml.cs index 43fa0eddb1f..1d398276d3c 100644 --- a/Flow.Launcher/App.xaml.cs +++ b/Flow.Launcher/App.xaml.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Timers; using System.Windows; @@ -84,14 +85,14 @@ await Stopwatch.NormalAsync("|App.OnStartup|Startup cost", async () => Current.MainWindow = window; Current.MainWindow.Title = Constant.FlowLauncher; - + HotKeyMapper.Initialize(_mainVM); - // happlebao todo temp fix for instance code logic + // todo temp fix for instance code logic // load plugin before change language, because plugin language also needs be changed InternationalizationManager.Instance.Settings = _settings; InternationalizationManager.Instance.ChangeLanguage(_settings.Language); - // main windows needs initialized before theme change because of blur settigns + // main windows needs initialized before theme change because of blur settings ThemeManager.Instance.Settings = _settings; ThemeManager.Instance.ChangeTheme(_settings.Theme); @@ -130,20 +131,17 @@ private void AutoStartup() //[Conditional("RELEASE")] private void AutoUpdates() { - Task.Run(async () => + _ = Task.Run(async () => { if (_settings.AutoUpdates) { - // check udpate every 5 hours - var timer = new Timer(1000 * 60 * 60 * 5); - timer.Elapsed += async (s, e) => - { - await _updater.UpdateAppAsync(API); - }; - timer.Start(); - - // check updates on startup + // check update every 5 hours + var timer = new PeriodicTimer(TimeSpan.FromHours(5)); await _updater.UpdateAppAsync(API); + + while (await timer.WaitForNextTickAsync()) + // check updates on startup + await _updater.UpdateAppAsync(API); } }); } diff --git a/Flow.Launcher/Converters/DiameterToCenterPointConverter.cs b/Flow.Launcher/Converters/DiameterToCenterPointConverter.cs new file mode 100644 index 00000000000..e81bb250790 --- /dev/null +++ b/Flow.Launcher/Converters/DiameterToCenterPointConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace Flow.Launcher.Converters +{ + public class DiameterToCenterPointConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is double d) + { + return new Point(d / 2, d / 2); + } + + return new Point(0, 0); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } +} diff --git a/Flow.Launcher/Converters/IconRadiusConverter.cs b/Flow.Launcher/Converters/IconRadiusConverter.cs new file mode 100644 index 00000000000..51129cfb873 --- /dev/null +++ b/Flow.Launcher/Converters/IconRadiusConverter.cs @@ -0,0 +1,27 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using Windows.Devices.PointOfService; + +namespace Flow.Launcher.Converters +{ + public class IconRadiusConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values.Length != 2) + throw new ArgumentException("IconRadiusConverter must have 2 parameters"); + + return values[1] switch + { + true => (double)values[0] / 2, + false => (double)values[0], + _ => throw new ArgumentException("The second argument should be boolean", nameof(values)) + }; + } + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } +} diff --git a/Flow.Launcher/Flow.Launcher.csproj b/Flow.Launcher/Flow.Launcher.csproj index 1f74ea8a524..a36668fc40e 100644 --- a/Flow.Launcher/Flow.Launcher.csproj +++ b/Flow.Launcher/Flow.Launcher.csproj @@ -90,7 +90,7 @@ - + all diff --git a/Flow.Launcher/HotkeyControl.xaml b/Flow.Launcher/HotkeyControl.xaml index 5a593d20a19..acf4a21ec40 100644 --- a/Flow.Launcher/HotkeyControl.xaml +++ b/Flow.Launcher/HotkeyControl.xaml @@ -48,6 +48,7 @@ Margin="0,0,18,0" VerticalContentAlignment="Center" input:InputMethod.IsInputMethodEnabled="False" + GotFocus="tbHotkey_GotFocus" LostFocus="tbHotkey_LostFocus" PreviewKeyDown="TbHotkey_OnPreviewKeyDown" TabIndex="100" /> diff --git a/Flow.Launcher/HotkeyControl.xaml.cs b/Flow.Launcher/HotkeyControl.xaml.cs index d746c8fd25d..15feae6cc9a 100644 --- a/Flow.Launcher/HotkeyControl.xaml.cs +++ b/Flow.Launcher/HotkeyControl.xaml.cs @@ -9,16 +9,11 @@ using Flow.Launcher.Infrastructure.Hotkey; using Flow.Launcher.Plugin; using System.Threading; -using System.Windows.Interop; namespace Flow.Launcher { public partial class HotkeyControl : UserControl { - private Brush tbMsgForegroundColorOriginal; - - private string tbMsgTextOriginal; - public HotkeyModel CurrentHotkey { get; private set; } public bool CurrentHotkeyAvailable { get; private set; } @@ -29,8 +24,6 @@ public partial class HotkeyControl : UserControl public HotkeyControl() { InitializeComponent(); - tbMsgTextOriginal = tbMsg.Text; - tbMsgForegroundColorOriginal = tbMsg.Foreground; } private CancellationTokenSource hotkeyUpdateSource; @@ -55,9 +48,7 @@ private void TbHotkey_OnPreviewKeyDown(object sender, KeyEventArgs e) specialKeyState.CtrlPressed, key); - var hotkeyString = hotkeyModel.ToString(); - - if (hotkeyString == tbHotkey.Text) + if (hotkeyModel.Equals(CurrentHotkey)) { return; } @@ -72,33 +63,32 @@ private void TbHotkey_OnPreviewKeyDown(object sender, KeyEventArgs e) public async Task SetHotkeyAsync(HotkeyModel keyModel, bool triggerValidate = true) { - CurrentHotkey = keyModel; - - tbHotkey.Text = CurrentHotkey.ToString(); + tbHotkey.Text = keyModel.ToString(); tbHotkey.Select(tbHotkey.Text.Length, 0); if (triggerValidate) { - CurrentHotkeyAvailable = CheckHotkeyAvailability(); - if (!CurrentHotkeyAvailable) - { - tbMsg.Foreground = new SolidColorBrush(Colors.Red); - tbMsg.Text = InternationalizationManager.Instance.GetTranslation("hotkeyUnavailable"); - } - else - { - tbMsg.Foreground = new SolidColorBrush(Colors.Green); - tbMsg.Text = InternationalizationManager.Instance.GetTranslation("success"); - } - tbMsg.Visibility = Visibility.Visible; + bool hotkeyAvailable = CheckHotkeyAvailability(keyModel); + CurrentHotkeyAvailable = hotkeyAvailable; + SetMessage(hotkeyAvailable); OnHotkeyChanged(); var token = hotkeyUpdateSource.Token; await Task.Delay(500, token); if (token.IsCancellationRequested) return; - FocusManager.SetFocusedElement(FocusManager.GetFocusScope(this), null); - Keyboard.ClearFocus(); + + if (CurrentHotkeyAvailable) + { + CurrentHotkey = keyModel; + // To trigger LostFocus + FocusManager.SetFocusedElement(FocusManager.GetFocusScope(this), null); + Keyboard.ClearFocus(); + } + } + else + { + CurrentHotkey = keyModel; } } @@ -107,14 +97,40 @@ public Task SetHotkeyAsync(string keyStr, bool triggerValidate = true) return SetHotkeyAsync(new HotkeyModel(keyStr), triggerValidate); } - private bool CheckHotkeyAvailability() => HotKeyMapper.CheckAvailability(CurrentHotkey); + private static bool CheckHotkeyAvailability(HotkeyModel hotkey) => hotkey.Validate() && HotKeyMapper.CheckAvailability(hotkey); public new bool IsFocused => tbHotkey.IsFocused; private void tbHotkey_LostFocus(object sender, RoutedEventArgs e) { - tbMsg.Text = tbMsgTextOriginal; + tbHotkey.Text = CurrentHotkey.ToString(); + tbHotkey.Select(tbHotkey.Text.Length, 0); + } + + private void tbHotkey_GotFocus(object sender, RoutedEventArgs e) + { + ResetMessage(); + } + + private void ResetMessage() + { + tbMsg.Text = InternationalizationManager.Instance.GetTranslation("flowlauncherPressHotkey"); tbMsg.SetResourceReference(TextBox.ForegroundProperty, "Color05B"); } + + private void SetMessage(bool hotkeyAvailable) + { + if (!hotkeyAvailable) + { + tbMsg.Foreground = new SolidColorBrush(Colors.Red); + tbMsg.Text = InternationalizationManager.Instance.GetTranslation("hotkeyUnavailable"); + } + else + { + tbMsg.Foreground = new SolidColorBrush(Colors.Green); + tbMsg.Text = InternationalizationManager.Instance.GetTranslation("success"); + } + tbMsg.Visibility = Visibility.Visible; + } } } diff --git a/Flow.Launcher/Languages/da.xaml b/Flow.Launcher/Languages/da.xaml index ec89377e77f..f8db8420d28 100644 --- a/Flow.Launcher/Languages/da.xaml +++ b/Flow.Launcher/Languages/da.xaml @@ -201,7 +201,7 @@ Om Website - Github + GitHub Docs Version Icons @@ -262,7 +262,7 @@ Tilpasset søgegenvejstast - Press a custom hotkey to open Flow Laucher and input the specified query automatically. + Press a custom hotkey to open Flow Launcher and input the specified query automatically. Vis Genvejstast er utilgængelig, vælg venligst en ny genvejstast Ugyldig plugin genvejstast @@ -336,7 +336,7 @@ Back / Context Menu Item Navigation Open Context Menu - Open Contaning Folder + Open Containing Folder Run as Admin Query History Back to Result in Context Menu diff --git a/Flow.Launcher/Languages/de.xaml b/Flow.Launcher/Languages/de.xaml index 2b5d2c1496c..09b800f71b0 100644 --- a/Flow.Launcher/Languages/de.xaml +++ b/Flow.Launcher/Languages/de.xaml @@ -201,7 +201,7 @@ Über Webseite - Github + GitHub Dokumentation Version Icons @@ -262,7 +262,7 @@ Benutzerdefinierte Abfrage Tastenkombination - Press a custom hotkey to open Flow Laucher and input the specified query automatically. + Press a custom hotkey to open Flow Launcher and input the specified query automatically. Vorschau Tastenkombination ist nicht verfügbar, bitte wähle eine andere Tastenkombination Ungültige Plugin Tastenkombination @@ -336,7 +336,7 @@ Back / Context Menu Item Navigation Open Context Menu - Open Contaning Folder + Open Containing Folder Run as Admin Query History Back to Result in Context Menu diff --git a/Flow.Launcher/Languages/en.xaml b/Flow.Launcher/Languages/en.xaml index 430bdef2699..49c603dc851 100644 --- a/Flow.Launcher/Languages/en.xaml +++ b/Flow.Launcher/Languages/en.xaml @@ -176,6 +176,7 @@ Are you sure you want to delete {0} plugin hotkey? Are you sure you want to delete shortcut: {0} with expansion {1}? Get text from clipboard. + Get path from active explorer. Query window shadow effect Shadow effect has a substantial usage of GPU. Not recommended if your computer performance is limited. Window Width Size @@ -203,7 +204,7 @@ About Website - Github + GitHub Docs Version Icons @@ -264,7 +265,7 @@ Custom Query Hotkey - Press a custom hotkey to open Flow Laucher and input the specified query automatically. + Press a custom hotkey to open Flow Launcher and input the specified query automatically. Preview Hotkey is unavailable, please select a new hotkey Invalid plugin hotkey @@ -338,7 +339,7 @@ Back / Context Menu Item Navigation Open Context Menu - Open Contaning Folder + Open Containing Folder Run as Admin Query History Back to Result in Context Menu diff --git a/Flow.Launcher/Languages/es.xaml b/Flow.Launcher/Languages/es.xaml index 49ae75d76be..aa28743990f 100644 --- a/Flow.Launcher/Languages/es.xaml +++ b/Flow.Launcher/Languages/es.xaml @@ -262,7 +262,7 @@ Atajo de teclado de consulta personalizada - Pulse el atajo de teclado personalizado para abrir Flow Laucher y realizar automáticamente la consulta especificada. + Pulse el atajo de teclado personalizado para abrir Flow Launcher y realizar automáticamente la consulta especificada. Vista previa El atajo de teclado no está disponible, por favor seleccione uno nuevo Atajo de teclado de complemento no válido diff --git a/Flow.Launcher/Languages/fr.xaml b/Flow.Launcher/Languages/fr.xaml index ce922b70b55..c700ecaadbd 100644 --- a/Flow.Launcher/Languages/fr.xaml +++ b/Flow.Launcher/Languages/fr.xaml @@ -201,7 +201,7 @@ À propos Website - Github + GitHub Docs Version Icons @@ -261,7 +261,7 @@ Requêtes personnalisées - Press a custom hotkey to open Flow Laucher and input the specified query automatically. + Press a custom hotkey to open Flow Launcher and input the specified query automatically. Prévisualiser Raccourci indisponible. Veuillez en choisir un autre. Raccourci invalide @@ -335,7 +335,7 @@ Back / Context Menu Item Navigation Open Context Menu - Open Contaning Folder + Open Containing Folder Run as Admin Query History Back to Result in Context Menu diff --git a/Flow.Launcher/Languages/it.xaml b/Flow.Launcher/Languages/it.xaml index 361bc0dcf70..8fc179093e1 100644 --- a/Flow.Launcher/Languages/it.xaml +++ b/Flow.Launcher/Languages/it.xaml @@ -201,7 +201,7 @@ Informazioni Sito web - Github + GitHub Documentazione Versione Icons @@ -262,7 +262,7 @@ Tasti scelta rapida per ricerche personalizzate - Press a custom hotkey to open Flow Laucher and input the specified query automatically. + Press a custom hotkey to open Flow Launcher and input the specified query automatically. Anteprima Tasto di scelta rapida non disponibile, per favore scegli un nuovo tasto di scelta rapida Tasto di scelta rapida plugin non valido @@ -336,7 +336,7 @@ Indietro / Menu contestuale Navigazione tra le voci Apri il menu di scelta rapida - Apri la cartella Contaning + Apri cartella superiore Esegui come amministratore Cronologia Query Torna al risultato nel menu contestuale diff --git a/Flow.Launcher/Languages/ja.xaml b/Flow.Launcher/Languages/ja.xaml index 6bfabf93220..20295f3e1f3 100644 --- a/Flow.Launcher/Languages/ja.xaml +++ b/Flow.Launcher/Languages/ja.xaml @@ -201,7 +201,7 @@ Flow Launcherについて ウェブサイト - Github + GitHub Docs バージョン Icons @@ -262,7 +262,7 @@ - Press a custom hotkey to open Flow Laucher and input the specified query automatically. + Press a custom hotkey to open Flow Launcher and input the specified query automatically. プレビュー ホットキーは使用できません。新しいホットキーを選択してください プラグインホットキーは無効です @@ -336,7 +336,7 @@ Back / Context Menu Item Navigation Open Context Menu - Open Contaning Folder + Open Containing Folder Run as Admin Query History Back to Result in Context Menu diff --git a/Flow.Launcher/Languages/ko.xaml b/Flow.Launcher/Languages/ko.xaml index cfdd8574747..e69e29c8c7c 100644 --- a/Flow.Launcher/Languages/ko.xaml +++ b/Flow.Launcher/Languages/ko.xaml @@ -201,7 +201,7 @@ 정보 웹사이트 - Github + GitHub 문서 버전 아이콘 @@ -262,7 +262,7 @@ 사용자지정 쿼리 단축키 - Press a custom hotkey to open Flow Laucher and input the specified query automatically. + Press a custom hotkey to open Flow Launcher and input the specified query automatically. 미리보기 단축키를 사용할 수 없습니다. 다른 단축키를 입력하세요. 플러그인 단축키가 유효하지 않습니다. diff --git a/Flow.Launcher/Languages/nb.xaml b/Flow.Launcher/Languages/nb.xaml index a247f84d3a9..629624f5ef9 100644 --- a/Flow.Launcher/Languages/nb.xaml +++ b/Flow.Launcher/Languages/nb.xaml @@ -201,7 +201,7 @@ About Website - Github + GitHub Docs Version Icons @@ -262,7 +262,7 @@ Custom Query Hotkey - Press a custom hotkey to open Flow Laucher and input the specified query automatically. + Press a custom hotkey to open Flow Launcher and input the specified query automatically. Preview Hotkey is unavailable, please select a new hotkey Invalid plugin hotkey @@ -336,7 +336,7 @@ Back / Context Menu Item Navigation Open Context Menu - Open Contaning Folder + Open Containing Folder Run as Admin Query History Back to Result in Context Menu diff --git a/Flow.Launcher/Languages/nl.xaml b/Flow.Launcher/Languages/nl.xaml index ab9b3ac0762..4b1c9bb84a8 100644 --- a/Flow.Launcher/Languages/nl.xaml +++ b/Flow.Launcher/Languages/nl.xaml @@ -201,7 +201,7 @@ About Website - Github + GitHub Docs Versie Icons @@ -262,7 +262,7 @@ Custom Query Sneltoets - Press a custom hotkey to open Flow Laucher and input the specified query automatically. + Press a custom hotkey to open Flow Launcher and input the specified query automatically. Voorbeeld Sneltoets is niet beschikbaar, selecteer een nieuwe sneltoets Ongeldige plugin sneltoets @@ -336,7 +336,7 @@ Back / Context Menu Item Navigation Open Context Menu - Open Contaning Folder + Open Containing Folder Run as Admin Query History Back to Result in Context Menu diff --git a/Flow.Launcher/Languages/pl.xaml b/Flow.Launcher/Languages/pl.xaml index ab8e6d001b4..2a9052ca3de 100644 --- a/Flow.Launcher/Languages/pl.xaml +++ b/Flow.Launcher/Languages/pl.xaml @@ -201,7 +201,7 @@ O programie Website - Github + GitHub Docs Wersja Icons @@ -262,7 +262,7 @@ Skrót klawiszowy niestandardowych zapyta - Press a custom hotkey to open Flow Laucher and input the specified query automatically. + Press a custom hotkey to open Flow Launcher and input the specified query automatically. Podgląd Skrót klawiszowy jest niedostępny, musisz podać inny skrót klawiszowy Niepoprawny skrót klawiszowy @@ -336,7 +336,7 @@ Back / Context Menu Item Navigation Open Context Menu - Open Contaning Folder + Open Containing Folder Run as Admin Query History Back to Result in Context Menu diff --git a/Flow.Launcher/Languages/pt-br.xaml b/Flow.Launcher/Languages/pt-br.xaml index e6c6ade0bc2..496aea4f340 100644 --- a/Flow.Launcher/Languages/pt-br.xaml +++ b/Flow.Launcher/Languages/pt-br.xaml @@ -201,7 +201,7 @@ Sobre Website - Github + GitHub Docs Versão Icons @@ -262,7 +262,7 @@ Atalho de Consulta Personalizada - Press a custom hotkey to open Flow Laucher and input the specified query automatically. + Press a custom hotkey to open Flow Launcher and input the specified query automatically. Prévia Atalho indisponível, escolha outro Atalho de plugin inválido @@ -336,7 +336,7 @@ Back / Context Menu Item Navigation Open Context Menu - Open Contaning Folder + Open Containing Folder Run as Admin Query History Back to Result in Context Menu diff --git a/Flow.Launcher/Languages/pt-pt.xaml b/Flow.Launcher/Languages/pt-pt.xaml index b19383e4484..ed2760857b7 100644 --- a/Flow.Launcher/Languages/pt-pt.xaml +++ b/Flow.Launcher/Languages/pt-pt.xaml @@ -335,7 +335,7 @@ Queira por favor mover a pasta do seu perfil de {0} para {1} Recuar/Menu de contexto Navegação nos itens Abrir menu de contexto - Abrir pasta + Abrir pasta do resultado Executar como administrador Histórico de consultas Voltar aos resultados no menu de contexto diff --git a/Flow.Launcher/Languages/ru.xaml b/Flow.Launcher/Languages/ru.xaml index aa55091be4e..6369fec8a82 100644 --- a/Flow.Launcher/Languages/ru.xaml +++ b/Flow.Launcher/Languages/ru.xaml @@ -201,7 +201,7 @@ О Flow Launcher Website - Github + GitHub Docs Версия Icons @@ -262,7 +262,7 @@ Задаваемые горячие клавиши для запросов - Press a custom hotkey to open Flow Laucher and input the specified query automatically. + Press a custom hotkey to open Flow Launcher and input the specified query automatically. Предпросмотр Горячая клавиша недоступна. Пожалуйста, задайте новую Недействительная горячая клавиша плагина @@ -336,7 +336,7 @@ Back / Context Menu Item Navigation Open Context Menu - Open Contaning Folder + Open Containing Folder Run as Admin Query History Back to Result in Context Menu diff --git a/Flow.Launcher/Languages/sk.xaml b/Flow.Launcher/Languages/sk.xaml index af20a2ee937..fc2df1e19f3 100644 --- a/Flow.Launcher/Languages/sk.xaml +++ b/Flow.Launcher/Languages/sk.xaml @@ -201,7 +201,7 @@ O aplikácii Webstránka - Github + GitHub Dokumentácia Verzia Ikony @@ -262,7 +262,7 @@ Klávesová skratka vlastného vyhľadávania - Stlačením vlastnej klávesovej skratky otvoríte Flow Laucher a automaticky vložíte zadaný dotaz. + Stlačením vlastnej klávesovej skratky otvoríte Flow Launcher a automaticky vložíte zadaný dotaz. Náhľad Klávesová skratka je nedostupná, prosím, zadajte novú skratku Neplatná klávesová skratka pluginu diff --git a/Flow.Launcher/Languages/sr.xaml b/Flow.Launcher/Languages/sr.xaml index f621bbf0df4..d747f630b01 100644 --- a/Flow.Launcher/Languages/sr.xaml +++ b/Flow.Launcher/Languages/sr.xaml @@ -201,7 +201,7 @@ O Flow Launcher-u Website - Github + GitHub Docs Verzija Icons @@ -262,7 +262,7 @@ prečica za ručno dodat upit - Press a custom hotkey to open Flow Laucher and input the specified query automatically. + Press a custom hotkey to open Flow Launcher and input the specified query automatically. Pregled Prečica je nedustupna, molim Vas izaberite drugu prečicu Nepravlna prečica za plugin @@ -336,7 +336,7 @@ Back / Context Menu Item Navigation Open Context Menu - Open Contaning Folder + Open Containing Folder Run as Admin Query History Back to Result in Context Menu diff --git a/Flow.Launcher/Languages/tr.xaml b/Flow.Launcher/Languages/tr.xaml index 1f87f8fd6ca..2bda02604cf 100644 --- a/Flow.Launcher/Languages/tr.xaml +++ b/Flow.Launcher/Languages/tr.xaml @@ -201,7 +201,7 @@ Hakkında Website - Github + GitHub Docs Sürüm Icons @@ -234,7 +234,7 @@ Arg For File - Default Web Browser + Varsayılan İnternet Tarayıcısı The default setting follows the OS default browser setting. If specified separately, flow uses that browser. Browser Browser Name @@ -262,7 +262,7 @@ Özel Sorgu Kısayolları - Press a custom hotkey to open Flow Laucher and input the specified query automatically. + Press a custom hotkey to open Flow Launcher and input the specified query automatically. Önizleme Kısayol tuşu kullanılabilir değil, lütfen başka bir kısayol tuşu seçin Geçersiz eklenti kısayol tuşu @@ -336,7 +336,7 @@ Back / Context Menu Item Navigation Open Context Menu - Open Contaning Folder + Open Containing Folder Run as Admin Query History Back to Result in Context Menu diff --git a/Flow.Launcher/Languages/uk-UA.xaml b/Flow.Launcher/Languages/uk-UA.xaml index e20fa7ca7c6..761396f119f 100644 --- a/Flow.Launcher/Languages/uk-UA.xaml +++ b/Flow.Launcher/Languages/uk-UA.xaml @@ -201,7 +201,7 @@ Про Flow Launcher Website - Github + GitHub Docs Версія Icons @@ -262,7 +262,7 @@ Задані гарячі клавіші для запитів - Press a custom hotkey to open Flow Laucher and input the specified query automatically. + Press a custom hotkey to open Flow Launcher and input the specified query automatically. Переглянути Гаряча клавіша недоступна. Будь ласка, вкажіть нову Недійсна гаряча клавіша плагіна @@ -336,7 +336,7 @@ Back / Context Menu Item Navigation Open Context Menu - Open Contaning Folder + Open Containing Folder Run as Admin Query History Back to Result in Context Menu diff --git a/Flow.Launcher/Languages/zh-cn.xaml b/Flow.Launcher/Languages/zh-cn.xaml index 7fb46dd3583..8604bbb1a42 100644 --- a/Flow.Launcher/Languages/zh-cn.xaml +++ b/Flow.Launcher/Languages/zh-cn.xaml @@ -201,7 +201,7 @@ 关于 官方网站 - Github + GitHub 文档 版本 图标 @@ -262,7 +262,7 @@ 自定义查询热键 - 输入一个自定义的快捷键来打开 Flow Laucher 并自动输入指定的查询。 + 输入一个自定义的快捷键来打开 Flow Launcher 并自动输入指定的查询。 预览 热键不可用,请选择一个新的热键 插件热键不合法 diff --git a/Flow.Launcher/Languages/zh-tw.xaml b/Flow.Launcher/Languages/zh-tw.xaml index 7d4f5d94841..b0a75be14ee 100644 --- a/Flow.Launcher/Languages/zh-tw.xaml +++ b/Flow.Launcher/Languages/zh-tw.xaml @@ -201,7 +201,7 @@ 關於 官方網站 - Github + GitHub 文檔 版本 Icons @@ -262,7 +262,7 @@ 自定義快捷鍵查詢 - Press a custom hotkey to open Flow Laucher and input the specified query automatically. + Press a custom hotkey to open Flow Launcher and input the specified query automatically. 預覽 快捷鍵不存在,請設定一個新的快捷鍵 外掛熱鍵無法使用 diff --git a/Flow.Launcher/MainWindow.xaml b/Flow.Launcher/MainWindow.xaml index 6ef8353d3cb..a9554da3420 100644 --- a/Flow.Launcher/MainWindow.xaml +++ b/Flow.Launcher/MainWindow.xaml @@ -9,11 +9,11 @@ xmlns:svgc="http://sharpvectors.codeplex.com/svgc/" xmlns:ui="http://schemas.modernwpf.com/2019" xmlns:vm="clr-namespace:Flow.Launcher.ViewModel" - d:DataContext="{d:DesignInstance Type=vm:MainViewModel}" Name="FlowMainWindow" Title="Flow Launcher" MinWidth="{Binding MainWindowWidth, Mode=OneWay}" MaxWidth="{Binding MainWindowWidth, Mode=OneWay}" + d:DataContext="{d:DesignInstance Type=vm:MainViewModel}" AllowDrop="True" AllowsTransparency="True" Background="Transparent" @@ -38,9 +38,9 @@ - - - + + + @@ -180,11 +180,11 @@ + Modifiers="Ctrl" /> + Modifiers="{Binding PreviewHotkey, Converter={StaticResource StringToKeyBindingConverter}, ConverterParameter='modifiers'}" /> @@ -207,12 +207,12 @@ @@ -273,9 +273,6 @@ - - diff --git a/Flow.Launcher/MainWindow.xaml.cs b/Flow.Launcher/MainWindow.xaml.cs index b2d842022f6..550648b2479 100644 --- a/Flow.Launcher/MainWindow.xaml.cs +++ b/Flow.Launcher/MainWindow.xaml.cs @@ -485,7 +485,7 @@ private async void OnDeactivated(object sender, EventArgs e) if (_settings.UseAnimation) await Task.Delay(100); - if (_settings.HideWhenDeactive) + if (_settings.HideWhenDeactivated) { _viewModel.Hide(); } diff --git a/Flow.Launcher/PriorityChangeWindow.xaml b/Flow.Launcher/PriorityChangeWindow.xaml index d6aadead9b1..c917eeffc54 100644 --- a/Flow.Launcher/PriorityChangeWindow.xaml +++ b/Flow.Launcher/PriorityChangeWindow.xaml @@ -85,6 +85,8 @@ + + + +