gogio: add deeplink support

Add a new flag "-schemes" which links the URL schemes to the app.

Signed-off-by: inkeliz <inkeliz@inkeliz.com>
This commit is contained in:
inkeliz
2025-12-14 12:50:14 +00:00
committed by Elias Naur
parent 74551d3253
commit ae8a780af9
7 changed files with 127 additions and 27 deletions
+11
View File
@@ -48,6 +48,7 @@ type manifestData struct {
Features []string Features []string
IconSnip string IconSnip string
AppName string AppName string
Schemes []string
} }
const ( const (
@@ -442,6 +443,7 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
Features: features, Features: features,
IconSnip: iconSnip, IconSnip: iconSnip,
AppName: appName, AppName: appName,
Schemes: bi.schemes,
} }
tmpl, err := template.New("test").Parse( tmpl, err := template.New("test").Parse(
`<?xml version="1.0" encoding="utf-8"?> `<?xml version="1.0" encoding="utf-8"?>
@@ -458,11 +460,20 @@ func exeAndroid(tmpDir string, tools *androidTools, bi *buildInfo, extraJars, pe
android:theme="@style/Theme.GioApp" android:theme="@style/Theme.GioApp"
android:configChanges="screenSize|screenLayout|smallestScreenSize|orientation|keyboardHidden" android:configChanges="screenSize|screenLayout|smallestScreenSize|orientation|keyboardHidden"
android:windowSoftInputMode="adjustResize" android:windowSoftInputMode="adjustResize"
android:launchMode="singleInstance"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
{{range .Schemes}}
<intent-filter>
<action android:name="android.intent.action.VIEW"></action>
<category android:name="android.intent.category.DEFAULT"></category>
<category android:name="android.intent.category.BROWSABLE"></category>
<data android:scheme="{{.}}"></data>
</intent-filter>
{{end}}
</activity> </activity>
</application> </application>
</manifest>`) </manifest>`)
+11
View File
@@ -31,6 +31,7 @@ type buildInfo struct {
notaryAppleID string notaryAppleID string
notaryPassword string notaryPassword string
notaryTeamID string notaryTeamID string
schemes []string
} }
type Semver struct { type Semver struct {
@@ -78,6 +79,7 @@ func newBuildInfo(pkgPath string) (*buildInfo, error) {
notaryAppleID: *notaryID, notaryAppleID: *notaryID,
notaryPassword: *notaryPass, notaryPassword: *notaryPass,
notaryTeamID: *notaryTeamID, notaryTeamID: *notaryTeamID,
schemes: getCommaList(*schemes),
} }
return bi, nil return bi, nil
} }
@@ -147,6 +149,15 @@ func getLdFlags(appID string) string {
return strings.Join(ldflags, " ") return strings.Join(ldflags, " ")
} }
func getCommaList(s string) (list []string) {
for _, v := range strings.Split(s, ",") {
if v := strings.TrimSpace(v); v != "" {
list = append(list, v)
}
}
return list
}
type packageMetadata struct { type packageMetadata struct {
PkgPath string PkgPath string
Dir string Dir string
+5
View File
@@ -82,4 +82,9 @@ for details. If not provided, the password will be prompted.
The -notaryteamid flag specifies the team ID to use for notarization of MacOS app, ignored if The -notaryteamid flag specifies the team ID to use for notarization of MacOS app, ignored if
-notaryid is not provided. -notaryid is not provided.
The -schemes flag specifies a list of comma separated URI schemes that the program can
handle. For example, use -schemes yourAppName to receive a app.URLEvent for URIs
starting with yourAppName://. It is only supported on Android, iOS, macOS and Windows.
On Windows, it will restrict the program to a single instance.
` `
+57 -11
View File
@@ -4,6 +4,7 @@ package main
import ( import (
"archive/zip" "archive/zip"
"bytes"
"crypto/sha1" "crypto/sha1"
"encoding/hex" "encoding/hex"
"errors" "errors"
@@ -15,6 +16,7 @@ import (
"slices" "slices"
"strconv" "strconv"
"strings" "strings"
"text/template"
"time" "time"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
@@ -302,36 +304,57 @@ func buildInfoPlist(bi *buildInfo) string {
case "tvos": case "tvos":
supportPlatform = "AppleTVOS" supportPlatform = "AppleTVOS"
} }
return fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
manifestSrc := struct {
AppName string
AppID string
Version string
VersionCode uint32
Platform string
MinVersion int
SupportPlatform string
Schemes []string
}{
AppName: appName,
AppID: bi.appID,
Version: bi.version.String(),
VersionCode: bi.version.VersionCode,
Platform: platform,
MinVersion: minIOSVersion,
SupportPlatform: supportPlatform,
Schemes: bi.schemes,
}
tmpl, err := template.New("manifest").Parse(`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
<string>en</string> <string>en</string>
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>%s</string> <string>{{.AppName}}</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>%s</string> <string>{{.AppID}}</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
<string>%s</string> <string>{{.AppName}}</string>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>%s</string> <string>{{.Version}}</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>%d</string> <string>{{.VersionCode}}</string>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key> <key>UIRequiredDeviceCapabilities</key>
<array><string>arm64</string></array> <array><string>arm64</string></array>
<key>DTPlatformName</key> <key>DTPlatformName</key>
<string>%s</string> <string>{{.Platform}}</string>
<key>DTPlatformVersion</key> <key>DTPlatformVersion</key>
<string>12.4</string> <string>12.4</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>
<string>%d</string> <string>{{.MinVersion}}</string>
<key>UIDeviceFamily</key> <key>UIDeviceFamily</key>
<array> <array>
<integer>1</integer> <integer>1</integer>
@@ -339,7 +362,7 @@ func buildInfoPlist(bi *buildInfo) string {
</array> </array>
<key>CFBundleSupportedPlatforms</key> <key>CFBundleSupportedPlatforms</key>
<array> <array>
<string>%s</string> <string>{{.SupportPlatform}}</string>
</array> </array>
<key>UISupportedInterfaceOrientations</key> <key>UISupportedInterfaceOrientations</key>
<array> <array>
@@ -354,13 +377,36 @@ func buildInfoPlist(bi *buildInfo) string {
<key>DTSDKBuild</key> <key>DTSDKBuild</key>
<string>16G73</string> <string>16G73</string>
<key>DTSDKName</key> <key>DTSDKName</key>
<string>%s12.4</string> <string>{{.Platform}}12.4</string>
<key>DTXcode</key> <key>DTXcode</key>
<string>1030</string> <string>1030</string>
<key>DTXcodeBuild</key> <key>DTXcodeBuild</key>
<string>10G8</string> <string>10G8</string>
{{if .Schemes}}
<key>CFBundleURLTypes</key>
<array>
{{range .Schemes}}
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>{{.}}</string>
</array>
</dict>
{{end}}
</array>
{{end}}
</dict> </dict>
</plist>`, appName, bi.appID, appName, bi.version, bi.version.VersionCode, platform, minIOSVersion, supportPlatform, platform) </plist>`)
if err != nil {
panic(err)
}
var manifestBuffer bytes.Buffer
if err := tmpl.Execute(&manifestBuffer, manifestSrc); err != nil {
panic(err)
}
return manifestBuffer.String()
} }
func iosPlatformFor(target string) string { func iosPlatformFor(target string) string {
+33 -15
View File
@@ -1,6 +1,7 @@
package main package main
import ( import (
"bytes"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@@ -34,9 +35,7 @@ func buildMac(tmpDir string, bi *buildInfo) error {
return err return err
} }
if err := builder.setInfo(bi, name); err != nil { builder.setInfo(bi, name)
return fmt.Errorf("can't build the resources: %v", err)
}
for _, arch := range bi.archs { for _, arch := range bi.archs {
tmpDest := filepath.Join(builder.TempDir, filepath.Base(builder.DestDir)) tmpDest := filepath.Join(builder.TempDir, filepath.Base(builder.DestDir))
@@ -122,7 +121,20 @@ func (b *macBuilder) setIcon(path string) (err error) {
return err return err
} }
func (b *macBuilder) setInfo(buildInfo *buildInfo, name string) error { func (b *macBuilder) setInfo(buildInfo *buildInfo, name string) {
manifestSrc := struct {
Name string
Bundle string
Version Semver
Schemes []string
}{
Name: name,
Bundle: buildInfo.appID,
Version: buildInfo.version,
Schemes: buildInfo.schemes,
}
t, err := template.New("manifest").Parse(`<?xml version="1.0" encoding="UTF-8"?> t, err := template.New("manifest").Parse(`<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
@@ -137,20 +149,28 @@ func (b *macBuilder) setInfo(buildInfo *buildInfo, name string) error {
<true/> <true/>
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
{{if .Schemes}}
<key>CFBundleURLTypes</key>
<array>
{{range .Schemes}}
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>{{.}}</string>
</array>
</dict>
{{end}}
</array>
{{end}}
</dict> </dict>
</plist>`) </plist>`)
if err != nil { if err != nil {
return err panic(err)
} }
var manifest bufferCoff var manifest bytes.Buffer
if err := t.Execute(&manifest, struct { if err := t.Execute(&manifest, manifestSrc); err != nil {
Name, Bundle string panic(err)
}{
Name: name,
Bundle: buildInfo.appID,
}); err != nil {
return err
} }
b.Manifest = manifest.Bytes() b.Manifest = manifest.Bytes()
@@ -164,8 +184,6 @@ func (b *macBuilder) setInfo(buildInfo *buildInfo, name string) error {
<true/> <true/>
</dict> </dict>
</plist>`) </plist>`)
return nil
} }
func (b *macBuilder) buildProgram(buildInfo *buildInfo, binDest string, name string, arch string) error { func (b *macBuilder) buildProgram(buildInfo *buildInfo, binDest string, name string, arch string) error {
+1
View File
@@ -41,6 +41,7 @@ var (
notaryID = flag.String("notaryid", "", "specify the apple id to use for notarization.") notaryID = flag.String("notaryid", "", "specify the apple id to use for notarization.")
notaryPass = flag.String("notarypass", "", "specify app-specific password of the Apple ID to be used for notarization.") notaryPass = flag.String("notarypass", "", "specify app-specific password of the Apple ID to be used for notarization.")
notaryTeamID = flag.String("notaryteamid", "", "specify the team id to use for notarization.") notaryTeamID = flag.String("notaryteamid", "", "specify the team id to use for notarization.")
schemes = flag.String("schemes", "", "specify a list of comma separated URL schemes that the program accepts")
) )
func main() { func main() {
+9 -1
View File
@@ -202,10 +202,18 @@ func (b *windowsBuilder) buildProgram(buildInfo *buildInfo, name string, arch st
dest = filepath.Join(filepath.Dir(b.DestDir), name+"_"+arch+".exe") dest = filepath.Join(filepath.Dir(b.DestDir), name+"_"+arch+".exe")
} }
ldflags := buildInfo.ldflags
if buildInfo.schemes != nil {
ldflags += ` -X "gioui.org/app.schemesURI=` + strings.Join(buildInfo.schemes, ",") + `" `
}
if buildInfo.appID != "" {
ldflags += ` -X "gioui.org/app.ID=` + buildInfo.appID + `" `
}
cmd := exec.Command( cmd := exec.Command(
"go", "go",
"build", "build",
"-ldflags=-H=windowsgui "+buildInfo.ldflags, "-ldflags=-H=windowsgui "+ldflags,
"-tags="+buildInfo.tags, "-tags="+buildInfo.tags,
"-o", dest, "-o", dest,
buildInfo.pkgPath, buildInfo.pkgPath,