diff --git a/CHANGELOG.md b/CHANGELOG.md index 274bfc6a9d..aa9e55bd00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ More detailed release notes can be found on the [releases page](https://github.c ### Added * Add SetIcon method on ToolbarAction (#2475) +* Access compiled app metadata using new `App.Metadata()` method ### Changed @@ -19,7 +20,7 @@ More detailed release notes can be found on the [releases page](https://github.c ### Fixed -* +* SendNotification does not show app name on Windows (#1940) ## 2.1.3 - Ongoing diff --git a/app.go b/app.go index 1a7663e8e7..77bc72ec0f 100644 --- a/app.go +++ b/app.go @@ -62,6 +62,11 @@ type App interface { // // Since: 2.1 Lifecycle() Lifecycle + + // Metadata returns the application metadata that was set at compile time. + // + // Since: 2.2 + Metadata() AppMetadata } // app contains an App variable, but due to atomic.Value restrictions on @@ -90,6 +95,22 @@ func CurrentApp() App { return (val).(appContainer).current } +// AppMetadata captures the build metadata for an application. +// +// Since: 2.2 +type AppMetadata struct { + // ID is the unique ID of this application, used by many distribution platforms. + ID string + // Name is the human friendly name of this app. + Name string + // Version represents the version of this application, normally following semantic versioning. + Version string + // Build is the build number of this app, some times appended to the version number. + Build int + // Icon contains, if present, a resource of the icon that was bundled at build time. + Icon Resource +} + // Lifecycle represents the various phases that an app can transition through. // // Since: 2.1 diff --git a/app/app.go b/app/app.go index 50f241db07..6164dfa1c2 100644 --- a/app/app.go +++ b/app/app.go @@ -35,7 +35,11 @@ type fyneApp struct { } func (a *fyneApp) Icon() fyne.Resource { - return a.icon + if a.icon != nil { + return a.icon + } + + return a.Metadata().Icon } func (a *fyneApp) SetIcon(icon fyne.Resource) { @@ -46,6 +50,9 @@ func (a *fyneApp) UniqueID() string { if a.uniqueID != "" { return a.uniqueID } + if a.Metadata().ID != "" { + return a.Metadata().ID + } fyne.LogError("Preferences API requires a unique ID, use app.NewWithID()", nil) a.uniqueID = "missing-id-" + strconv.FormatInt(time.Now().Unix(), 10) // This is a fake unique - it just has to not be reused... diff --git a/app/app_windows.go b/app/app_windows.go index 202f371412..17a8bbb336 100644 --- a/app/app_windows.go +++ b/app/app_windows.go @@ -74,9 +74,9 @@ var scriptNum = 0 func (a *fyneApp) SendNotification(n *fyne.Notification) { title := escapeNotificationString(n.Title) content := escapeNotificationString(n.Content) - appID := a.UniqueID() // TODO once we have an app name compiled in this could be improved + appID := a.UniqueID() if appID == "" || strings.Index(appID, "missing-id") == 0 { - appID = "Fyne app" + appID = a.Metadata().Name } script := fmt.Sprintf(notificationTemplate, title, content, appID) diff --git a/app/meta.go b/app/meta.go new file mode 100644 index 0000000000..36147e624a --- /dev/null +++ b/app/meta.go @@ -0,0 +1,48 @@ +package app + +import ( + "encoding/base64" + "io/ioutil" + "strconv" + "strings" + + "fyne.io/fyne/v2" + intapp "fyne.io/fyne/v2/internal/app" +) + +var ( + meta fyne.AppMetadata +) + +func init() { + build, err := strconv.Atoi(intapp.MetaBuild) + if err != nil { + build = 1 + } + + meta = fyne.AppMetadata{ + Icon: convertIcon(intapp.MetaIcon), + ID: intapp.MetaID, + Name: intapp.MetaName, + Version: intapp.MetaVersion, + Build: build, + } +} + +func (a *fyneApp) Metadata() fyne.AppMetadata { + return meta +} + +func convertIcon(bytes string) fyne.Resource { + data := base64.NewDecoder(base64.StdEncoding, strings.NewReader(bytes)) + img, err := ioutil.ReadAll(data) + if err != nil { + fyne.LogError("Failed to decode icon from embedded data", err) + return nil + } + + return &fyne.StaticResource{ + StaticName: "FyneAppIcon", + StaticContent: img, + } +} diff --git a/app_test.go b/app_test.go index 12aa969910..6ed371a85a 100644 --- a/app_test.go +++ b/app_test.go @@ -57,6 +57,10 @@ func (dummyApp) Lifecycle() Lifecycle { return nil } +func (dummyApp) Metadata() AppMetadata { + return AppMetadata{} +} + func TestSetCurrentApp(t *testing.T) { a := &dummyApp{} SetCurrentApp(a) diff --git a/cmd/fyne/internal/commands/build.go b/cmd/fyne/internal/commands/build.go index c2d0bbc727..42ea2178f4 100644 --- a/cmd/fyne/internal/commands/build.go +++ b/cmd/fyne/internal/commands/build.go @@ -1,11 +1,14 @@ package commands import ( + "encoding/base64" "fmt" "os" "runtime" + "strconv" "strings" + "fyne.io/fyne/v2" version "github.com/mcuadros/go-version" ) @@ -15,6 +18,9 @@ type builder struct { tags []string runner runner + + icon, id, name, version string + buildNum int } func checkVersion(output string, versionConstraint *version.ConstraintGroup) error { @@ -73,18 +79,23 @@ func (b *builder) build() error { env = append(env, "CGO_CFLAGS=-mmacosx-version-min=10.11", "CGO_LDFLAGS=-mmacosx-version-min=10.11") } + meta := b.generateMetaLDFlags() if !isWeb(goos) { env = append(env, "CGO_ENABLED=1") // in case someone is trying to cross-compile... if goos == "windows" { if b.release { - args = append(args, "-ldflags", "-s -w -H=windowsgui") + args = append(args, "-ldflags", "-s -w -H=windowsgui "+meta) } else { - args = append(args, "-ldflags", "-H=windowsgui") + args = append(args, "-ldflags", "-H=windowsgui "+meta) } } else if b.release { - args = append(args, "-ldflags", "-s -w") + args = append(args, "-ldflags", "-s -w "+meta) + } else if meta != "" { + args = append(args, "-ldflags", meta) } + } else if meta != "" { + args = append(args, "-ldflags", meta) } if b.target != "" { @@ -126,6 +137,44 @@ func (b *builder) build() error { return err } +func (b *builder) generateMetaLDFlags() string { + buildIDString := "" + if b.buildNum > 0 { + buildIDString = strconv.Itoa(b.buildNum) + } + iconBytes := "" + if b.icon != "" { + res, err := fyne.LoadResourceFromPath(b.icon) + if err != nil { + fyne.LogError("Unable to load file "+b.icon, err) + } else { + staticRes, ok := res.(*fyne.StaticResource) + if !ok { + fyne.LogError("Unable to format resource", fmt.Errorf("unexpected resource type %T", res)) + } else { + iconBytes = base64.StdEncoding.EncodeToString(staticRes.StaticContent) + } + } + } + + inserts := [][2]string{ + {"MetaID", b.id}, + {"MetaName", b.name}, + {"MetaVersion", b.version}, + {"MetaBuild", buildIDString}, + {"MetaIcon", iconBytes}, + } + + var vars []string + for _, item := range inserts { + if item[1] != "" { + vars = append(vars, "-X 'fyne.io/fyne/v2/internal/app."+item[0]+"="+item[1]+"'") + } + } + + return strings.Join(vars, " ") +} + func targetOS() string { osEnv, ok := os.LookupEnv("GOOS") if ok { diff --git a/cmd/fyne/internal/commands/build_test.go b/cmd/fyne/internal/commands/build_test.go index 5eb1e79fd0..ab62f08e18 100644 --- a/cmd/fyne/internal/commands/build_test.go +++ b/cmd/fyne/internal/commands/build_test.go @@ -8,6 +8,17 @@ import ( "github.com/stretchr/testify/assert" ) +func TestBuildGenerateMetaLDFlags(t *testing.T) { + b := &builder{} + assert.Equal(t, "", b.generateMetaLDFlags()) + + b.id = "com.example" + assert.Equal(t, "-X 'fyne.io/fyne/v2/internal/app.MetaID=com.example'", b.generateMetaLDFlags()) + + b.version = "1.2.3" + assert.Equal(t, "-X 'fyne.io/fyne/v2/internal/app.MetaID=com.example' -X 'fyne.io/fyne/v2/internal/app.MetaVersion=1.2.3'", b.generateMetaLDFlags()) +} + func Test_CheckGoVersionNoGo(t *testing.T) { commandNil := &testCommandRuns{runs: []mockRunner{}, t: t} assert.Nil(t, checkGoVersion(commandNil, nil)) diff --git a/cmd/fyne/internal/commands/package.go b/cmd/fyne/internal/commands/package.go index a42ed408e2..5954c68ef9 100644 --- a/cmd/fyne/internal/commands/package.go +++ b/cmd/fyne/internal/commands/package.go @@ -196,6 +196,12 @@ func (p *Packager) buildPackage(runner runner) ([]string, error) { release: p.release, tags: tags, runner: runner, + + icon: p.icon, + id: p.appID, + name: p.name, + version: p.appVersion, + buildNum: p.appBuild, } return []string{p.exe}, b.build() @@ -208,6 +214,12 @@ func (p *Packager) buildPackage(runner runner) ([]string, error) { release: p.release, tags: tags, runner: runner, + + icon: p.icon, + id: p.appID, + name: p.name, + version: p.appVersion, + buildNum: p.appBuild, } err := bWasm.build() @@ -222,6 +234,12 @@ func (p *Packager) buildPackage(runner runner) ([]string, error) { release: p.release, tags: tags, runner: runner, + + icon: p.icon, + id: p.appID, + name: p.name, + version: p.appVersion, + buildNum: p.appBuild, } err = bGopherJS.build() diff --git a/cmd/fyne/internal/commands/package_test.go b/cmd/fyne/internal/commands/package_test.go index d0e9be07d4..cc25fd7edf 100644 --- a/cmd/fyne/internal/commands/package_test.go +++ b/cmd/fyne/internal/commands/package_test.go @@ -119,7 +119,9 @@ func Test_PackageWasm(t *testing.T) { }, { expectedValue: expectedValue{ - args: []string{"build", "-o", "myTest.wasm"}, + args: []string{"build", "-ldflags", + "-X 'fyne.io/fyne/v2/internal/app.MetaName=myTest.wasm' -X 'fyne.io/fyne/v2/internal/app.MetaVersion=1.0.0' -X 'fyne.io/fyne/v2/internal/app.MetaBuild=1'", + "-o", "myTest.wasm"}, env: []string{"GOARCH=wasm", "GOOS=js"}, osEnv: true, dir: "myTest", @@ -226,7 +228,9 @@ func Test_PackageGopherJS(t *testing.T) { expected := []mockRunner{ { expectedValue: expectedValue{ - args: []string{"build", "-o", "myTest.js"}, + args: []string{"build", "-ldflags", + "-X 'fyne.io/fyne/v2/internal/app.MetaName=myTest.js' -X 'fyne.io/fyne/v2/internal/app.MetaVersion=1.0.0' -X 'fyne.io/fyne/v2/internal/app.MetaBuild=1'", + "-o", "myTest.js"}, osEnv: true, dir: "myTest", }, @@ -354,7 +358,9 @@ func Test_PackageWeb(t *testing.T) { }, { expectedValue: expectedValue{ - args: []string{"build", "-o", "myTest.wasm"}, + args: []string{"build", "-ldflags", + "-X 'fyne.io/fyne/v2/internal/app.MetaName=myTest' -X 'fyne.io/fyne/v2/internal/app.MetaVersion=1.0.0' -X 'fyne.io/fyne/v2/internal/app.MetaBuild=1'", + "-o", "myTest.wasm"}, env: []string{"GOARCH=wasm", "GOOS=js"}, osEnv: true, dir: "myTest", @@ -365,7 +371,9 @@ func Test_PackageWeb(t *testing.T) { }, { expectedValue: expectedValue{ - args: []string{"build", "-o", "myTest.js"}, + args: []string{"build", "-ldflags", + "-X 'fyne.io/fyne/v2/internal/app.MetaName=myTest' -X 'fyne.io/fyne/v2/internal/app.MetaVersion=1.0.0' -X 'fyne.io/fyne/v2/internal/app.MetaBuild=1'", + "-o", "myTest.js"}, osEnv: true, dir: "myTest", }, diff --git a/internal/app/meta.go b/internal/app/meta.go new file mode 100644 index 0000000000..54a545b6a6 --- /dev/null +++ b/internal/app/meta.go @@ -0,0 +1,10 @@ +package app + +// these internal variables are set by the fyne build command so that the "FyneApp.toml" data is readable at runtime. +var ( + MetaIcon = "" // this will contain base64 encoded icon bytes + MetaID = "com.example" + MetaName = "Fyne App" + MetaVersion = "1.0.0" + MetaBuild = "1" +) diff --git a/test/testapp.go b/test/testapp.go index 6ab9b87610..4d70d33e21 100644 --- a/test/testapp.go +++ b/test/testapp.go @@ -86,6 +86,10 @@ func (a *testApp) Lifecycle() fyne.Lifecycle { return a.lifecycle } +func (a *testApp) Metadata() fyne.AppMetadata { + return fyne.AppMetadata{} // just dummy data +} + func (a *testApp) lastAppliedTheme() fyne.Theme { a.propertyLock.Lock() defer a.propertyLock.Unlock() diff --git a/theme/themedtestapp.go b/theme/themedtestapp.go index d6b220790b..8741c10d32 100644 --- a/theme/themedtestapp.go +++ b/theme/themedtestapp.go @@ -64,6 +64,10 @@ func (t *themedApp) Lifecycle() fyne.Lifecycle { return nil } +func (t *themedApp) Metadata() fyne.AppMetadata { + return fyne.AppMetadata{} +} + func (t *themedApp) PrimaryColor() string { return ColorBlue }