diff --git a/mkdocs-website/shared/alpha2.csv b/mkdocs-website/shared/alpha2.csv index 2dd5262c0..a65bac128 100644 --- a/mkdocs-website/shared/alpha2.csv +++ b/mkdocs-website/shared/alpha2.csv @@ -2,4 +2,4 @@ `wails init`,:material-check-bold:,:material-check-bold:,:material-check-bold: `wails build`,:material-check-bold:,:material-check-bold:,:material-check-bold: `wails dev`," "," "," " -`wails package`," ",:material-check-bold:," " +`wails package`," ",:material-check-bold:,:material-check-bold: diff --git a/v3/cmd/wails3/main.go b/v3/cmd/wails3/main.go index 6472556b2..b993072d3 100644 --- a/v3/cmd/wails3/main.go +++ b/v3/cmd/wails3/main.go @@ -45,6 +45,8 @@ func main() { generate.NewSubCommandFunction("bindings", "Generate bindings + models", commands.GenerateBindings) generate.NewSubCommandFunction("constants", "Generate JS constants from Go", commands.GenerateConstants) generate.NewSubCommandFunction(".desktop", "Generate .desktop file", commands.GenerateDotDesktop) + generate.NewSubCommandFunction("appimage", "Generate Linux AppImage", commands.GenerateAppImage) + plugin := app.NewSubCommand("plugin", "Plugin tools") //plugin.NewSubCommandFunction("list", "List plugins", commands.PluginList) plugin.NewSubCommandFunction("init", "Initialise a new plugin", commands.PluginInit) diff --git a/v3/go.mod b/v3/go.mod index a94caafd5..a40732f36 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -10,6 +10,7 @@ require ( github.com/go-task/task/v3 v3.31.0 github.com/godbus/dbus/v5 v5.1.0 github.com/google/go-cmp v0.5.9 + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/uuid v1.3.0 github.com/gorilla/pat v1.0.1 github.com/gorilla/sessions v1.2.1 diff --git a/v3/go.sum b/v3/go.sum index a867c1c65..88b9feb81 100644 --- a/v3/go.sum +++ b/v3/go.sum @@ -173,6 +173,8 @@ github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= diff --git a/v3/internal/commands/appimage.go b/v3/internal/commands/appimage.go new file mode 100644 index 000000000..90b7599ff --- /dev/null +++ b/v3/internal/commands/appimage.go @@ -0,0 +1,173 @@ +package commands + +import ( + _ "embed" + "fmt" + "github.com/pterm/pterm" + "github.com/wailsapp/wails/v3/internal/s" + "os" + "path/filepath" + "sync" +) + +//go:embed linuxdeploy-plugin-gtk.sh +var gtkPlugin []byte + +func log(p *pterm.ProgressbarPrinter, message string) { + p.UpdateTitle(message) + pterm.Info.Println(message) + p.Increment() +} + +type GenerateAppImageOptions struct { + Binary string `description:"The binary to package including path"` + Icon string `description:"Path to the icon"` + DesktopFile string `description:"Path to the desktop file"` + OutputDir string `description:"Path to the output directory" default:"."` + BuildDir string `description:"Path to the build directory"` +} + +func GenerateAppImage(options *GenerateAppImageOptions) error { + + defer func() { + pterm.DefaultSpinner.Stop() + }() + + if options.Binary == "" { + return fmt.Errorf("binary not provided") + } + if options.Icon == "" { + return fmt.Errorf("icon path not provided") + } + if options.DesktopFile == "" { + return fmt.Errorf("desktop file path not provided") + } + if options.BuildDir == "" { + // Create temp directory + var err error + options.BuildDir, err = os.MkdirTemp("", "wails-appimage-*") + if err != nil { + return err + } + } + var err error + options.OutputDir, err = filepath.Abs(options.OutputDir) + if err != nil { + return err + } + + return generateAppImage(options) +} + +func generateAppImage(options *GenerateAppImageOptions) error { + numberOfSteps := 5 + p, _ := pterm.DefaultProgressbar.WithTotal(numberOfSteps).WithTitle("Generating AppImage").Start() + + // Get the last path of the binary and normalise the name + name := normaliseName(filepath.Base(options.Binary)) + + appDir := filepath.Join(options.BuildDir, name+"-x86_64.AppDir") + s.RMDIR(appDir) + + log(p, "Preparing AppImage Directory: "+appDir) + + usrBin := filepath.Join(appDir, "usr", "bin") + s.MKDIR(options.BuildDir) + s.MKDIR(usrBin) + s.COPY(options.Binary, usrBin) + s.CHMOD(filepath.Join(usrBin, filepath.Base(options.Binary)), 0755) + dotDirIcon := filepath.Join(appDir, ".DirIcon") + s.COPY(options.Icon, dotDirIcon) + iconLink := filepath.Join(appDir, filepath.Base(options.Icon)) + s.DELETE(iconLink) + s.SYMLINK(".DirIcon", iconLink) + s.COPY(options.DesktopFile, appDir) + + // Download linuxdeploy and make it executable + s.CD(options.BuildDir) + + // Download necessary files + log(p, "Downloading AppImage tooling") + var wg sync.WaitGroup + wg.Add(2) + go func() { + if !s.EXISTS(filepath.Join(options.BuildDir, "linuxdeploy-x86_64.AppImage")) { + s.DOWNLOAD("https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage", filepath.Join(options.BuildDir, "linuxdeploy-x86_64.AppImage")) + } + s.CHMOD(filepath.Join(options.BuildDir, "linuxdeploy-x86_64.AppImage"), 0755) + wg.Done() + }() + go func() { + target := filepath.Join(appDir, "AppRun") + if !s.EXISTS(target) { + s.DOWNLOAD("https://github.com/AppImage/AppImageKit/releases/download/continuous/AppRun-x86_64", target) + } + s.CHMOD(target, 0755) + wg.Done() + }() + wg.Wait() + + log(p, "Processing GTK files.") + files := s.FINDFILES("/usr/lib", "WebKitNetworkProcess", "WebKitWebProcess", "libwebkit2gtkinjectedbundle.so") + if len(files) != 3 { + return fmt.Errorf("unable to locate all WebKit libraries") + } + s.CD(appDir) + for _, file := range files { + targetDir := filepath.Dir(file) + // Strip leading forward slash + if targetDir[0] == '/' { + targetDir = targetDir[1:] + } + var err error + targetDir, err = filepath.Abs(targetDir) + if err != nil { + return err + } + s.MKDIR(targetDir) + s.COPY(file, targetDir) + } + // Copy GTK Plugin + err := os.WriteFile(filepath.Join(options.BuildDir, "linuxdeploy-plugin-gtk.sh"), gtkPlugin, 0755) + if err != nil { + return err + } + + // Determine GTK Version + // Run ldd on the binary and capture the output + targetBinary := filepath.Join(appDir, "usr", "bin", options.Binary) + lddOutput, err := s.EXEC(fmt.Sprintf("ldd %s", targetBinary)) + if err != nil { + println(string(lddOutput)) + return err + } + lddString := string(lddOutput) + // Check if GTK3 is present + var DeployGtkVersion string + if s.CONTAINS(lddString, "libgtk-x11-2.0.so") { + DeployGtkVersion = "2" + } else if s.CONTAINS(lddString, "libgtk-3.so") { + DeployGtkVersion = "3" + } else if s.CONTAINS(lddString, "libgtk-4.so") { + DeployGtkVersion = "4" + } else { + return fmt.Errorf("unable to determine GTK version") + } + // Run linuxdeploy to bundle the application + s.CD(options.BuildDir) + log(p, "Generating AppImage (This may take a while...)") + cmd := fmt.Sprintf("./linuxdeploy-x86_64.AppImage --appimage-extract-and-run --appdir %s --output appimage --plugin gtk", appDir) + s.SETENV("DEPLOY_GTK_VERSION", DeployGtkVersion) + output, err := s.EXEC(cmd) + if err != nil { + println(output) + return err + } + + // Move file to output directory + targetFile := filepath.Join(options.BuildDir, name+"-x86_64.AppImage") + s.MOVE(targetFile, options.OutputDir) + + log(p, "AppImage created: "+targetFile) + return nil +} diff --git a/v3/internal/commands/appimage_test.go b/v3/internal/commands/appimage_test.go new file mode 100644 index 000000000..23e81efaf --- /dev/null +++ b/v3/internal/commands/appimage_test.go @@ -0,0 +1,80 @@ +//go:build full_test + +package commands_test + +import ( + "github.com/wailsapp/wails/v3/internal/commands" + "github.com/wailsapp/wails/v3/internal/s" + "testing" +) + +func Test_generateAppImage(t *testing.T) { + + tests := []struct { + name string + options *commands.GenerateAppImageOptions + wantErr bool + setup func() + teardown func() + }{ + { + name: "Should fail if binary path is not provided", + options: &commands.GenerateAppImageOptions{}, + wantErr: true, + }, + { + name: "Should fail if Icon is not provided", + options: &commands.GenerateAppImageOptions{ + Binary: "testapp", + }, + wantErr: true, + }, + + { + name: "Should fail if desktop file is not provided", + options: &commands.GenerateAppImageOptions{ + Binary: "testapp", + Icon: "testicon", + }, + wantErr: true, + }, + { + name: "Should work if inputs are valid", + options: &commands.GenerateAppImageOptions{ + Binary: "testapp", + Icon: "appicon.png", + DesktopFile: "testapp.desktop", + }, + setup: func() { + // Compile the test application + s.CD("appimage_testfiles") + testDir := s.CWD() + _, err := s.EXEC(`go build -ldflags="-s -w" -o testapp`) + if err != nil { + t.Fatal(err) + } + s.DEFER(func() { + s.CD(testDir) + s.RM("testapp") + s.RM("testapp-x86_64.AppImage") + }) + }, + teardown: func() { + s.CALLDEFER() + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.setup != nil { + tt.setup() + } + if err := commands.GenerateAppImage(tt.options); (err != nil) != tt.wantErr { + t.Errorf("generateAppImage() error = %v, wantErr %v", err, tt.wantErr) + } + if tt.teardown != nil { + tt.teardown() + } + }) + } +} diff --git a/v3/internal/commands/appimage_testfiles/appicon.png b/v3/internal/commands/appimage_testfiles/appicon.png new file mode 100644 index 000000000..63617fe4f Binary files /dev/null and b/v3/internal/commands/appimage_testfiles/appicon.png differ diff --git a/v3/internal/commands/appimage_testfiles/main.go b/v3/internal/commands/appimage_testfiles/main.go new file mode 100644 index 000000000..609051bc0 --- /dev/null +++ b/v3/internal/commands/appimage_testfiles/main.go @@ -0,0 +1,417 @@ +package main + +import ( + _ "embed" + "fmt" + "log" + "math/rand" + "runtime" + "strconv" + "time" + + "github.com/samber/lo" + "github.com/wailsapp/wails/v3/pkg/events" + + "github.com/wailsapp/wails/v3/pkg/application" +) + +func main() { + app := application.New(application.Options{ + Name: "WebviewWindow Demo", + Description: "A demo of the WebviewWindow API", + Assets: application.AlphaAssets, + Mac: application.MacOptions{ + ApplicationShouldTerminateAfterLastWindowClosed: false, + }, + }) + app.On(events.Mac.ApplicationDidFinishLaunching, func(event *application.Event) { + log.Println("ApplicationDidFinishLaunching") + }) + + var hiddenWindows []*application.WebviewWindow + + currentWindow := func(fn func(window *application.WebviewWindow)) { + if app.CurrentWindow() != nil { + fn(app.CurrentWindow()) + } else { + println("Current WebviewWindow is nil") + } + } + + // Create a custom menu + menu := app.NewMenu() + menu.AddRole(application.AppMenu) + + windowCounter := 1 + + // Let's make a "Demo" menu + myMenu := menu.AddSubmenu("New") + + myMenu.Add("New WebviewWindow"). + SetAccelerator("CmdOrCtrl+N"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindow(). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetURL("https://wails.io"). + Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (Hides on Close one time)"). + SetAccelerator("CmdOrCtrl+H"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + // This will be called when the user clicks the close button + // on the window. It will hide the window for 5 seconds. + // If the user clicks the close button again, the window will + // close. + ShouldClose: func(window *application.WebviewWindow) bool { + if !lo.Contains(hiddenWindows, window) { + hiddenWindows = append(hiddenWindows, window) + go func() { + time.Sleep(5 * time.Second) + window.Show() + }() + window.Hide() + return false + } + // Remove the window from the hiddenWindows list + hiddenWindows = lo.Without(hiddenWindows, window) + return true + }, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetURL("https://wails.io"). + Show() + windowCounter++ + }) + myMenu.Add("New Frameless WebviewWindow"). + SetAccelerator("CmdOrCtrl+F"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + X: rand.Intn(1000), + Y: rand.Intn(800), + BackgroundColour: application.NewRGB(33, 37, 41), + Frameless: true, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + }, + }).Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (ignores mouse events"). + SetAccelerator("CmdOrCtrl+F"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + HTML: "
", + X: rand.Intn(1000), + Y: rand.Intn(800), + IgnoreMouseEvents: true, + BackgroundType: application.BackgroundTypeTransparent, + Mac: application.MacWindow{ + InvisibleTitleBarHeight: 50, + }, + }).Show() + windowCounter++ + }) + if runtime.GOOS == "darwin" { + myMenu.Add("New WebviewWindow (MacTitleBarHiddenInset)"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Mac: application.MacWindow{ + TitleBar: application.MacTitleBarHiddenInset, + InvisibleTitleBarHeight: 25, + }, + }). + SetBackgroundColour(application.NewRGB(33, 37, 41)). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetHTML("A MacTitleBarHiddenInset WebviewWindow example
"). + Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (MacTitleBarHiddenInsetUnified)"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Mac: application.MacWindow{ + TitleBar: application.MacTitleBarHiddenInsetUnified, + InvisibleTitleBarHeight: 50, + }, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetHTML("A MacTitleBarHiddenInsetUnified WebviewWindow example
"). + Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (MacTitleBarHidden)"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Mac: application.MacWindow{ + TitleBar: application.MacTitleBarHidden, + InvisibleTitleBarHeight: 25, + }, + }). + SetTitle("WebviewWindow "+strconv.Itoa(windowCounter)). + SetRelativePosition(rand.Intn(1000), rand.Intn(800)). + SetHTML("A MacTitleBarHidden WebviewWindow example
"). + Show() + windowCounter++ + }) + } + if runtime.GOOS == "windows" { + myMenu.Add("New WebviewWindow (Mica)"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "WebviewWindow " + strconv.Itoa(windowCounter), + X: rand.Intn(1000), + Y: rand.Intn(800), + BackgroundType: application.BackgroundTypeTranslucent, + HTML: "", + Windows: application.WindowsWindow{ + BackdropType: application.Mica, + }, + }).Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (Acrylic)"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "WebviewWindow " + strconv.Itoa(windowCounter), + X: rand.Intn(1000), + Y: rand.Intn(800), + BackgroundType: application.BackgroundTypeTranslucent, + HTML: "", + Windows: application.WindowsWindow{ + BackdropType: application.Acrylic, + }, + }).Show() + windowCounter++ + }) + myMenu.Add("New WebviewWindow (Tabbed)"). + OnClick(func(ctx *application.Context) { + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + Title: "WebviewWindow " + strconv.Itoa(windowCounter), + X: rand.Intn(1000), + Y: rand.Intn(800), + BackgroundType: application.BackgroundTypeTranslucent, + HTML: "", + Windows: application.WindowsWindow{ + BackdropType: application.Tabbed, + }, + }).Show() + windowCounter++ + }) + } + + sizeMenu := menu.AddSubmenu("Size") + sizeMenu.Add("Set Size (800,600)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetSize(800, 600) + }) + }) + + sizeMenu.Add("Set Size (Random)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetSize(rand.Intn(800)+200, rand.Intn(600)+200) + }) + }) + sizeMenu.Add("Set Min Size (200,200)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetMinSize(200, 200) + }) + }) + sizeMenu.Add("Set Max Size (600,600)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetFullscreenButtonEnabled(false) + w.SetMaxSize(600, 600) + }) + }) + sizeMenu.Add("Get Current WebviewWindow Size").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + width, height := w.Size() + application.InfoDialog().SetTitle("Current WebviewWindow Size").SetMessage("Width: " + strconv.Itoa(width) + " Height: " + strconv.Itoa(height)).Show() + }) + }) + + sizeMenu.Add("Reset Min Size").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetMinSize(0, 0) + }) + }) + + sizeMenu.Add("Reset Max Size").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetMaxSize(0, 0) + w.SetFullscreenButtonEnabled(true) + }) + }) + positionMenu := menu.AddSubmenu("Position") + positionMenu.Add("Set Relative Position (0,0)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetRelativePosition(0, 0) + }) + }) + positionMenu.Add("Set Relative Position (Random)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetRelativePosition(rand.Intn(1000), rand.Intn(800)) + }) + }) + + positionMenu.Add("Get Relative Position").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + x, y := w.RelativePosition() + application.InfoDialog().SetTitle("Current WebviewWindow Position").SetMessage("X: " + strconv.Itoa(x) + " Y: " + strconv.Itoa(y)).Show() + }) + }) + + positionMenu.Add("Set Absolute Position (0,0)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetAbsolutePosition(0, 0) + }) + }) + + positionMenu.Add("Set Absolute Position (Random)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetAbsolutePosition(rand.Intn(1000), rand.Intn(800)) + }) + }) + + positionMenu.Add("Get Absolute Position").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + x, y := w.AbsolutePosition() + application.InfoDialog().SetTitle("Current WebviewWindow Position").SetMessage("X: " + strconv.Itoa(x) + " Y: " + strconv.Itoa(y)).Show() + }) + }) + + positionMenu.Add("Center").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.Center() + }) + }) + stateMenu := menu.AddSubmenu("State") + stateMenu.Add("Minimise (for 2 secs)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.Minimise() + time.Sleep(2 * time.Second) + w.Restore() + }) + }) + stateMenu.Add("Maximise").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.Maximise() + }) + }) + stateMenu.Add("Fullscreen").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.Fullscreen() + }) + }) + stateMenu.Add("UnFullscreen").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.UnFullscreen() + }) + }) + stateMenu.Add("Restore").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.Restore() + }) + }) + stateMenu.Add("Hide (for 2 seconds)").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.Hide() + time.Sleep(2 * time.Second) + w.Show() + }) + }) + stateMenu.Add("Always on Top").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetAlwaysOnTop(true) + }) + }) + stateMenu.Add("Not always on Top").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetAlwaysOnTop(false) + }) + }) + stateMenu.Add("Google.com").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetURL("https://google.com") + }) + }) + stateMenu.Add("wails.io").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetURL("https://wails.io") + }) + }) + stateMenu.Add("Get Primary Screen").OnClick(func(ctx *application.Context) { + screen, err := app.GetPrimaryScreen() + if err != nil { + application.ErrorDialog().SetTitle("Error").SetMessage(err.Error()).Show() + return + } + msg := fmt.Sprintf("Screen: %+v", screen) + application.InfoDialog().SetTitle("Primary Screen").SetMessage(msg).Show() + }) + stateMenu.Add("Get Screens").OnClick(func(ctx *application.Context) { + screens, err := app.GetScreens() + if err != nil { + application.ErrorDialog().SetTitle("Error").SetMessage(err.Error()).Show() + return + } + for _, screen := range screens { + msg := fmt.Sprintf("Screen: %+v", screen) + application.InfoDialog().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() + } + }) + stateMenu.Add("Get Screen for WebviewWindow").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + screen, err := w.GetScreen() + if err != nil { + application.ErrorDialog().SetTitle("Error").SetMessage(err.Error()).Show() + return + } + msg := fmt.Sprintf("Screen: %+v", screen) + application.InfoDialog().SetTitle(fmt.Sprintf("Screen %s", screen.ID)).SetMessage(msg).Show() + }) + }) + stateMenu.Add("Disable for 5s").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + w.SetEnabled(false) + time.Sleep(5 * time.Second) + w.SetEnabled(true) + }) + }) + + if runtime.GOOS == "windows" { + stateMenu.Add("Flash Start").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + time.Sleep(2 * time.Second) + w.Flash(true) + }) + }) + } + + printMenu := menu.AddSubmenu("Print") + printMenu.Add("Print").OnClick(func(ctx *application.Context) { + currentWindow(func(w *application.WebviewWindow) { + _ = w.Print() + }) + }) + + app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{ + BackgroundColour: application.NewRGB(33, 37, 41), + Mac: application.MacWindow{ + DisableShadow: true, + }, + }) + + app.SetMenu(menu) + err := app.Run() + + if err != nil { + log.Fatal(err) + } + +} diff --git a/v3/internal/commands/appimage_testfiles/testapp.desktop b/v3/internal/commands/appimage_testfiles/testapp.desktop new file mode 100644 index 000000000..27bbed876 --- /dev/null +++ b/v3/internal/commands/appimage_testfiles/testapp.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Type=Application +Name=testapp +Exec=testapp +Icon=appicon +Categories=Development; +Terminal=false +Keywords=wails +Version=1.0 +StartupNotify=false diff --git a/v3/internal/commands/dot_desktop.go b/v3/internal/commands/dot_desktop.go index 0fd674d9b..1e76163a7 100644 --- a/v3/internal/commands/dot_desktop.go +++ b/v3/internal/commands/dot_desktop.go @@ -35,9 +35,7 @@ func (d *DotDesktopOptions) asBytes() []byte { if d.Icon != "" { buf.WriteString(fmt.Sprintf("Icon=%s\n", d.Icon)) } - if d.Categories != "" { - buf.WriteString(fmt.Sprintf("Categories=%s\n", d.Categories)) - } + buf.WriteString(fmt.Sprintf("Categories=%s\n", d.Categories)) if d.Comment != "" { buf.WriteString(fmt.Sprintf("Comment=%s\n", d.Comment)) } diff --git a/v3/internal/commands/linuxdeploy-plugin-gtk.sh b/v3/internal/commands/linuxdeploy-plugin-gtk.sh new file mode 100644 index 000000000..51c1231dd --- /dev/null +++ b/v3/internal/commands/linuxdeploy-plugin-gtk.sh @@ -0,0 +1,376 @@ +#! /usr/bin/env bash + +# Source: https://raw.githubusercontent.com/linuxdeploy/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh +# License: MIT (https://github.com/linuxdeploy/linuxdeploy-plugin-gtk/blob/master/LICENSE.txt) + +# GTK3 environment variables: https://developer.gnome.org/gtk3/stable/gtk-running.html +# GTK4 environment variables: https://developer.gnome.org/gtk4/stable/gtk-running.html + +# abort on all errors +set -e + +if [ "$DEBUG" != "" ]; then + set -x + verbose="--verbose" +fi + +SCRIPT="$(basename "$(readlink -f "$0")")" + +show_usage() { + echo "Usage: $SCRIPT --appdir