+
Custom Protocol / Deep Link Test
+
+ This page demonstrates handling custom URL schemes (deep links).
+
+
+ Example Link:
+ Try opening this URL (e.g., by pasting it into your browser's address bar or using open your-app-scheme://... in terminal):
+
+ wailsexample://test/path?value=123&message=hello
+
+
+
+
Received URL:
+
Waiting for application to be opened via a custom URL...
+
+
+ `:console.error('Element with ID "app" not found.')});ne("frontend:ShowURL",e=>{console.log("frontend:ShowURL event received, data:",e),displayUrl(e.data)});window.displayUrl=function(e){const t=document.getElementById("received-url");t?t.textContent=e||"No URL received or an error occurred.":console.error("Element with ID 'received-url' not found in displayUrl.")};
diff --git a/v3/examples/custom-protocol-example/frontend/dist/assets/index-uDrhEpT0.css b/v3/examples/custom-protocol-example/frontend/dist/assets/index-uDrhEpT0.css
new file mode 100644
index 000000000..37a5c205c
--- /dev/null
+++ b/v3/examples/custom-protocol-example/frontend/dist/assets/index-uDrhEpT0.css
@@ -0,0 +1 @@
+:root{font-family:system-ui,Avenir,Helvetica,Arial,sans-serif;line-height:1.5;font-weight:400;color-scheme:light dark;color:#ffffffde;background-color:#242424;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}a{font-weight:500;color:#646cff;text-decoration:inherit}a:hover{color:#535bf2}body{font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Open Sans,Helvetica Neue,sans-serif;margin:0;background-color:#f7f7f7;color:#333;display:flex;justify-content:center;align-items:center;min-height:100vh;padding:20px;box-sizing:border-box}h1{color:#0056b3;font-size:1.8em;margin-bottom:.8em}#app{width:100%;max-width:600px;margin:auto}.container{background-color:#fff;padding:25px 30px;border-radius:8px;box-shadow:0 2px 10px #0000001a;text-align:left}p{line-height:1.6;font-size:1em;margin-bottom:1em}a{color:#007bff;text-decoration:none}a:hover{text-decoration:underline}.url-display{margin-top:25px;padding:15px;background-color:#e9ecef;border:1px solid #ced4da;border-radius:4px;font-family:Courier New,Courier,monospace;font-size:.95em;word-break:break-all}.label{font-weight:700;display:block;margin-bottom:8px;color:#495057}.logo{height:6em;padding:1.5em;will-change:filter;transition:filter .3s}.logo:hover{filter:drop-shadow(0 0 2em #646cffaa)}.logo.vanilla:hover{filter:drop-shadow(0 0 2em #f7df1eaa)}.card{padding:2em}.read-the-docs{color:#888}button{border-radius:8px;border:1px solid transparent;padding:.6em 1.2em;font-size:1em;font-weight:500;font-family:inherit;background-color:#1a1a1a;cursor:pointer;transition:border-color .25s}button:hover{border-color:#646cff}button:focus,button:focus-visible{outline:4px auto -webkit-focus-ring-color}@media (prefers-color-scheme: light){:root{color:#213547;background-color:#fff}a:hover{color:#747bff}button{background-color:#f9f9f9}}
diff --git a/v3/examples/custom-protocol-example/frontend/dist/index.html b/v3/examples/custom-protocol-example/frontend/dist/index.html
new file mode 100644
index 000000000..126962c69
--- /dev/null
+++ b/v3/examples/custom-protocol-example/frontend/dist/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
Custom Protocol / Deep Link Test
+
+ This page demonstrates handling custom URL schemes (deep links).
+
+
+ Example Link:
+ Try opening this URL (e.g., by pasting it into your browser's address bar or using open your-app-scheme://... in terminal):
+
+ wailsexample://test/path?value=123&message=hello
+
+
+
+
Received URL:
+
Waiting for application to be opened via a custom URL...
+
+
+ `;
+ } else {
+ console.error('Element with ID "app" not found.');
+ }
+});
+
+// Listen for the event from Go
+Events.On('frontend:ShowURL', (e) => {
+ console.log('frontend:ShowURL event received, data:', e);
+ displayUrl(e.data);
+});
+
+// Make displayUrl available globally just in case, though direct call from event is better
+window.displayUrl = function(url) {
+ const urlElement = document.getElementById('received-url');
+ if (urlElement) {
+ urlElement.textContent = url || "No URL received or an error occurred.";
+ } else {
+ console.error("Element with ID 'received-url' not found in displayUrl.");
+ }
+}
diff --git a/v3/examples/custom-protocol-example/frontend/src/style.css b/v3/examples/custom-protocol-example/frontend/src/style.css
new file mode 100644
index 000000000..0fb047c33
--- /dev/null
+++ b/v3/examples/custom-protocol-example/frontend/src/style.css
@@ -0,0 +1,142 @@
+:root {
+ font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
+ margin: 0; /* Reset default margin */
+ background-color: #f7f7f7;
+ color: #333;
+ display: flex; /* Use flexbox to center content */
+ justify-content: center; /* Center horizontally */
+ align-items: center; /* Center vertically */
+ min-height: 100vh; /* Full viewport height */
+ padding: 20px; /* Add some padding around the content */
+ box-sizing: border-box; /* Ensure padding doesn't expand body beyond viewport */
+}
+
+h1 {
+ color: #0056b3;
+ font-size: 1.8em;
+ margin-bottom: 0.8em;
+}
+
+#app {
+ width: 100%;
+ max-width: 600px;
+ margin: auto; /* This also helps in centering if body flex isn't enough or overridden */
+}
+
+.container {
+ background-color: #fff;
+ padding: 25px 30px;
+ border-radius: 8px;
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+ text-align: left;
+}
+
+p {
+ line-height: 1.6;
+ font-size: 1em;
+ margin-bottom: 1em;
+}
+
+a {
+ color: #007bff;
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+.url-display {
+ margin-top: 25px;
+ padding: 15px;
+ background-color: #e9ecef;
+ border: 1px solid #ced4da;
+ border-radius: 4px;
+ font-family: 'Courier New', Courier, monospace;
+ font-size: 0.95em;
+ word-break: break-all;
+}
+
+.label {
+ font-weight: bold;
+ display: block;
+ margin-bottom: 8px;
+ color: #495057;
+}
+
+.logo {
+ height: 6em;
+ padding: 1.5em;
+ will-change: filter;
+ transition: filter 300ms;
+}
+.logo:hover {
+ filter: drop-shadow(0 0 2em #646cffaa);
+}
+.logo.vanilla:hover {
+ filter: drop-shadow(0 0 2em #f7df1eaa);
+}
+
+.card {
+ padding: 2em;
+}
+
+.read-the-docs {
+ color: #888;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+button:hover {
+ border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+
+@media (prefers-color-scheme: light) {
+ :root {
+ color: #213547;
+ background-color: #ffffff;
+ }
+ a:hover {
+ color: #747bff;
+ }
+ button {
+ background-color: #f9f9f9;
+ }
+}
diff --git a/v3/examples/custom-protocol-example/greetservice.go b/v3/examples/custom-protocol-example/greetservice.go
new file mode 100644
index 000000000..8972c39cd
--- /dev/null
+++ b/v3/examples/custom-protocol-example/greetservice.go
@@ -0,0 +1,7 @@
+package main
+
+type GreetService struct{}
+
+func (g *GreetService) Greet(name string) string {
+ return "Hello " + name + "!"
+}
diff --git a/v3/examples/custom-protocol-example/main.go b/v3/examples/custom-protocol-example/main.go
new file mode 100644
index 000000000..f5c5db38b
--- /dev/null
+++ b/v3/examples/custom-protocol-example/main.go
@@ -0,0 +1,82 @@
+package main
+
+import (
+ "embed"
+ _ "embed"
+ "log"
+ "time"
+
+ "github.com/wailsapp/wails/v3/pkg/application"
+ "github.com/wailsapp/wails/v3/pkg/events"
+)
+
+// Wails uses Go's `embed` package to embed the frontend files into the binary.
+// Any files in the frontend/dist folder will be embedded into the binary and
+// made available to the frontend.
+// See https://pkg.go.dev/embed for more information.
+
+//go:embed all:frontend/dist
+var assets embed.FS
+
+// main function serves as the application's entry point. It initializes the application, creates a window,
+// and starts a goroutine that emits a time-based event every second. It subsequently runs the application and
+// logs any error that might occur.
+func main() {
+ // Create a new Wails application by providing the necessary options.
+ // Variables 'Name' and 'Description' are for application metadata.
+ // 'Assets' configures the asset server with the 'FS' variable pointing to the frontend files.
+ // 'Bind' is a list of Go struct instances. The frontend has access to the methods of these instances.
+ // 'Mac' options tailor the application when running an macOS.
+ app := application.New(application.Options{
+ Name: "custom-protocol-example",
+ Description: "A demo of using raw HTML & CSS",
+ Services: []application.Service{
+ application.NewService(&GreetService{}),
+ },
+ Assets: application.AssetOptions{
+ Handler: application.AssetFileServerFS(assets),
+ },
+ Mac: application.MacOptions{
+ ApplicationShouldTerminateAfterLastWindowClosed: true,
+ },
+ })
+
+ // Listen for the system event indicating the app was launched with a URL
+ app.Event.OnApplicationEvent(events.Common.ApplicationLaunchedWithUrl, func(e *application.ApplicationEvent) {
+ app.Event.Emit("frontend:ShowURL", e.Context().URL())
+ })
+
+ // Create a new window with the necessary options.
+ // 'Title' is the title of the window.
+ // 'Mac' options tailor the window when running on macOS.
+ // 'BackgroundColour' is the background colour of the window.
+ // 'URL' is the URL that will be loaded into the webview.
+ _ = app.Window.NewWithOptions(application.WebviewWindowOptions{
+ Title: "Window 1",
+ Mac: application.MacWindow{
+ InvisibleTitleBarHeight: 50,
+ Backdrop: application.MacBackdropTranslucent,
+ TitleBar: application.MacTitleBarHiddenInset,
+ },
+ BackgroundColour: application.NewRGB(27, 38, 54),
+ URL: "/",
+ })
+
+ // Create a goroutine that emits an event containing the current time every second.
+ // The frontend can listen to this event and update the UI accordingly.
+ go func() {
+ for {
+ now := time.Now().Format(time.RFC1123)
+ app.Event.Emit("time", now)
+ time.Sleep(time.Second)
+ }
+ }()
+
+ // Run the application. This blocks until the application has been exited.
+ err := app.Run()
+
+ // If an error occurred while running the application, log it and exit.
+ if err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/v3/internal/commands/build-assets.go b/v3/internal/commands/build-assets.go
index b9a6283c9..e3dbaa128 100644
--- a/v3/internal/commands/build-assets.go
+++ b/v3/internal/commands/build-assets.go
@@ -21,8 +21,17 @@ var buildAssets embed.FS
//go:embed updatable_build_assets
var updatableBuildAssets embed.FS
+// ProtocolConfig defines the structure for a custom protocol in wails.json/wails.yaml
+type ProtocolConfig struct {
+ Scheme string `yaml:"scheme" json:"scheme"`
+ Description string `yaml:"description,omitempty" json:"description,omitempty"`
+ // Future platform-specific fields can be added here if needed by templates.
+ // E.g., for macOS: CFBundleURLName string `yaml:"cfBundleURLName,omitempty" json:"cfBundleURLName,omitempty"`
+}
+
+// BuildAssetsOptions defines the options for generating build assets.
type BuildAssetsOptions struct {
- Dir string `description:"The directory to generate the files into" default:"."`
+ Dir string `description:"The directory to generate the files into" default:"."`
Name string `description:"The name of the project"`
BinaryName string `description:"The name of the binary"`
ProductName string `description:"The name of the product" default:"My Product"`
@@ -42,26 +51,30 @@ type BuildAssetsOptions struct {
Typescript bool `description:"Use typescript" default:"false"`
}
+// BuildConfig defines the configuration for generating build assets.
type BuildConfig struct {
BuildAssetsOptions
FileAssociations []FileAssociation `yaml:"fileAssociations"`
+ Protocols []ProtocolConfig `yaml:"protocols,omitempty"`
}
+// UpdateBuildAssetsOptions defines the options for updating build assets.
type UpdateBuildAssetsOptions struct {
- Dir string `description:"The directory to generate the files into" default:"build"`
+ Dir string `description:"The directory to generate the files into" default:"build"`
Name string `description:"The name of the project"`
BinaryName string `description:"The name of the binary"`
- ProductName string `description:"The name of the product" default:"My Product"`
- ProductDescription string `description:"The description of the product" default:"My Product Description"`
- ProductVersion string `description:"The version of the product" default:"0.1.0"`
- ProductCompany string `description:"The company of the product" default:"My Company"`
- ProductCopyright string `description:"The copyright notice" default:"\u00a9 now, My Company"`
- ProductComments string `description:"Comments to add to the generated files" default:"This is a comment"`
+ ProductName string `description:"The name of the product" default:"My Product"`
+ ProductDescription string `description:"The description of the product" default:"My Product Description"`
+ ProductVersion string `description:"The version of the product" default:"0.1.0"`
+ ProductCompany string `description:"The company of the product" default:"My Company"`
+ ProductCopyright string `description:"The copyright notice" default:"© now, My Company"`
+ ProductComments string `description:"Comments to add to the generated files" default:"This is a comment"`
ProductIdentifier string `description:"The product identifier, e.g com.mycompany.myproduct"`
Config string `description:"The path to the config file"`
Silent bool `description:"Suppress output to console"`
}
+// GenerateBuildAssets generates the build assets for the project.
func GenerateBuildAssets(options *BuildAssetsOptions) error {
DisableFooter = true
@@ -144,6 +157,7 @@ func GenerateBuildAssets(options *BuildAssetsOptions) error {
return nil
}
+// FileAssociation defines the structure for a file association.
type FileAssociation struct {
Ext string `yaml:"ext"`
Name string `yaml:"name"`
@@ -153,11 +167,14 @@ type FileAssociation struct {
MimeType string `yaml:"mimeType"`
}
+// UpdateConfig defines the configuration for updating build assets.
type UpdateConfig struct {
UpdateBuildAssetsOptions
FileAssociations []FileAssociation `yaml:"fileAssociations"`
+ Protocols []ProtocolConfig `yaml:"protocols,omitempty"`
}
+// WailsConfig defines the structure for a Wails configuration.
type WailsConfig struct {
Info struct {
CompanyName string `yaml:"companyName"`
@@ -168,9 +185,11 @@ type WailsConfig struct {
Comments string `yaml:"comments"`
Version string `yaml:"version"`
} `yaml:"info"`
- FileAssociations []FileAssociation `yaml:"fileAssociations"`
+ FileAssociations []FileAssociation `yaml:"fileAssociations,omitempty"`
+ Protocols []ProtocolConfig `yaml:"protocols,omitempty"`
}
+// UpdateBuildAssets updates the build assets for the project.
func UpdateBuildAssets(options *UpdateBuildAssetsOptions) error {
DisableFooter = true
@@ -200,6 +219,7 @@ func UpdateBuildAssets(options *UpdateBuildAssetsOptions) error {
options.ProductComments = wailsConfig.Info.Comments
options.ProductVersion = wailsConfig.Info.Version
config.FileAssociations = wailsConfig.FileAssociations
+ config.Protocols = wailsConfig.Protocols
}
config.UpdateBuildAssetsOptions = *options
diff --git a/v3/internal/commands/build_assets/linux/nfpm/scripts/postinstall.sh b/v3/internal/commands/build_assets/linux/nfpm/scripts/postinstall.sh
index a9bf588e2..4bbb815a3 100644
--- a/v3/internal/commands/build_assets/linux/nfpm/scripts/postinstall.sh
+++ b/v3/internal/commands/build_assets/linux/nfpm/scripts/postinstall.sh
@@ -1 +1,21 @@
-#!/bin/bash
+#!/bin/sh
+
+# Update desktop database for .desktop file changes
+# This makes the application appear in application menus and registers its capabilities.
+if command -v update-desktop-database >/dev/null 2>&1; then
+ echo "Updating desktop database..."
+ update-desktop-database -q /usr/share/applications
+else
+ echo "Warning: update-desktop-database command not found. Desktop file may not be immediately recognized." >&2
+fi
+
+# Update MIME database for custom URL schemes (x-scheme-handler)
+# This ensures the system knows how to handle your custom protocols.
+if command -v update-mime-database >/dev/null 2>&1; then
+ echo "Updating MIME database..."
+ update-mime-database -n /usr/share/mime
+else
+ echo "Warning: update-mime-database command not found. Custom URL schemes may not be immediately recognized." >&2
+fi
+
+exit 0
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 e44a679d2..b68a8f1f7 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
@@ -51,5 +51,20 @@