├── .config └── dotnet-tools.json ├── .devinit.json ├── .github ├── dependabot.yml ├── template │ └── build-signed │ │ └── action.yaml └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── BrowserPicker.sln ├── BrowserPicker.sln.DotSettings ├── LICENSE ├── dist ├── Dependent │ ├── Dependent.wixproj │ └── Product.wxs ├── Portable │ ├── Portable.wixproj │ └── Product.wxs └── code_signing.cer ├── docs ├── config_add_browser.png ├── config_add_browser_exe_picked.png ├── config_behaviour.png ├── config_defaults_browsers.png ├── config_defaults_empty.png ├── config_defaults_match_type.png ├── config_defaults_test_no_match.png ├── config_disabled.png ├── config_list.png ├── config_list_with_notepad.png ├── selector_edit_url.png ├── selector_edited_url.png └── selector_two_running.png ├── nuget.config ├── readme.md └── src ├── BrowserPicker.App ├── .editorconfig ├── App.xaml ├── App.xaml.cs ├── BrowserPicker.App.csproj ├── Converter │ └── IconConverter.cs ├── Program.cs ├── Properties │ ├── PublishProfiles │ │ └── FolderProfile.pubxml │ └── launchSettings.json ├── Resources │ ├── ResourceDictionary.xaml │ ├── privacy.png │ ├── web_icon.ico │ └── web_icon.png ├── View │ ├── BrowserEditor.xaml │ ├── BrowserEditor.xaml.cs │ ├── BrowserList.xaml │ ├── BrowserList.xaml.cs │ ├── Configuration.xaml │ ├── Configuration.xaml.cs │ ├── ExceptionReport.xaml │ ├── ExceptionReport.xaml.cs │ ├── LoadingWindow.xaml │ ├── LoadingWindow.xaml.cs │ ├── MainWindow.xaml │ └── MainWindow.xaml.cs ├── ViewModel │ ├── ApplicationViewModel.cs │ ├── BrowserViewModel.cs │ ├── ConfigurationViewModel.cs │ └── ExceptionViewModel.cs └── appsettings.json ├── BrowserPicker.Windows ├── AppSettings.cs ├── BrowserPicker.Windows.csproj └── RegistryHelpers.cs ├── BrowserPicker ├── BrowserModel.cs ├── BrowserPicker.csproj ├── BrowserSorter.cs ├── DefaultSetting.cs ├── ExceptionModel.cs ├── Framework │ ├── DelegateCommand.cs │ ├── ModelBase.cs │ └── ViewModelBase.cs ├── IApplicationSettings.cs ├── IBrowserPickerConfiguration.cs ├── ILongRunningProcess.cs ├── KeyBinding.cs ├── LoggingExtensions.cs ├── MatchType.cs ├── Pattern.cs ├── SerializableSettings.cs ├── UrlHandler.cs └── WellKnownBrowsers.cs ├── Directory.Build.props └── Directory.Packages.props /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "wix": { 6 | "version": "6.0.0", 7 | "commands": [ 8 | "wix" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.devinit.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/devinit.schema-4.0", 3 | "run": [ 4 | { 5 | "comments": "Installs the Wix toolset", 6 | "tool": "msi-install", 7 | "input": "https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311.exe" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "nuget" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/template/build-signed/action.yaml: -------------------------------------------------------------------------------- 1 | name: build-signed 2 | description: builds a signed executable 3 | 4 | inputs: 5 | configuration: 6 | required: true 7 | description: "dotnet build configuration" 8 | default: "Release" 9 | 10 | solution_path: 11 | required: true 12 | description: "The path to the solution file" 13 | default: "BrowserPicker.sln" 14 | 15 | project_path: 16 | required: true 17 | description: "The path to the application project file to publish" 18 | default: "src/BrowserPicker.App/BrowserPicker.App.csproj" 19 | 20 | dotnet_args: 21 | required: true 22 | description: "Extra arguments for dotnet" 23 | 24 | package_project: 25 | required: true 26 | description: "Path to wixproj to build" 27 | 28 | package_version: 29 | required: true 30 | description: "MSI package VersionPrefix" 31 | 32 | package: 33 | required: true 34 | description: "Path to msi package to build and sign" 35 | 36 | package_name: 37 | required: true 38 | description: "Name of the uploaded package artifact" 39 | 40 | package_path: 41 | required: true 42 | description: "Path to the package to upload" 43 | 44 | binaries: 45 | required: true 46 | description: "Pattern matching binaries to be signed and bundled" 47 | default: "" 48 | 49 | bundle_name: 50 | required: true 51 | description: "Name of the uploaded bundle artifact" 52 | 53 | bundle_path: 54 | required: true 55 | description: "Path to the files to bundle and upload" 56 | 57 | runs: 58 | using: composite 59 | steps: 60 | - name: Install .NET Core 61 | uses: actions/setup-dotnet@v4 62 | with: 63 | dotnet-version: 9.x 64 | 65 | # Restore dotnet tools 66 | - name: Restore tools 67 | shell: bash 68 | run: dotnet tool restore 69 | 70 | # Restore the application to populate the obj folder with RuntimeIdentifiers 71 | - name: Restore the application 72 | shell: bash 73 | run: dotnet restore ${{ inputs.solution_path }} ${{ inputs.dotnet_args }} 74 | 75 | # Build and publish the application 76 | - name: Build application 77 | shell: bash 78 | run: dotnet publish -c ${{ inputs.configuration }} ${{ inputs.project_path }} ${{ inputs.dotnet_args }} 79 | 80 | # Create the app package by building and packaging the Windows Application Packaging project 81 | - name: Create the installer 82 | shell: bash 83 | run: dotnet build ${{ inputs.package_project }} --no-dependencies -c ${{ inputs.configuration }} -p Version=${{ inputs.package_version }} 84 | 85 | - name: Upload msi 86 | uses: actions/upload-artifact@v4 87 | with: 88 | name: ${{ inputs.package_name }} 89 | path: ${{ inputs.package_path }} 90 | 91 | - name: Upload bundle 92 | uses: actions/upload-artifact@v4 93 | with: 94 | name: ${{ inputs.bundle_name }} 95 | path: ${{ inputs.bundle_path }} 96 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | 7 | jobs: 8 | prepare: 9 | 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Determine version 19 | id: version 20 | uses: paulhatch/semantic-version@v5.4.0 21 | with: 22 | version_format: "${major}.${minor}.${patch}" 23 | 24 | outputs: 25 | version: ${{ steps.version.outputs.version}} 26 | version_suffix: "beta${{ steps.version.outputs.increment }}" 27 | package_version: "${{ steps.version.outputs.major }}.${{ steps.version.outputs.minor }}.${{ steps.version.outputs.patch }}.${{ steps.version.outputs.increment }}" 28 | 29 | dependent: 30 | 31 | strategy: 32 | matrix: 33 | configuration: [Debug, Release] 34 | 35 | runs-on: windows-latest 36 | needs: prepare 37 | 38 | steps: 39 | - name: Checkout 40 | uses: actions/checkout@v4 41 | with: 42 | fetch-depth: 0 43 | 44 | - name: Build runtime dependent binaries 45 | uses: "./.github/template/build-signed" 46 | with: 47 | configuration: ${{ matrix.configuration }} 48 | dotnet_args: "-p VersionPrefix=${{ needs.prepare.outputs.version }} -p VersionSuffix=${{ needs.prepare.outputs.version_suffix }}" 49 | package_project: dist/Dependent/Dependent.wixproj 50 | package_version: ${{ needs.prepare.outputs.package_version }} 51 | package: dist\Dependent\bin\${{ matrix.configuration }}\BrowserPicker.msi 52 | package_name: DependentSetup-${{ needs.prepare.outputs.version }}-${{ matrix.configuration }} 53 | package_path: dist/Dependent/bin/${{ matrix.configuration }} 54 | binaries: | 55 | src\BrowserPicker.App\bin\${{ matrix.configuration }}\net9.0-windows\publish\BrowserPicker*.dll src\BrowserPicker.App\bin\${{ matrix.configuration }}\net9.0-windows\publish\BrowserPicker*.exe 56 | bundle_name: Dependent-${{ needs.prepare.outputs.version }}-${{ matrix.configuration }} 57 | bundle_path: src/BrowserPicker.App/bin/${{ matrix.configuration }}/net9.0-windows/publish 58 | 59 | portable: 60 | 61 | strategy: 62 | matrix: 63 | configuration: [Debug, Release] 64 | 65 | runs-on: windows-latest 66 | needs: prepare 67 | 68 | steps: 69 | - name: Checkout 70 | uses: actions/checkout@v4 71 | with: 72 | fetch-depth: 0 73 | 74 | - name: Build runtime portable binaries 75 | uses: "./.github/template/build-signed" 76 | with: 77 | configuration: ${{ matrix.configuration }} 78 | dotnet_args: "-p VersionPrefix=${{ needs.prepare.outputs.version }} -p VersionSuffix=${{ needs.prepare.outputs.version_suffix }} -r win-x64 -p:PublishSingleFile=true" 79 | package_project: dist/Portable/Portable.wixproj 80 | package_version: ${{ needs.prepare.outputs.package_version }} 81 | package: dist\Portable\bin\${{ matrix.configuration }}\BrowserPicker-Portable.msi 82 | package_name: PortableSetup-${{ needs.prepare.outputs.version }}-${{ matrix.configuration }} 83 | package_path: dist/Portable/bin/${{ matrix.configuration }} 84 | binaries: src\BrowserPicker.App\bin\${{ matrix.configuration }}\net9.0-windows\win-x64\publish\BrowserPicker.exe 85 | bundle_name: Portable-${{ needs.prepare.outputs.version }}-${{ matrix.configuration }} 86 | bundle_path: src/BrowserPicker.App/bin/${{ matrix.configuration }}/net9.0-windows/win-x64/publish -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | 10 | prepare: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | 20 | - name: Determine version 21 | id: version 22 | uses: paulhatch/semantic-version@v5.4.0 23 | with: 24 | version_format: "${major}.${minor}.${patch}" 25 | 26 | - name: Upload certificate 27 | uses: actions/upload-artifact@v4 28 | with: 29 | name: dist 30 | path: dist/code_signing.cer 31 | 32 | outputs: 33 | version: ${{ steps.version.outputs.version}} 34 | 35 | dependent: 36 | runs-on: windows-latest 37 | needs: prepare 38 | 39 | steps: 40 | - name: Checkout 41 | uses: actions/checkout@v4 42 | with: 43 | fetch-depth: 0 44 | 45 | - name: Build runtime dependent binaries 46 | uses: "./.github/template/build-signed" 47 | with: 48 | dotnet_args: "-p VersionPrefix=${{ needs.prepare.outputs.version }}" 49 | package_project: dist/Dependent/Dependent.wixproj 50 | package_version: ${{ needs.prepare.outputs.version }} 51 | package: dist\Dependent\bin\Release\BrowserPicker.msi 52 | package_name: DependentSetup-${{ needs.prepare.outputs.version }}-Release 53 | package_path: dist/Dependent/bin/Release 54 | binaries: | 55 | src\BrowserPicker.App\bin\Release\net9.0-windows\publish\BrowserPicker*.dll src\BrowserPicker.App\bin\Release\net9.0-windows\publish\BrowserPicker*.exe 56 | bundle_name: Dependent-${{ needs.prepare.outputs.version }}-Release 57 | bundle_path: src/BrowserPicker.App/bin/Release/net9.0-windows/publish 58 | 59 | portable: 60 | runs-on: windows-latest 61 | needs: prepare 62 | 63 | steps: 64 | - name: Checkout 65 | uses: actions/checkout@v4 66 | with: 67 | fetch-depth: 0 68 | 69 | - name: Build runtime independent binaries 70 | uses: "./.github/template/build-signed" 71 | with: 72 | dotnet_args: "-p VersionPrefix=${{ needs.prepare.outputs.version }} -r win-x64 -p:PublishSingleFile=true" 73 | package_project: dist/Portable/Portable.wixproj 74 | package_version: ${{ needs.prepare.outputs.version }} 75 | package: dist\Portable\bin\Release\BrowserPicker-Portable.msi 76 | package_name: PortableSetup-${{ needs.prepare.outputs.version }}-Release 77 | package_path: dist/Portable/bin/Release 78 | binaries: src\BrowserPicker.App\bin\Release\net9.0-windows\win-x64\publish\BrowserPicker.exe 79 | bundle_name: Portable-${{ needs.prepare.outputs.version }}-Release 80 | bundle_path: src/BrowserPicker.App/bin/Release/net9.0-windows/win-x64/publish 81 | 82 | publish: 83 | runs-on: ubuntu-latest 84 | needs: [prepare, dependent, portable] 85 | 86 | steps: 87 | - name: Checkout 88 | uses: actions/checkout@v4 89 | with: 90 | fetch-depth: 0 91 | 92 | - name: Retrieve artifacts 93 | uses: actions/download-artifact@v4 94 | 95 | - name: Package bundles 96 | run: | 97 | rm -rf *.zip 98 | for bundle in Dependent Portable; do 99 | (cd $bundle-${{ needs.prepare.outputs.version }}-Release; zip -r ../$bundle.zip *) 100 | done 101 | 102 | - name: Release 103 | uses: softprops/action-gh-release@v2 104 | with: 105 | generate_release_notes: true 106 | draft: true 107 | prerelease: true 108 | files: | 109 | DependentSetup-${{ needs.prepare.outputs.version }}-Release/BrowserPicker.msi 110 | PortableSetup-${{ needs.prepare.outputs.version }}-Release/BrowserPicker-Portable.msi 111 | Dependent.zip 112 | Portable.zip 113 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | bin 3 | obj 4 | *.user 5 | packages/ 6 | -------------------------------------------------------------------------------- /BrowserPicker.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31919.166 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F46B5958-C607-4E07-9C2C-F63538149287}" 7 | ProjectSection(SolutionItems) = preProject 8 | LICENSE = LICENSE 9 | nuget.config = nuget.config 10 | readme.md = readme.md 11 | EndProjectSection 12 | EndProject 13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BrowserPicker", "src\BrowserPicker\BrowserPicker.csproj", "{D7695535-9C0D-4983-B8F7-09B067347E7E}" 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BrowserPicker.App", "src\BrowserPicker.App\BrowserPicker.App.csproj", "{B875AE86-5212-4F7F-BB1C-2BAA1FC110BC}" 16 | EndProject 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BrowserPicker.Windows", "src\BrowserPicker.Windows\BrowserPicker.Windows.csproj", "{C3CE17EA-BAAE-4DE7-AFB2-319D50ECB2C8}" 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A7A5C3AA-4BF6-4B3B-A515-6C1E21E0B4E1}" 20 | ProjectSection(SolutionItems) = preProject 21 | src\Directory.Build.props = src\Directory.Build.props 22 | src\Directory.Packages.props = src\Directory.Packages.props 23 | EndProjectSection 24 | EndProject 25 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dist", "dist", "{CD1CE02F-9EBF-48E0-81E4-E047F20F10C8}" 26 | ProjectSection(SolutionItems) = preProject 27 | dist\code_signing.cer = dist\code_signing.cer 28 | EndProjectSection 29 | EndProject 30 | Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "Dependent", "dist\Dependent\Dependent.wixproj", "{F9D9359C-6DA4-463E-86B9-505E04E01C3A}" 31 | EndProject 32 | Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "Portable", "dist\Portable\Portable.wixproj", "{FADE3CC5-5631-4BF0-A92B-A3464BA5A5EA}" 33 | EndProject 34 | Global 35 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 36 | Debug|ARM64 = Debug|ARM64 37 | Debug|x64 = Debug|x64 38 | Debug|x86 = Debug|x86 39 | Release|ARM64 = Release|ARM64 40 | Release|x64 = Release|x64 41 | Release|x86 = Release|x86 42 | EndGlobalSection 43 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 44 | {D7695535-9C0D-4983-B8F7-09B067347E7E}.Debug|ARM64.ActiveCfg = Debug|x64 45 | {D7695535-9C0D-4983-B8F7-09B067347E7E}.Debug|ARM64.Build.0 = Debug|x64 46 | {D7695535-9C0D-4983-B8F7-09B067347E7E}.Debug|x64.ActiveCfg = Debug|x64 47 | {D7695535-9C0D-4983-B8F7-09B067347E7E}.Debug|x64.Build.0 = Debug|x64 48 | {D7695535-9C0D-4983-B8F7-09B067347E7E}.Debug|x86.ActiveCfg = Debug|x64 49 | {D7695535-9C0D-4983-B8F7-09B067347E7E}.Debug|x86.Build.0 = Debug|x64 50 | {D7695535-9C0D-4983-B8F7-09B067347E7E}.Release|ARM64.ActiveCfg = Release|x64 51 | {D7695535-9C0D-4983-B8F7-09B067347E7E}.Release|ARM64.Build.0 = Release|x64 52 | {D7695535-9C0D-4983-B8F7-09B067347E7E}.Release|x64.ActiveCfg = Release|x64 53 | {D7695535-9C0D-4983-B8F7-09B067347E7E}.Release|x64.Build.0 = Release|x64 54 | {D7695535-9C0D-4983-B8F7-09B067347E7E}.Release|x86.ActiveCfg = Release|x64 55 | {D7695535-9C0D-4983-B8F7-09B067347E7E}.Release|x86.Build.0 = Release|x64 56 | {B875AE86-5212-4F7F-BB1C-2BAA1FC110BC}.Debug|ARM64.ActiveCfg = Debug|x64 57 | {B875AE86-5212-4F7F-BB1C-2BAA1FC110BC}.Debug|ARM64.Build.0 = Debug|x64 58 | {B875AE86-5212-4F7F-BB1C-2BAA1FC110BC}.Debug|x64.ActiveCfg = Debug|x64 59 | {B875AE86-5212-4F7F-BB1C-2BAA1FC110BC}.Debug|x64.Build.0 = Debug|x64 60 | {B875AE86-5212-4F7F-BB1C-2BAA1FC110BC}.Debug|x86.ActiveCfg = Debug|x64 61 | {B875AE86-5212-4F7F-BB1C-2BAA1FC110BC}.Debug|x86.Build.0 = Debug|x64 62 | {B875AE86-5212-4F7F-BB1C-2BAA1FC110BC}.Release|ARM64.ActiveCfg = Release|x64 63 | {B875AE86-5212-4F7F-BB1C-2BAA1FC110BC}.Release|ARM64.Build.0 = Release|x64 64 | {B875AE86-5212-4F7F-BB1C-2BAA1FC110BC}.Release|x64.ActiveCfg = Release|x64 65 | {B875AE86-5212-4F7F-BB1C-2BAA1FC110BC}.Release|x64.Build.0 = Release|x64 66 | {B875AE86-5212-4F7F-BB1C-2BAA1FC110BC}.Release|x86.ActiveCfg = Release|x64 67 | {B875AE86-5212-4F7F-BB1C-2BAA1FC110BC}.Release|x86.Build.0 = Release|x64 68 | {C3CE17EA-BAAE-4DE7-AFB2-319D50ECB2C8}.Debug|ARM64.ActiveCfg = Debug|x64 69 | {C3CE17EA-BAAE-4DE7-AFB2-319D50ECB2C8}.Debug|ARM64.Build.0 = Debug|x64 70 | {C3CE17EA-BAAE-4DE7-AFB2-319D50ECB2C8}.Debug|x64.ActiveCfg = Debug|x64 71 | {C3CE17EA-BAAE-4DE7-AFB2-319D50ECB2C8}.Debug|x64.Build.0 = Debug|x64 72 | {C3CE17EA-BAAE-4DE7-AFB2-319D50ECB2C8}.Debug|x86.ActiveCfg = Debug|x64 73 | {C3CE17EA-BAAE-4DE7-AFB2-319D50ECB2C8}.Debug|x86.Build.0 = Debug|x64 74 | {C3CE17EA-BAAE-4DE7-AFB2-319D50ECB2C8}.Release|ARM64.ActiveCfg = Release|x64 75 | {C3CE17EA-BAAE-4DE7-AFB2-319D50ECB2C8}.Release|ARM64.Build.0 = Release|x64 76 | {C3CE17EA-BAAE-4DE7-AFB2-319D50ECB2C8}.Release|x64.ActiveCfg = Release|x64 77 | {C3CE17EA-BAAE-4DE7-AFB2-319D50ECB2C8}.Release|x64.Build.0 = Release|x64 78 | {C3CE17EA-BAAE-4DE7-AFB2-319D50ECB2C8}.Release|x86.ActiveCfg = Release|x64 79 | {C3CE17EA-BAAE-4DE7-AFB2-319D50ECB2C8}.Release|x86.Build.0 = Release|x64 80 | {F9D9359C-6DA4-463E-86B9-505E04E01C3A}.Debug|ARM64.ActiveCfg = Debug|ARM64 81 | {F9D9359C-6DA4-463E-86B9-505E04E01C3A}.Debug|ARM64.Build.0 = Debug|ARM64 82 | {F9D9359C-6DA4-463E-86B9-505E04E01C3A}.Debug|x64.ActiveCfg = Debug|x64 83 | {F9D9359C-6DA4-463E-86B9-505E04E01C3A}.Debug|x86.ActiveCfg = Debug|x86 84 | {F9D9359C-6DA4-463E-86B9-505E04E01C3A}.Debug|x86.Build.0 = Debug|x86 85 | {F9D9359C-6DA4-463E-86B9-505E04E01C3A}.Release|ARM64.ActiveCfg = Release|ARM64 86 | {F9D9359C-6DA4-463E-86B9-505E04E01C3A}.Release|ARM64.Build.0 = Release|ARM64 87 | {F9D9359C-6DA4-463E-86B9-505E04E01C3A}.Release|x64.ActiveCfg = Release|x64 88 | {F9D9359C-6DA4-463E-86B9-505E04E01C3A}.Release|x86.ActiveCfg = Release|x86 89 | {F9D9359C-6DA4-463E-86B9-505E04E01C3A}.Release|x86.Build.0 = Release|x86 90 | {FADE3CC5-5631-4BF0-A92B-A3464BA5A5EA}.Debug|ARM64.ActiveCfg = Debug|ARM64 91 | {FADE3CC5-5631-4BF0-A92B-A3464BA5A5EA}.Debug|ARM64.Build.0 = Debug|ARM64 92 | {FADE3CC5-5631-4BF0-A92B-A3464BA5A5EA}.Debug|x64.ActiveCfg = Debug|x64 93 | {FADE3CC5-5631-4BF0-A92B-A3464BA5A5EA}.Debug|x86.ActiveCfg = Debug|x86 94 | {FADE3CC5-5631-4BF0-A92B-A3464BA5A5EA}.Debug|x86.Build.0 = Debug|x86 95 | {FADE3CC5-5631-4BF0-A92B-A3464BA5A5EA}.Release|ARM64.ActiveCfg = Release|ARM64 96 | {FADE3CC5-5631-4BF0-A92B-A3464BA5A5EA}.Release|ARM64.Build.0 = Release|ARM64 97 | {FADE3CC5-5631-4BF0-A92B-A3464BA5A5EA}.Release|x64.ActiveCfg = Release|x64 98 | {FADE3CC5-5631-4BF0-A92B-A3464BA5A5EA}.Release|x86.ActiveCfg = Release|x86 99 | {FADE3CC5-5631-4BF0-A92B-A3464BA5A5EA}.Release|x86.Build.0 = Release|x86 100 | EndGlobalSection 101 | GlobalSection(SolutionProperties) = preSolution 102 | HideSolutionNode = FALSE 103 | EndGlobalSection 104 | GlobalSection(NestedProjects) = preSolution 105 | {D7695535-9C0D-4983-B8F7-09B067347E7E} = {A7A5C3AA-4BF6-4B3B-A515-6C1E21E0B4E1} 106 | {B875AE86-5212-4F7F-BB1C-2BAA1FC110BC} = {A7A5C3AA-4BF6-4B3B-A515-6C1E21E0B4E1} 107 | {C3CE17EA-BAAE-4DE7-AFB2-319D50ECB2C8} = {A7A5C3AA-4BF6-4B3B-A515-6C1E21E0B4E1} 108 | {F9D9359C-6DA4-463E-86B9-505E04E01C3A} = {CD1CE02F-9EBF-48E0-81E4-E047F20F10C8} 109 | {FADE3CC5-5631-4BF0-A92B-A3464BA5A5EA} = {CD1CE02F-9EBF-48E0-81E4-E047F20F10C8} 110 | EndGlobalSection 111 | GlobalSection(ExtensibilityGlobals) = postSolution 112 | SolutionGuid = {FA02F9A8-CC4F-4C63-A345-418FC0D10D32} 113 | EndGlobalSection 114 | EndGlobal 115 | -------------------------------------------------------------------------------- /BrowserPicker.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | URL 4 | UTF 5 | <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> 6 | <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> 7 | <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aa_bb" /></Policy> 8 | <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="aa_bb" /></Policy> 9 | True 10 | True 11 | True 12 | True 13 | True 14 | True 15 | True 16 | True 17 | True 18 | True 19 | True 20 | True 21 | True 22 | True 23 | True 24 | True 25 | True 26 | True 27 | True 28 | True 29 | True 30 | True 31 | True -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Morten Nilsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /dist/Dependent/Dependent.wixproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | BrowserPicker 4 | false 5 | x64 6 | ProductVersion=$(Version) 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | BrowserPicker.App 15 | {b875ae86-5212-4f7f-bb1c-2baa1fc110bc} 16 | True 17 | True 18 | Binaries;Content;Satellites 19 | INSTALLFOLDER 20 | 21 | 22 | 23 | Debug 24 | bin\$(Platform)\$(Configuration)\ 25 | obj\$(Platform)\$(Configuration)\ 26 | 27 | 28 | bin\$(Platform)\$(Configuration)\ 29 | obj\$(Platform)\$(Configuration)\ 30 | 31 | 32 | -------------------------------------------------------------------------------- /dist/Dependent/Product.wxs: -------------------------------------------------------------------------------- 1 |  5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /dist/Portable/Portable.wixproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | BrowserPicker-Portable 4 | false 5 | x64 6 | ProductVersion=$(Version) 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | BrowserPicker.App 15 | {b875ae86-5212-4f7f-bb1c-2baa1fc110bc} 16 | True 17 | True 18 | Binaries;Content;Satellites 19 | INSTALLFOLDER 20 | 21 | 22 | 23 | Debug 24 | bin\$(Platform)\$(Configuration)\ 25 | obj\$(Platform)\$(Configuration)\ 26 | 27 | 28 | bin\$(Platform)\$(Configuration)\ 29 | obj\$(Platform)\$(Configuration)\ 30 | 31 | 32 | -------------------------------------------------------------------------------- /dist/Portable/Product.wxs: -------------------------------------------------------------------------------- 1 |  4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /dist/code_signing.cer: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDJjCCAg6gAwIBAgIQcNd6jmhb3bJIFsoJWcMm9jANBgkqhkiG9w0BAQsFADAc 3 | MRowGAYDVQQDDBFtb3J0ZW5AcnVuc2FmZS5ubzAeFw0yNDAzMzEyMTU5NDhaFw0y 4 | NTAzMzEyMjE5NDhaMBwxGjAYBgNVBAMMEW1vcnRlbkBydW5zYWZlLm5vMIIBIjAN 5 | BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqJNT3p8bhY8qpsbjobZJoMQjS9Wo 6 | so55x2WcEYmCUej7Mb+yscclAS/Mh/o2QjtQdor5dM9+aFCt5zAe4aYRjPBQWSmw 7 | qsyzuVK5GtUE8vhdmjKFXFn+RfCQzij7+auyRh/ntXJ0lTdDHdY78kK7hw/MQiqX 8 | 6+Ff6LWSdHKpjuElfHO++mqarmVt0h9KpuEhRN8cUMxiNMgZzoPuK31TGK4C5ovY 9 | 9smn5JJwVOjAPTIL/v3z3sYuWWovIdM0e7gFV6B1Er7H9IPmWZay0L69b43nxVx4 10 | wiGeCUcV6Oxj01Gl3YqpXXfh6NFGo3XwHULr09CdJr+Wc01yF91XgPa3bQIDAQAB 11 | o2QwYjAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwHAYDVR0R 12 | BBUwE4IRbW9ydGVuQHJ1bnNhZmUubm8wHQYDVR0OBBYEFKErAdBjYvNYE1rgfSAN 13 | bsu/RZY7MA0GCSqGSIb3DQEBCwUAA4IBAQBZnYV8ww2IKqQYssrxu8+I60Xkvqgj 14 | Mw8x35PWwlSuvQSzPzs50HBTXsOLyNQcFETjfMs58TqtwkOaILeswD/MxhVdyJ7H 15 | 7lsIjiOuozDlta/2pfIfaRtniPAOOqNr4kD/y/f4v1maLKQL4ct3RauKrPxcQHPK 16 | bzE4OuzIGVVwcllnget23kWxNwWnK4RdxqtugMwI6kzqJFDKH982Du+V6AEfxU4n 17 | yWSYIuREcgc5eRG+1uun+KSxT78TtKsPsfrgmA5X9jjMW0jDZ7GLb37m+gZ9r+/P 18 | RhYO5m0SMOjnDQIovm+GQcHfj3yQXqPIvr37FY5ZEMfqf4z56bB13jI1 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /docs/config_add_browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenn/BrowserPicker/e409f39d885b51f0b385664a1a49cb2177c25d31/docs/config_add_browser.png -------------------------------------------------------------------------------- /docs/config_add_browser_exe_picked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenn/BrowserPicker/e409f39d885b51f0b385664a1a49cb2177c25d31/docs/config_add_browser_exe_picked.png -------------------------------------------------------------------------------- /docs/config_behaviour.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenn/BrowserPicker/e409f39d885b51f0b385664a1a49cb2177c25d31/docs/config_behaviour.png -------------------------------------------------------------------------------- /docs/config_defaults_browsers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenn/BrowserPicker/e409f39d885b51f0b385664a1a49cb2177c25d31/docs/config_defaults_browsers.png -------------------------------------------------------------------------------- /docs/config_defaults_empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenn/BrowserPicker/e409f39d885b51f0b385664a1a49cb2177c25d31/docs/config_defaults_empty.png -------------------------------------------------------------------------------- /docs/config_defaults_match_type.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenn/BrowserPicker/e409f39d885b51f0b385664a1a49cb2177c25d31/docs/config_defaults_match_type.png -------------------------------------------------------------------------------- /docs/config_defaults_test_no_match.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenn/BrowserPicker/e409f39d885b51f0b385664a1a49cb2177c25d31/docs/config_defaults_test_no_match.png -------------------------------------------------------------------------------- /docs/config_disabled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenn/BrowserPicker/e409f39d885b51f0b385664a1a49cb2177c25d31/docs/config_disabled.png -------------------------------------------------------------------------------- /docs/config_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenn/BrowserPicker/e409f39d885b51f0b385664a1a49cb2177c25d31/docs/config_list.png -------------------------------------------------------------------------------- /docs/config_list_with_notepad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenn/BrowserPicker/e409f39d885b51f0b385664a1a49cb2177c25d31/docs/config_list_with_notepad.png -------------------------------------------------------------------------------- /docs/selector_edit_url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenn/BrowserPicker/e409f39d885b51f0b385664a1a49cb2177c25d31/docs/selector_edit_url.png -------------------------------------------------------------------------------- /docs/selector_edited_url.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenn/BrowserPicker/e409f39d885b51f0b385664a1a49cb2177c25d31/docs/selector_edited_url.png -------------------------------------------------------------------------------- /docs/selector_two_running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenn/BrowserPicker/e409f39d885b51f0b385664a1a49cb2177c25d31/docs/selector_two_running.png -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Browser Picker 2 | A default browser replacement for windows to let you pick your preferred browser on the fly or in accordance with your own rules. 3 | 4 | ![Screenshot of browserpicker with three options, of which 2 are running and 1 is not](docs/selector_two_running.png) 5 | 6 | You can easily configure it to use Firefox for `github.com` and `slashdot.org`, but leave Edge to handle `microsoft.com` 7 | and even let Internet Explorer handle that old internal LOB app you'd rather not use but must. 8 | 9 | ## Installation 10 | You can find the latest release on [github](https://github.com/mortenn/BrowserPicker/releases). 11 | 12 | ### Default browser 13 | To enable the browser picker window, you need to set Browser Picker as your default browser. 14 | 15 | ### .NET Runtime dependent binary 16 | BrowserPicker.msi and Dependent.zip are JIT compiled and require you have the [.NET 9.0 Desktop Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/9.0) installed. 17 | Direct links: [64bit systems](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-9.0.3-windows-x64-installer), [32bit systems](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-9.0.3-windows-x86-installer). 18 | 19 | #### Native image generation 20 | As part of installation, `BrowserPicker.msi` will execute ngen to build a native image for your computer. 21 | This significantly enhances launch times for the executable. 22 | If you prefer the bundle, you may run `ngen install BrowserPicker.exe` to get the same benefit. 23 | 24 | ### Portable binary 25 | If you do not want to have the .net runtime installed on your computer, you may download the Portable version, which includes the runtime. 26 | 27 | `BrowserPicker-Portable.msi` and `Portable.zip` contain a win-x64 binary executable with embedded .NET runtime. 28 | This makes the file sizes quite significantly larger, but you do not need an additional runtime to use these. 29 | 30 | ### Signing certificate 31 | To avoid warnings about unknown publisher, you may [import](https://stackoverflow.com/questions/49039136/powershell-script-to-install-trusted-publisher-certificates) the provided certificate into your certificate store first. 32 | 33 | ### Manual steps 34 | You need to open the settings app from the start menu, navigate into Apps, select Default apps, then change the Web browser to BrowserPicker. 35 | Please ensure BrowserPicker can be started before you do this. 36 | 37 | ## Usage 38 | 39 | When you open a link outside a browser, one of these things will happen, in order: 40 | 41 | 1. If you have previously selected `Always ask`, the browser selection window is shown. 42 | 2. If you have set up a configuration rule matching the url being opened, the selected browser will be launched with the url. 43 | 3. If you only have one browser running, the link will be opened in that browser. 44 | 4. If you have configured a default browser, it will be asked to open the url. 45 | 3. Otherwise, you will be presented with a simple window asking you which browser you want to use. 46 | 47 | The url is shown at the top of the window, and if it matches a list of known url shorteners, BrowserPicker will expand this address and show you the real one after a short delay. 48 | If you do not want BrowserPicker to perform this operation (it will call the internet), you may disable this feature in the settings. 49 | 50 | ### Copy url 51 | You can click the clipboard icon at the top to copy the url without opening it 52 | 53 | ### Edit url 54 | You can click the pencil icon at the top of the window to edit or copy the url before visiting it or cancelling: 55 | 56 | ![Screenshot of truncated url being edited](docs/selector_edit_url.png) 57 | ![Screenshot of updated url in window](docs/selector_edited_url.png) 58 | 59 | ### Keyboard shortcuts 60 | 61 | When this window is open and has focus, you can use the following keyboard shortcuts: 62 | 63 | `[enter]` or `[1]` Pick the first browser in the list 64 | 65 | `[2]` Pick the second browser in the list 66 | 67 | ... 68 | 69 | `[9]` Pick the ninth browser in the list 70 | 71 | If you keep `[alt]` pressed while hitting one of these, the browser will be opened in privacy mode. 72 | 73 | `[esc]` Abort and close window 74 | 75 | If you click outside the window such that it loses focus, it will close without opening the url in any browser. 76 | 77 | Each browser that supports it, has a blue shield button on the right side. 78 | Browsers currently supporting privacy mode are firefox, internet explorer, chrome, and edge. 79 | 80 | Currently running browsers will have their name in bold, whilst browsers not currently running will have their names in cursive. 81 | 82 | As you use the application, it keeps count of how many times you selected each browser. This information is used to show you your browsers in your preferred order automatically. 83 | 84 | At the bottom of the window, there is a checkbox to enable "always ask" and a hyperlink to open settings. 85 | 86 | ## Settings 87 | By simply launching BrowserPicker from the start menu or double clicking the `BrowserPicker.exe` file, you will be presented with a GUI to configure the behaviour. 88 | The configuration is saved in the Windows registry: `HKEY_CURRENT_USER\Software\BrowserPicker`, if you ever need to manually edit it or make a backup. 89 | 90 | ![Screenshot of the browser configuration interface with three browsers](docs/config_list.png) 91 | 92 | ### Browsers 93 | 94 | The browser list shows you the browsers BrowserPicker has been configured or detected to use. 95 | 96 | #### Disabling browsers 97 | You can disable a browser by clicking `Enabled`, this will hide the browser from the selection list. 98 | 99 | ![Screenshot of a red hyperlink saying Disabled](docs/config_disabled.png) 100 | 101 | #### Removing browsers 102 | If you click the red X, you may remove a browser. 103 | 104 | Do note that if it was automatically detected, it will return to the list the next time auto configuration is performed. 105 | 106 | #### Automatic configuration 107 | The `Refresh broser list` function gets automatically executed in the background when you use BrowserPicker. 108 | This helps it discovering newly installed browsers, in case a new browser has been installed, 109 | 110 | #### Manually adding browser 111 | You may click the hyperlink `Add browser` to open a popup where you may manually add a browser that has not been detected - or some other tool that isn't a browser. 112 | 113 | You can click the buttons behind the input boxes to bring up the file picker interface of windows to select the executable or icon file you want to use. 114 | 115 | ![Screenshot of user interface for entering parameters for a new browser](docs/config_add_browser.png) 116 | ![Screenshot of filled user interface](docs/config_add_browser_exe_picked.png) 117 | 118 | ![List of configured browsers including notepad as an option](docs/config_list_with_notepad.png) 119 | 120 | If you browse for the command first, the application will assume the executable also has an icon, and prefill that box. 121 | 122 | The name of the application will be attempted to be set automatically based on information in the executable. 123 | 124 | ##### Chrome profiles 125 | 126 | Tip for Chrome Users: If you are using multiple Chrome profiles, by default if you choose Chrome it will launch in the last 127 | profile you launched Chrome with. To make it possibe for browser picker to select a profile you can create a new browser 128 | for each profile, set the program to the chrome executable, and add a command line argument to specify which profile to launch: 129 | `--profile-directory=Default` for the first profile, `--profile-directory="Profile 1"` for the second profile, and so on. 130 | 131 | Please note that arguments with spaces do require "" around them to be properly passed to chrome. 132 | 133 | ##### Firefox profiles 134 | 135 | Similar configuration should be possible for firefox. 136 | 137 | ### Behaviour 138 | This tab contains various settings that govern how BrowserPicker operates. 139 | 140 | ![Screenshot of all the options under the behaviour tab](docs/config_behaviour.png) 141 | 142 | > [ ] Turn off transparency 143 | 144 | This will make BrowserPicker have a simple black background, to help with legibility 145 | 146 | > [ ] Always show browser selection window 147 | 148 | This option is also available on the browser selection window. When enabled, BrowserPicker will always ask the user to make a choice. 149 | 150 | > [ ] When no default is configured matching the url, use: [__v] 151 | 152 | When configured, BrowserPicker will always use this browser unless a default browser has been configured for that url. 153 | 154 | > [ ] Always ask when no default is matching url 155 | 156 | This option makes it so BrowserPicker will only pick matched default browsers and otherwise show the selection window. 157 | 158 | > [ ] Disable url resolution 159 | 160 | 161 | > [ ] Ignore defaults when browser is not running 162 | 163 | When enabled, configured default browsers only apply when they are already running. 164 | 165 | > [ ] Update order in browser list based on usage 166 | 167 | This option will make your list of browsers automatically sorted by how often you pick them. 168 | 169 | > [ ] Disallow network activity 170 | 171 | BrowserPicker may perform DNS and HTTP calls to probe the specified url in order to check if the url redirects elsewhere. 172 | This option turns this feature off, preventing BrowserPicker to call the network when you launch a url. 173 | 174 | > URL resolution timeout: [_____] 175 | 176 | You may adjust for how long BrowserPicker attempts to resolve an url here. 177 | 178 | ### Defaults 179 | The defaults tab lets you configure rules to map certain urls to certain browsers. 180 | 181 | ![Illustration of the empty list of default browser choices](docs/config_defaults_empty.png) 182 | 183 | ##### Match types 184 | There exists four different match types, but you cannot use Default, that is reserved for use elsewhere. 185 | The option will eventually get hidden in the interface, but for now it becomes Hostname when selected. 186 | 187 | ![Illustration of a dropdown showing the four match types Hostname, Prefix, Regex and Default](docs/config_defaults_match_type.png) 188 | 189 | ###### Hostname match 190 | The pattern will match the end of the hostname part of the url, ie. `hub.com` would match `https://www.github.com/mortenn/BrowserPicker`, but not `https://example.com/cgi-bin/hub.com` 191 | 192 | ###### Prefix match 193 | The pattern will match the beginning of the url, ie. `https://github.com/mortenn` would match `https://github.com/mortenn/BrowserPicker` but not `https://www.github.com/mortenn/BrowserPicker` 194 | 195 | ###### Regex match 196 | The pattern is a .NET regular expression and will be executed against the url, see [.NET regular expressions](https://learn.microsoft.com/en-us/dotnet/standard/base-types/regular-expressions) for details. 197 | 198 | ##### Browser 199 | The selected browser will be the one to launch for matched urls. 200 | 201 | ![Illustration of a dropdown showing each browser icon](docs/config_defaults_browsers.png) 202 | 203 | ### Test defaults 204 | There is even a handy dandy tool for verifying your settings, 205 | just paste that url into the big white text box and get instant feedback on the browser selection process: 206 | 207 | ![Example of the test defaults interface in use](docs/config_defaults_test_no_match.png) 208 | 209 | ### Logging 210 | BrowserPicker uses ILogger with EventLog support. 211 | 212 | To get detailed logs, please either change appsettings.json or set the environment variable `Logging__EventLog__LogLevel__BrowserPicker` to either `Information` or `Debug` 213 | By default, only warnings or higher level events get logged. 214 | 215 | If you are using the archived version rather than the installer package, 216 | you will need to run this powershell command before logs will appear: 217 | 218 | ```New-EventLog -LogName Application -Source BrowserPicker``` 219 | ` 220 | -------------------------------------------------------------------------------- /src/BrowserPicker.App/.editorconfig: -------------------------------------------------------------------------------- 1 | # To learn more about .editorconfig see https://aka.ms/editorconfigdocs 2 | ############################### 3 | # Core EditorConfig Options # 4 | ############################### 5 | root = true 6 | # All files 7 | [*] 8 | indent_style = tab 9 | # Code files 10 | [*.{cs,csx,vb,vbx}] 11 | # indent_size = 2 12 | insert_final_newline = true 13 | charset = utf-8-bom 14 | ############################### 15 | # .NET Coding Conventions # 16 | ############################### 17 | [*.{cs,vb}] 18 | # Organize usings 19 | dotnet_sort_system_directives_first = true 20 | # this. preferences 21 | dotnet_style_qualification_for_field = false:silent 22 | dotnet_style_qualification_for_property = false:silent 23 | dotnet_style_qualification_for_method = false:silent 24 | dotnet_style_qualification_for_event = false:silent 25 | # Language keywords vs BCL types preferences 26 | dotnet_style_predefined_type_for_locals_parameters_members = true:silent 27 | dotnet_style_predefined_type_for_member_access = true:silent 28 | # Parentheses preferences 29 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent 30 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent 31 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent 32 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent 33 | # Modifier preferences 34 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent 35 | dotnet_style_readonly_field = true:suggestion 36 | # Expression-level preferences 37 | dotnet_style_object_initializer = true:suggestion 38 | dotnet_style_collection_initializer = true:suggestion 39 | dotnet_style_explicit_tuple_names = true:suggestion 40 | dotnet_style_null_propagation = true:suggestion 41 | dotnet_style_coalesce_expression = true:suggestion 42 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent 43 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 44 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 45 | dotnet_style_prefer_auto_properties = true:silent 46 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 47 | dotnet_style_prefer_conditional_expression_over_return = true:silent 48 | ############################### 49 | # Naming Conventions # 50 | ############################### 51 | # Style Definitions 52 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case 53 | # Use PascalCase for constant fields 54 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion 55 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields 56 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style 57 | dotnet_naming_symbols.constant_fields.applicable_kinds = field 58 | dotnet_naming_symbols.constant_fields.applicable_accessibilities = * 59 | dotnet_naming_symbols.constant_fields.required_modifiers = const 60 | ############################### 61 | # C# Coding Conventions # 62 | ############################### 63 | [*.cs] 64 | # var preferences 65 | csharp_style_var_for_built_in_types = true:silent 66 | csharp_style_var_when_type_is_apparent = true:silent 67 | csharp_style_var_elsewhere = true:silent 68 | # Expression-bodied members 69 | csharp_style_expression_bodied_methods = false:silent 70 | csharp_style_expression_bodied_constructors = false:silent 71 | csharp_style_expression_bodied_operators = false:silent 72 | csharp_style_expression_bodied_properties = true:silent 73 | csharp_style_expression_bodied_indexers = true:silent 74 | csharp_style_expression_bodied_accessors = true:silent 75 | # Pattern matching preferences 76 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 77 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 78 | # Null-checking preferences 79 | csharp_style_throw_expression = true:suggestion 80 | csharp_style_conditional_delegate_call = true:suggestion 81 | # Modifier preferences 82 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion 83 | # Expression-level preferences 84 | csharp_prefer_braces = true:silent 85 | csharp_style_deconstructed_variable_declaration = true:suggestion 86 | csharp_prefer_simple_default_expression = true:suggestion 87 | csharp_style_pattern_local_over_anonymous_function = true:suggestion 88 | csharp_style_inlined_variable_declaration = true:suggestion 89 | ############################### 90 | # C# Formatting Rules # 91 | ############################### 92 | # New line preferences 93 | csharp_new_line_before_open_brace = all 94 | csharp_new_line_before_else = true 95 | csharp_new_line_before_catch = true 96 | csharp_new_line_before_finally = true 97 | csharp_new_line_before_members_in_object_initializers = true 98 | csharp_new_line_before_members_in_anonymous_types = true 99 | csharp_new_line_between_query_expression_clauses = true 100 | # Indentation preferences 101 | csharp_indent_case_contents = true 102 | csharp_indent_switch_labels = true 103 | csharp_indent_labels = flush_left 104 | # Space preferences 105 | csharp_space_after_cast = false 106 | csharp_space_after_keywords_in_control_flow_statements = true 107 | csharp_space_between_method_call_parameter_list_parentheses = false 108 | csharp_space_between_method_declaration_parameter_list_parentheses = false 109 | csharp_space_between_parentheses = false 110 | csharp_space_before_colon_in_inheritance_clause = true 111 | csharp_space_after_colon_in_inheritance_clause = true 112 | csharp_space_around_binary_operators = before_and_after 113 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 114 | csharp_space_between_method_call_name_and_opening_parenthesis = false 115 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 116 | # Wrapping preferences 117 | csharp_preserve_single_line_statements = true 118 | csharp_preserve_single_line_blocks = true 119 | ############################### 120 | # VB Coding Conventions # 121 | ############################### 122 | [*.vb] 123 | # Modifier preferences 124 | visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion 125 | -------------------------------------------------------------------------------- /src/BrowserPicker.App/App.xaml: -------------------------------------------------------------------------------- 1 |  4 | 5 | -------------------------------------------------------------------------------- /src/BrowserPicker.App/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using System.Windows; 8 | using System.Windows.Threading; 9 | using BrowserPicker.View; 10 | using BrowserPicker.ViewModel; 11 | 12 | namespace BrowserPicker; 13 | 14 | public partial class App 15 | { 16 | private const int LoadingWindowDelayMilliseconds = 300; 17 | 18 | /// 19 | /// This CancellationToken gets cancelled when the application exits 20 | /// 21 | private static CancellationTokenSource ApplicationCancellationToken { get; } = new(); 22 | 23 | public static IBrowserPickerConfiguration Settings { get; set; } = null!; 24 | 25 | private class InvalidUTF8Patch : EncodingProvider 26 | { 27 | public override Encoding? GetEncoding(int codepage) 28 | { 29 | return null; 30 | } 31 | 32 | public override Encoding? GetEncoding(string name) 33 | { 34 | return name.ToLowerInvariant().Replace("-", "") switch 35 | { 36 | "utf8" => Encoding.UTF8, 37 | _ => null 38 | }; 39 | } 40 | } 41 | 42 | public App() 43 | { 44 | Encoding.RegisterProvider(new InvalidUTF8Patch()); 45 | Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); 46 | BackgroundTasks.Add(Settings); 47 | 48 | // Basic unhandled exception catchment 49 | AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException; 50 | DispatcherUnhandledException += OnDispatcherUnhandledException; 51 | 52 | // Get command line arguments and initialize ViewModel 53 | var arguments = Environment.GetCommandLineArgs().Skip(1).ToList(); 54 | try 55 | { 56 | ViewModel = new ApplicationViewModel(arguments, Settings); 57 | if (ViewModel.Url.TargetURL != null) 58 | { 59 | BackgroundTasks.Add(ViewModel.Url); 60 | } 61 | } 62 | catch (Exception exception) 63 | { 64 | ShowExceptionReport(exception); 65 | } 66 | } 67 | 68 | protected override void OnStartup(StartupEventArgs e) 69 | { 70 | var worker = StartupBackgroundTasks(); 71 | worker.ContinueWith(CheckBackgroundTasks); 72 | } 73 | 74 | /// 75 | /// This method should never be called, as the StartupBackgroundTasks has robust exception handling 76 | /// 77 | private static void CheckBackgroundTasks(Task task) 78 | { 79 | if (task.IsFaulted) 80 | { 81 | MessageBox.Show( 82 | task.Exception?.ToString() ?? string.Empty, 83 | "Error", 84 | MessageBoxButton.OK, 85 | MessageBoxImage.Error 86 | ); 87 | } 88 | } 89 | 90 | private async Task StartupBackgroundTasks() 91 | { 92 | // Something failed during startup, abort. 93 | if (ViewModel == null) 94 | { 95 | return; 96 | } 97 | CancellationTokenSource? urlLookup = null; 98 | Task? loadingWindow = null; 99 | try 100 | { 101 | // Hook up shutdown on the viewmodel to shut down the application 102 | ViewModel.OnShutdown += ExitApplication; 103 | 104 | // Catch user switching to another window 105 | Deactivated += (_, _) => ViewModel.OnDeactivated(); 106 | 107 | long_running_processes = RunLongRunningProcesses(); 108 | 109 | // Open in configuration mode if user started BrowserPicker directly 110 | if (string.IsNullOrWhiteSpace(ViewModel.Url.TargetURL)) 111 | { 112 | ShowMainWindow(); 113 | return; 114 | } 115 | 116 | // Create a CancellationToken that cancels after the lookup timeout 117 | // to limit the amount of time spent looking up underlying URLs 118 | urlLookup = ViewModel.Configuration.GetUrlLookupTimeout(); 119 | try 120 | { 121 | // Show LoadingWindow after a small delay 122 | // Goal is to avoid flicker for fast loading sites but to show progress for sites that take longer 123 | loadingWindow = ShowLoadingWindow(urlLookup.Token); 124 | 125 | // Wait for long-running processes in case they finish quickly 126 | await Task.Run(() => long_running_processes.Wait(ApplicationCancellationToken.Token), urlLookup.Token); 127 | 128 | // cancel the token to prevent showing LoadingWindow if it is not needed and has not been shown already 129 | await urlLookup.CancelAsync(); 130 | 131 | ShowMainWindow(); 132 | 133 | // close loading window if it got opened 134 | var waited = await loadingWindow; 135 | waited?.Close(); 136 | } 137 | catch (TaskCanceledException) 138 | { 139 | // Open up the browser picker window 140 | ShowMainWindow(); 141 | } 142 | } 143 | catch (Exception exception) 144 | { 145 | try { if (urlLookup != null) await urlLookup.CancelAsync(); } catch { /* ignored */ } 146 | try { if (loadingWindow != null) (await loadingWindow)?.Close(); } catch { /* ignored */ } 147 | try { if (ViewModel != null) ViewModel.OnShutdown -= ExitApplication; } catch { /* ignored */ } 148 | ShowExceptionReport(exception); 149 | } 150 | } 151 | 152 | private static async Task RunLongRunningProcesses() 153 | { 154 | try 155 | { 156 | var tasks = BackgroundTasks.Select(task => task.Start(ApplicationCancellationToken.Token)).ToArray(); 157 | await Task.WhenAll(tasks); 158 | foreach (var task in tasks) 159 | { 160 | await task; 161 | } 162 | } 163 | catch (TaskCanceledException) 164 | { 165 | // ignored 166 | } 167 | } 168 | 169 | /// 170 | /// Tells the ViewModel it can initialize and then show the browser list window 171 | /// 172 | private void ShowMainWindow() 173 | { 174 | ViewModel?.Initialize(); 175 | MainWindow = new MainWindow 176 | { 177 | DataContext = ViewModel 178 | }; 179 | MainWindow.Show(); 180 | MainWindow.Focus(); 181 | } 182 | 183 | /// 184 | /// Shows the loading message window after a short delay, to let the user know we are in fact working on it 185 | /// 186 | /// token that will cancel when the loading is complete or timed out 187 | /// The loading message window, so it may be closed. 188 | private static async Task ShowLoadingWindow(CancellationToken cancellationToken) 189 | { 190 | try 191 | { 192 | await Task.Delay(LoadingWindowDelayMilliseconds, cancellationToken); 193 | } 194 | catch (TaskCanceledException) 195 | { 196 | return null; 197 | } 198 | var window = new LoadingWindow(); 199 | window.Show(); 200 | return window; 201 | } 202 | 203 | private static void ShowExceptionReport(Exception exception) 204 | { 205 | var viewModel = new ExceptionViewModel(exception); 206 | var window = new ExceptionReport(); 207 | viewModel.OnWindowClosed += (_, _) => window.Close(); 208 | window.DataContext = viewModel; 209 | window.Show(); 210 | window.Focus(); 211 | } 212 | 213 | /// 214 | /// Bare-bones exception handler 215 | /// 216 | /// 217 | /// 218 | private static void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs unhandledException) 219 | { 220 | ApplicationCancellationToken.Cancel(); 221 | _ = MessageBox.Show(unhandledException.ExceptionObject.ToString()); 222 | } 223 | 224 | private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e) 225 | { 226 | ApplicationCancellationToken.Cancel(); 227 | _ = MessageBox.Show(e.Exception.ToString()); 228 | } 229 | 230 | private static void ExitApplication(object? sender, EventArgs args) 231 | { 232 | ApplicationCancellationToken.Cancel(); 233 | try 234 | { 235 | long_running_processes?.Wait(); 236 | } 237 | catch (TaskCanceledException) 238 | { 239 | // ignore; 240 | } 241 | Current.Shutdown(); 242 | } 243 | 244 | public ApplicationViewModel? ViewModel { get; } 245 | public static IServiceProvider Services { get; set; } = null!; 246 | 247 | private static readonly List BackgroundTasks = []; 248 | private static Task? long_running_processes; 249 | } 250 | -------------------------------------------------------------------------------- /src/BrowserPicker.App/BrowserPicker.App.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | WinExe 4 | WinExe 5 | BrowserPicker 6 | BrowserPicker 7 | Dynamically pick browser on the fly 8 | true 9 | true 10 | Resources\web_icon.ico 11 | False 12 | enable 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | MSBuild:Compile 33 | Wpf 34 | Designer 35 | 36 | 37 | 38 | 39 | PreserveNewest 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/BrowserPicker.App/Converter/IconConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Drawing; 4 | using System.Drawing.Imaging; 5 | using System.Globalization; 6 | using System.IO; 7 | using System.Windows; 8 | using System.Windows.Data; 9 | using System.Windows.Media.Imaging; 10 | 11 | namespace BrowserPicker.Converter; 12 | 13 | public sealed class IconFileToImageConverter : IValueConverter 14 | { 15 | public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) 16 | { 17 | if (value is byte[]) 18 | return value; 19 | 20 | if (value?.ToString() is not { } iconPath) 21 | return GetDefaultIcon(); 22 | 23 | if (cache.TryGetValue(iconPath, out var cachedIcon)) 24 | { 25 | return cachedIcon; 26 | } 27 | 28 | if (string.IsNullOrWhiteSpace(iconPath)) 29 | { 30 | return GetDefaultIcon(); 31 | } 32 | 33 | var realIconPath = iconPath.Trim('"', '\'', ' ', '\t', '\r', '\n'); 34 | try 35 | { 36 | if (!File.Exists(realIconPath) && realIconPath.Contains('%')) 37 | realIconPath = Environment.ExpandEnvironmentVariables(realIconPath); 38 | 39 | if (!File.Exists(realIconPath)) 40 | return GetDefaultIcon(); 41 | 42 | Stream icon; 43 | if (realIconPath.EndsWith(".exe") || realIconPath.EndsWith(".dll")) 44 | { 45 | var iconData = Icon.ExtractAssociatedIcon(realIconPath)?.ToBitmap(); 46 | if (iconData == null) 47 | return GetDefaultIcon(); 48 | icon = new MemoryStream(); 49 | iconData.Save(icon, ImageFormat.Png); 50 | } 51 | else 52 | { 53 | icon = File.Open(realIconPath, FileMode.Open, FileAccess.Read, FileShare.Read); 54 | } 55 | 56 | cache.Add(iconPath, BitmapFrame.Create(icon)); 57 | return cache[iconPath]; 58 | } 59 | catch 60 | { 61 | // ignored 62 | } 63 | return GetDefaultIcon(); 64 | } 65 | 66 | public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) 67 | { 68 | return null; 69 | } 70 | 71 | private static object GetDefaultIcon() 72 | { 73 | return Application.Current.TryFindResource("DefaultIcon"); 74 | } 75 | 76 | private readonly Dictionary cache = []; 77 | } 78 | -------------------------------------------------------------------------------- /src/BrowserPicker.App/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows; 3 | using BrowserPicker.View; 4 | using BrowserPicker.Windows; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.Hosting; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace BrowserPicker; 10 | 11 | internal static class Program 12 | { 13 | [STAThread] 14 | private static void Main(string[] args) 15 | { 16 | var builder = Host.CreateDefaultBuilder() 17 | .ConfigureServices(services => 18 | { 19 | services.AddSingleton(); 20 | services.AddSingleton(); 21 | services.AddSingleton(); 22 | }) 23 | .ConfigureLogging(logging => logging.AddEventLog( 24 | settings => settings.SourceName = "BrowserPicker" 25 | )); 26 | 27 | var host = builder.Build(); 28 | App.Services = host.Services; 29 | App.Settings = host.Services.GetRequiredService(); 30 | var app = host.Services.GetRequiredService(); 31 | 32 | var resourceDictionary = new ResourceDictionary 33 | { 34 | Source = new Uri( 35 | "pack://application:,,,/BrowserPicker;component/Resources/ResourceDictionary.xaml", 36 | UriKind.Absolute 37 | ) 38 | }; 39 | app.Resources.MergedDictionaries.Add(resourceDictionary); 40 | var logger = host.Services.GetRequiredService>(); 41 | logger.LogApplicationLaunched(args); 42 | 43 | app.Run(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/BrowserPicker.App/Properties/PublishProfiles/FolderProfile.pubxml: -------------------------------------------------------------------------------- 1 |  2 | 5 | 6 | 7 | SignedRelease 8 | Any CPU 9 | bin\SignedRelease\net6.0-windows\win-x64\publish\ 10 | FileSystem 11 | net6.0-windows 12 | win-x64 13 | false 14 | True 15 | 16 | -------------------------------------------------------------------------------- /src/BrowserPicker.App/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Configuration UI": { 4 | "commandName": "Project" 5 | }, 6 | "rutracker": { 7 | "commandName": "Project", 8 | "commandLineArgs": "https://rutracker.org/forum/viewtopic.php?t=6350891" 9 | }, 10 | "github url": { 11 | "commandName": "Project", 12 | "commandLineArgs": "https://github.com/mortenn/BrowserPicker" 13 | }, 14 | "file url": { 15 | "commandName": "Project", 16 | "commandLineArgs": "file://c:/windows/win.ini" 17 | }, 18 | "unc url": { 19 | "commandName": "Project", 20 | "commandLineArgs": "file://server/share/file.txt" 21 | }, 22 | "long url": { 23 | "commandName": "Project", 24 | "commandLineArgs": "https://extremely-long-domain-example-for-design-time-use.some-long-domain-name.com/with-a-long-query-path/Lorem-ipsum-dolor-sit-amet,-consectetur-adipiscing-elit.-Integer-fermentum,-ipsum-quis-cursus-finibus,-turpis-lectus-tincidunt-elit,-eget-consectetur-tellus-leo-eget-neque.-Praesent.?and-a-param-for-good-measure=42" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/BrowserPicker.App/Resources/ResourceDictionary.xaml: -------------------------------------------------------------------------------- 1 |  3 | pack://application:,,,/Resources/web_icon.png 4 | pack://application:,,,/Resources/privacy.png 5 | -------------------------------------------------------------------------------- /src/BrowserPicker.App/Resources/privacy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenn/BrowserPicker/e409f39d885b51f0b385664a1a49cb2177c25d31/src/BrowserPicker.App/Resources/privacy.png -------------------------------------------------------------------------------- /src/BrowserPicker.App/Resources/web_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenn/BrowserPicker/e409f39d885b51f0b385664a1a49cb2177c25d31/src/BrowserPicker.App/Resources/web_icon.ico -------------------------------------------------------------------------------- /src/BrowserPicker.App/Resources/web_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mortenn/BrowserPicker/e409f39d885b51f0b385664a1a49cb2177c25d31/src/BrowserPicker.App/Resources/web_icon.png -------------------------------------------------------------------------------- /src/BrowserPicker.App/View/BrowserEditor.xaml: -------------------------------------------------------------------------------- 1 |  15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | -------------------------------------------------------------------------------- /src/BrowserPicker.App/View/BrowserList.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using BrowserPicker.ViewModel; 3 | 4 | namespace BrowserPicker.View; 5 | 6 | /// 7 | /// Interaction logic for BrowserList.xaml 8 | /// 9 | public partial class BrowserList 10 | { 11 | public BrowserList() 12 | { 13 | InitializeComponent(); 14 | } 15 | 16 | private void ButtonBase_OnClick(object sender, RoutedEventArgs e) 17 | { 18 | e.Handled = true; 19 | } 20 | 21 | private void Editor_KeyUp(object sender, System.Windows.Input.KeyEventArgs e) 22 | { 23 | if (e.Key is not (System.Windows.Input.Key.Enter)) 24 | { 25 | return; 26 | } 27 | 28 | if (DataContext is ApplicationViewModel app) 29 | { 30 | app.EndEdit.Execute(null); 31 | } 32 | e.Handled = true; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/BrowserPicker.App/View/Configuration.xaml.cs: -------------------------------------------------------------------------------- 1 | namespace BrowserPicker.View; 2 | 3 | /// 4 | /// Interaction logic for Configuration.xaml 5 | /// 6 | public partial class Configuration 7 | { 8 | public Configuration() 9 | { 10 | InitializeComponent(); 11 | } 12 | 13 | private void CheckBox_Checked(object sender, System.Windows.RoutedEventArgs e) 14 | { 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/BrowserPicker.App/View/ExceptionReport.xaml: -------------------------------------------------------------------------------- 1 |  11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 |