From 7ebb00025344a4e30c1b8188c7cd98b625eb6d1b Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Wed, 22 Jan 2025 21:51:35 +1100 Subject: [PATCH] WIP --- .../docs/guides/custom-protocol-schemes.mdx | 189 ++++++++++++++++++ v3/internal/commands/build-assets.go | 9 + v3/internal/commands/build_assets/config.yml | 10 +- .../windows/nsis/project.nsi.tmpl | 2 + .../darwin/Info.dev.plist.tmpl | 17 ++ .../darwin/Info.plist.tmpl | 17 ++ .../windows/nsis/wails_tools.nsh.tmpl | 29 +++ v3/pkg/application/application_options.go | 2 + 8 files changed, 274 insertions(+), 1 deletion(-) create mode 100644 docs/src/content/docs/guides/custom-protocol-schemes.mdx diff --git a/docs/src/content/docs/guides/custom-protocol-schemes.mdx b/docs/src/content/docs/guides/custom-protocol-schemes.mdx new file mode 100644 index 000000000..6e8f58307 --- /dev/null +++ b/docs/src/content/docs/guides/custom-protocol-schemes.mdx @@ -0,0 +1,189 @@ +# Custom Protocol Scheme association + +Custom Protocols feature allows you to associate specific custom protocol with your app so that when users open links with this protocol, +your app is launched to handle them. This can be particularly useful to connect your desktop app with your web app. +In this guide, we'll walk through the steps to implement custom protocols in Wails app. + + +## Set Up Custom Protocol Schemes Association: +To set up custom protocol, you need to modify your application's wails.json file. +In "info" section add a "protocols" section specifying the protocols your app should be associated with. + +For example: +```json +{ + "info": { + "protocols": [ + { + "scheme": "myapp", + "description": "My App Protocol", + "role": "Editor" + } + ] + } +} +``` + +| Property | Description | +|:------------|:--------------------------------------------------------------------------------------| +| scheme | Custom Protocol scheme. e.g. myapp | +| description | Windows-only. The description. | +| role | macOS-only. The app’s role with respect to the type. Corresponds to CFBundleTypeRole. | + +## Platform Specifics: + +### macOS +When you open custom protocol with your app, the system will launch your app and call the `OnUrlOpen` function in your Wails app. Example: +```go title="main.go" +func main() { + // Create application with options + err := wails.Run(&options.App{ + Title: "wails-open-file", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, + BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, + Mac: &mac.Options{ + OnUrlOpen: func(url string) { println(url) }, + }, + Bind: []interface{}{ + app, + }, + }) + + if err != nil { + println("Error:", err.Error()) + } +} +``` + + +### Windows +On Windows Custom Protocol Schemes is supported only with NSIS installer. During installation, the installer will create a +registry entry for your schemes. When you open url with your app, new instance of app is launched and url is passed +as argument to your app. To handle this you should parse command line arguments in your app. Example: +```go title="main.go" +func main() { + argsWithoutProg := os.Args[1:] + + if len(argsWithoutProg) != 0 { + println("launchArgs", argsWithoutProg) + } +} +``` + +You also can enable single instance lock for your app. In this case, when you open url with your app, new instance of app is not launched +and arguments are passed to already running instance. Check single instance lock guide for details. Example: +```go title="main.go" +func main() { + // Create application with options + err := wails.Run(&options.App{ + Title: "wails-open-file", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, + BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, + SingleInstanceLock: &options.SingleInstanceLock{ + UniqueId: "e3984e08-28dc-4e3d-b70a-45e961589cdc", + OnSecondInstanceLaunch: app.onSecondInstanceLaunch, + }, + Bind: []interface{}{ + app, + }, + }) +} +``` + +### Linux +Currently, Wails doesn't support bundling for Linux. So, you need to create file associations manually. +For example if you distribute your app as a .deb package, you can create file associations by adding required files in you bundle. +You can use [nfpm](https://nfpm.goreleaser.com/) to create .deb package for your app. + +1. Create a .desktop file for your app and specify file associations there (note that `%u` is important in Exec). Example: +```ini +[Desktop Entry] +Categories=Office +Exec=/usr/bin/wails-open-file %u +Icon=wails-open-file.png +Name=wails-open-file +Terminal=false +Type=Application +MimeType=x-scheme-handler/myapp; +``` + +2. Prepare postInstall/postRemove scripts for your package. Example: +```sh +# reload desktop database to load app in list of available +update-desktop-database /usr/share/applications +``` +3. Configure nfpm to use your scripts and files. Example: +```yaml +name: "wails-open-file" +arch: "arm64" +platform: "linux" +version: "1.0.0" +section: "default" +priority: "extra" +maintainer: "FooBarCorp " +description: "Sample Package" +vendor: "FooBarCorp" +homepage: "http://example.com" +license: "MIT" +contents: +- src: ../bin/wails-open-file + dst: /usr/bin/wails-open-file +- src: ./main.desktop + dst: /usr/share/applications/wails-open-file.desktop +- src: ../appicon.svg + dst: /usr/share/icons/hicolor/scalable/apps/wails-open-file.svg +# copy icons to Yaru theme as well. For some reason Ubuntu didn't pick up fileicons from hicolor theme +- src: ../appicon.svg + dst: /usr/share/icons/Yaru/scalable/apps/wails-open-file.svg +scripts: + postinstall: ./postInstall.sh + postremove: ./postRemove.sh +``` +6. Build your .deb package using nfpm: +```sh +nfpm pkg --packager deb --target . +``` +7. Now when your package is installed, your app will be associated with custom protocol scheme. When you open url with your app, +new instance of app is launched and file path is passed as argument to your app. +To handle this you should parse command line arguments in your app. Example: +```go title="main.go" +func main() { + argsWithoutProg := os.Args[1:] + + if len(argsWithoutProg) != 0 { + println("launchArgs", argsWithoutProg) + } +} +``` + +You also can enable single instance lock for your app. In this case, when you open url with your app, new instance of app is not launched +and arguments are passed to already running instance. Check single instance lock guide for details. Example: +```go title="main.go" +func main() { + // Create application with options + err := wails.Run(&options.App{ + Title: "wails-open-file", + Width: 1024, + Height: 768, + AssetServer: &assetserver.Options{ + Assets: assets, + }, + BackgroundColour: &options.RGBA{R: 27, G: 38, B: 54, A: 1}, + SingleInstanceLock: &options.SingleInstanceLock{ + UniqueId: "e3984e08-28dc-4e3d-b70a-45e961589cdc", + OnSecondInstanceLaunch: app.onSecondInstanceLaunch, + }, + Bind: []interface{}{ + app, + }, + }) +} +``` \ No newline at end of file diff --git a/v3/internal/commands/build-assets.go b/v3/internal/commands/build-assets.go index 12b5694fd..6fd2c69df 100644 --- a/v3/internal/commands/build-assets.go +++ b/v3/internal/commands/build-assets.go @@ -126,6 +126,7 @@ type FileAssociation struct { type UpdateConfig struct { UpdateBuildAssetsOptions FileAssociations []FileAssociation `yaml:"fileAssociations"` + Protocols []Protocol `yaml:"protocols"` } type WailsConfig struct { @@ -139,6 +140,13 @@ type WailsConfig struct { Version string `yaml:"version"` } `yaml:"info"` FileAssociations []FileAssociation `yaml:"fileAssociations"` + Protocols []Protocol `yaml:"protocols"` +} + +type Protocol struct { + Scheme string `json:"scheme"` + Role string `json:"role"` + Description string `json:"description"` } func UpdateBuildAssets(options *UpdateBuildAssetsOptions) error { @@ -169,6 +177,7 @@ func UpdateBuildAssets(options *UpdateBuildAssetsOptions) error { options.ProductCopyright = wailsConfig.Info.Copyright options.ProductComments = wailsConfig.Info.Comments options.ProductVersion = wailsConfig.Info.Version + config.Protocols = wailsConfig.Protocols config.FileAssociations = wailsConfig.FileAssociations } diff --git a/v3/internal/commands/build_assets/config.yml b/v3/internal/commands/build_assets/config.yml index 37da25119..c4200b4d9 100644 --- a/v3/internal/commands/build_assets/config.yml +++ b/v3/internal/commands/build_assets/config.yml @@ -44,7 +44,7 @@ dev_mode: type: primary # File Associations -# More information at: https://v3alpha.wails.io/noit/done/yet +# More information at: https://v3.wails.io/not/done/yet fileAssociations: # - ext: wails # name: Wails @@ -57,6 +57,14 @@ fileAssociations: # iconName: jpegFileIcon # role: Editor +# Custom URL Protocols +# A list of custom URL protocols associated with this application +# More information at: https://v3.wails.io/not/done/yet +protocols: +# - scheme: "magnet" +# description: "Magnet URI Scheme" +# role: "Editor" + # Other data other: - name: My Other Data \ No newline at end of file diff --git a/v3/internal/commands/build_assets/windows/nsis/project.nsi.tmpl b/v3/internal/commands/build_assets/windows/nsis/project.nsi.tmpl index 404fae750..fb387b2c3 100644 --- a/v3/internal/commands/build_assets/windows/nsis/project.nsi.tmpl +++ b/v3/internal/commands/build_assets/windows/nsis/project.nsi.tmpl @@ -92,6 +92,7 @@ Section CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}" !insertmacro wails.associateFiles + !insertmacro wails.associateCustomProtocols !insertmacro wails.writeUninstaller SectionEnd @@ -107,6 +108,7 @@ Section "uninstall" Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk" !insertmacro wails.unassociateFiles + !insertmacro wails.unassociateCustomProtocols !insertmacro wails.deleteUninstaller SectionEnd diff --git a/v3/internal/commands/updatable_build_assets/darwin/Info.dev.plist.tmpl b/v3/internal/commands/updatable_build_assets/darwin/Info.dev.plist.tmpl index 47f39f537..cce0996ee 100644 --- a/v3/internal/commands/updatable_build_assets/darwin/Info.dev.plist.tmpl +++ b/v3/internal/commands/updatable_build_assets/darwin/Info.dev.plist.tmpl @@ -28,5 +28,22 @@ NSAllowsLocalNetworking + {{if .Protocols}} + CFBundleURLTypes + + {{range .Protocols}} + + CFBundleURLName + com.wails.{{.Scheme}} + CFBundleURLSchemes + + {{.Scheme}} + + CFBundleTypeRole + {{.Role}} + + {{end}} + + {{end}} \ No newline at end of file diff --git a/v3/internal/commands/updatable_build_assets/darwin/Info.plist.tmpl b/v3/internal/commands/updatable_build_assets/darwin/Info.plist.tmpl index 3dce3676c..d65101129 100644 --- a/v3/internal/commands/updatable_build_assets/darwin/Info.plist.tmpl +++ b/v3/internal/commands/updatable_build_assets/darwin/Info.plist.tmpl @@ -23,5 +23,22 @@ true NSHumanReadableCopyright {{.ProductCopyright}} + {{if .Protocols}} + CFBundleURLTypes + + {{range .Protocols}} + + CFBundleURLName + com.wails.{{.Scheme}} + CFBundleURLSchemes + + {{.Scheme}} + + CFBundleTypeRole + {{.Role}} + + {{end}} + + {{end}} \ No newline at end of file diff --git a/v3/internal/commands/updatable_build_assets/windows/nsis/wails_tools.nsh.tmpl b/v3/internal/commands/updatable_build_assets/windows/nsis/wails_tools.nsh.tmpl index ca5c11ccc..6a364cad8 100644 --- a/v3/internal/commands/updatable_build_assets/windows/nsis/wails_tools.nsh.tmpl +++ b/v3/internal/commands/updatable_build_assets/windows/nsis/wails_tools.nsh.tmpl @@ -215,4 +215,33 @@ RequestExecutionLevel "${REQUEST_EXECUTION_LEVEL}" !insertmacro APP_UNASSOCIATE "{{.Ext}}" "{{.Name}}" Delete "$INSTDIR\{{.IconName}}.ico" {{end}} +!macroend + +!macro CUSTOM_PROTOCOL_ASSOCIATE PROTOCOL DESCRIPTION ICON COMMAND + DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "" "${DESCRIPTION}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}" "URL Protocol" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\DefaultIcon" "" "${ICON}" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell" "" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open" "" "" + WriteRegStr SHELL_CONTEXT "Software\Classes\${PROTOCOL}\shell\open\command" "" "${COMMAND}" +!macroend + +!macro CUSTOM_PROTOCOL_UNASSOCIATE PROTOCOL + DeleteRegKey SHELL_CONTEXT "Software\Classes\${PROTOCOL}" +!macroend + +!macro wails.associateCustomProtocols + ; Create custom protocols associations + {{range .Protocols}} + !insertmacro CUSTOM_PROTOCOL_ASSOCIATE "{{.Scheme}}" "{{.Description}}" "$INSTDIR\${PRODUCT_EXECUTABLE},0" "$INSTDIR\${PRODUCT_EXECUTABLE} $\"%1$\"" + + {{end}} +!macroend + +!macro wails.unassociateCustomProtocols + ; Delete app custom protocol associations + {{range .Protocols}} + !insertmacro CUSTOM_PROTOCOL_UNASSOCIATE "{{.Scheme}}" + {{end}} !macroend \ No newline at end of file diff --git a/v3/pkg/application/application_options.go b/v3/pkg/application/application_options.go index 2f52996ed..3f2f8d75a 100644 --- a/v3/pkg/application/application_options.go +++ b/v3/pkg/application/application_options.go @@ -195,6 +195,8 @@ type MacOptions struct { ActivationPolicy ActivationPolicy // If set to true, the application will terminate when the last window is closed. ApplicationShouldTerminateAfterLastWindowClosed bool + // OnURLOpen is a function that is called when the user opens an associated url such as `magnet://l234i2li34h`. + OnUrlOpen func(filePath string) } /****** Windows Options *******/