diff --git a/v2/go.sum b/v2/go.sum index fdfdb76bd..78b174740 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -35,8 +35,6 @@ github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/harry1453/go-common-file-dialog v1.0.0 h1:fzBAGhRTqWQyJw5xkm0PSsA+d3CBYBrfh+Nayb6U0nM= -github.com/harry1453/go-common-file-dialog v1.0.0/go.mod h1:3zwmbo7fy+uYGyaec74mu+Z9DPg0aEt10fSjjPwfyiY= github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/jackmordaunt/icns v1.0.0 h1:RYSxplerf/l/DUd09AHtITwckkv/mqjVv4DjYdPmAMQ= diff --git a/v2/internal/app/dev.go b/v2/internal/app/dev.go index b92fb441e..d368c607e 100644 --- a/v2/internal/app/dev.go +++ b/v2/internal/app/dev.go @@ -125,7 +125,7 @@ func (a *App) Run() error { return err } - runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback) + runtimesubsystem, err := subsystem.NewRuntime(ctx, a.servicebus, a.logger, a.startupCallback, nil) if err != nil { return err } diff --git a/v2/internal/ffenestri/ffenestri.go b/v2/internal/ffenestri/ffenestri.go index 5d5939d15..1aed0d7b2 100644 --- a/v2/internal/ffenestri/ffenestri.go +++ b/v2/internal/ffenestri/ffenestri.go @@ -41,8 +41,6 @@ type Application struct { // Logger logger logger.CustomLogger - // Window handle (used by windows) - hwnd unsafe.Pointer } func (a *Application) saveMemoryReference(mem unsafe.Pointer) { diff --git a/v2/internal/ffenestri/ffenestri.h b/v2/internal/ffenestri/ffenestri.h index 8df2090d3..7c2913a2c 100644 --- a/v2/internal/ffenestri/ffenestri.h +++ b/v2/internal/ffenestri/ffenestri.h @@ -47,6 +47,7 @@ extern void AddContextMenu(struct Application*, char *contextMenuJSON); extern void UpdateContextMenu(struct Application*, char *contextMenuJSON); extern void WebviewIsTransparent(struct Application*); extern void WindowBackgroundIsTranslucent(struct Application*); +extern void* GetWindowHandle(struct Application*); #ifdef __cplusplus } diff --git a/v2/internal/ffenestri/ffenestri_client.go b/v2/internal/ffenestri/ffenestri_client.go index 73d7165ec..6c279ab09 100644 --- a/v2/internal/ffenestri/ffenestri_client.go +++ b/v2/internal/ffenestri/ffenestri_client.go @@ -1,3 +1,5 @@ +// +build !windows + package ffenestri /* @@ -15,7 +17,7 @@ import ( "github.com/wailsapp/wails/v2/internal/logger" ) -// Client is our implentation of messageDispatcher.Client +// Client is our implementation of messageDispatcher.Client type Client struct { app *Application logger logger.CustomLogger @@ -124,8 +126,8 @@ func (c *Client) WindowSetColour(colour int) { C.SetColour(c.app.app, r, g, b, a) } -// OpenDialog will open a dialog with the given title and filter -func (c *Client) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) { +// OpenFileDialog will open a dialog with the given title and filter +func (c *Client) OpenFileDialog(dialogOptions *dialog.OpenDialog, callbackID string) { filters := []string{} if runtime.GOOS == "darwin" { for _, filter := range dialogOptions.Filters { @@ -148,8 +150,33 @@ func (c *Client) OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) ) } -// OpenMultipleDialog will open a dialog with the given title and filter -func (c *Client) OpenMultipleDialog(dialogOptions *dialog.OpenDialog, callbackID string) { + +// OpenDirectoryDialog will open a dialog with the given title and filter +func (c *Client) OpenDirectoryDialog(dialogOptions *dialog.OpenDialog, callbackID string) { + filters := []string{} + if runtime.GOOS == "darwin" { + for _, filter := range dialogOptions.Filters { + filters = append(filters, strings.Split(filter.Pattern, ",")...) + } + } + C.OpenDialog(c.app.app, + c.app.string2CString(callbackID), + c.app.string2CString(dialogOptions.Title), + c.app.string2CString(strings.Join(filters, ";")), + c.app.string2CString(dialogOptions.DefaultFilename), + c.app.string2CString(dialogOptions.DefaultDirectory), + c.app.bool2Cint(false), // Files + c.app.bool2Cint(true), // Directories + c.app.bool2Cint(false), // Multiple + c.app.bool2Cint(dialogOptions.ShowHiddenFiles), + c.app.bool2Cint(dialogOptions.CanCreateDirectories), + c.app.bool2Cint(dialogOptions.ResolvesAliases), + c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories), + ) +} + +// OpenMultipleFilesDialog will open a dialog with the given title and filter +func (c *Client) OpenMultipleFilesDialog(dialogOptions *dialog.OpenDialog, callbackID string) { filters := []string{} if runtime.GOOS == "darwin" { for _, filter := range dialogOptions.Filters { diff --git a/v2/internal/ffenestri/ffenestri_client_windows.go b/v2/internal/ffenestri/ffenestri_client_windows.go new file mode 100644 index 000000000..cf22fa55e --- /dev/null +++ b/v2/internal/ffenestri/ffenestri_client_windows.go @@ -0,0 +1,301 @@ +// +build windows + +package ffenestri + +/* +#include "ffenestri.h" +*/ +import "C" + +import ( + "encoding/json" + "github.com/harry1453/go-common-file-dialog/cfd" + "log" + "runtime" + "strconv" + "strings" + + "github.com/wailsapp/wails/v2/pkg/options/dialog" + + "github.com/wailsapp/wails/v2/internal/logger" +) + +// Client is our implementation of messageDispatcher.Client +type Client struct { + app *Application + logger logger.CustomLogger +} + +func newClient(app *Application) *Client { + return &Client{ + app: app, + logger: app.logger, + } +} + +// Quit the application +func (c *Client) Quit() { + c.app.logger.Trace("Got shutdown message") + C.Quit(c.app.app) +} + +// NotifyEvent will pass on the event message to the frontend +func (c *Client) NotifyEvent(message string) { + eventMessage := `window.wails._.Notify(` + strconv.Quote(message) + `);` + c.app.logger.Trace("eventMessage = %+v", eventMessage) + C.ExecJS(c.app.app, c.app.string2CString(eventMessage)) +} + +// CallResult contains the result of the call from JS +func (c *Client) CallResult(message string) { + callbackMessage := `window.wails._.Callback(` + strconv.Quote(message) + `);` + c.app.logger.Trace("callbackMessage = %+v", callbackMessage) + C.ExecJS(c.app.app, c.app.string2CString(callbackMessage)) +} + +// WindowSetTitle sets the window title to the given string +func (c *Client) WindowSetTitle(title string) { + C.SetTitle(c.app.app, c.app.string2CString(title)) +} + +// WindowFullscreen will set the window to be fullscreen +func (c *Client) WindowFullscreen() { + C.Fullscreen(c.app.app) +} + +// WindowUnFullscreen will unfullscreen the window +func (c *Client) WindowUnFullscreen() { + C.UnFullscreen(c.app.app) +} + +// WindowShow will show the window +func (c *Client) WindowShow() { + C.Show(c.app.app) +} + +// WindowHide will hide the window +func (c *Client) WindowHide() { + C.Hide(c.app.app) +} + +// WindowCenter will hide the window +func (c *Client) WindowCenter() { + C.Center(c.app.app) +} + +// WindowMaximise will maximise the window +func (c *Client) WindowMaximise() { + C.Maximise(c.app.app) +} + +// WindowMinimise will minimise the window +func (c *Client) WindowMinimise() { + C.Minimise(c.app.app) +} + +// WindowUnmaximise will unmaximise the window +func (c *Client) WindowUnmaximise() { + C.Unmaximise(c.app.app) +} + +// WindowUnminimise will unminimise the window +func (c *Client) WindowUnminimise() { + C.Unminimise(c.app.app) +} + +// WindowPosition will position the window to x,y on the +// monitor that the window is mostly on +func (c *Client) WindowPosition(x int, y int) { + C.SetPosition(c.app.app, C.int(x), C.int(y)) +} + +// WindowSize will resize the window to the given +// width and height +func (c *Client) WindowSize(width int, height int) { + C.SetSize(c.app.app, C.int(width), C.int(height)) +} + +func (c *Client) WindowSetMinSize(width int, height int) { + C.SetMinWindowSize(c.app.app, C.int(width), C.int(height)) +} + +func (c *Client) WindowSetMaxSize(width int, height int) { + C.SetMaxWindowSize(c.app.app, C.int(width), C.int(height)) +} + +// WindowSetColour sets the window colour +func (c *Client) WindowSetColour(colour int) { + r, g, b, a := intToColour(colour) + C.SetColour(c.app.app, r, g, b, a) +} + +func convertFilters(filters []dialog.FileFilter) []cfd.FileFilter { + var result []cfd.FileFilter + for _, filter := range filters { + result = append(result, cfd.FileFilter(filter)) + } + return result +} + +// OpenFileDialog will open a dialog with the given title and filter +func (c *Client) OpenFileDialog(options *dialog.OpenDialog, callbackID string) { + config := cfd.DialogConfig{ + Folder: options.DefaultDirectory, + FileFilters: convertFilters(options.Filters), + FileName: options.DefaultFilename, + } + thisdialog, err := cfd.NewOpenFileDialog(config) + if err != nil { + log.Fatal(err) + } + thisdialog.SetParentWindowHandle(uintptr(C.GetWindowHandle(c.app.app))) + defer func(thisdialog cfd.OpenFileDialog) { + err := thisdialog.Release() + if err != nil { + log.Fatal(err) + } + }(thisdialog) + result, err := thisdialog.ShowAndGetResult() + if err != nil { + log.Fatal(err) + } + + resultJSON, err := json.Marshal([]string{result}) + if err != nil { + log.Fatal(err) + } + dispatcher.DispatchMessage("DO" + callbackID + "|" + string(resultJSON)) + +} + + +// OpenDirectoryDialog will open a dialog with the given title and filter +func (c *Client) OpenDirectoryDialog(dialogOptions *dialog.OpenDialog, callbackID string) { + config := cfd.DialogConfig{ + Title: dialogOptions.Title, + Role: "PickFolder", + Folder: dialogOptions.DefaultDirectory, + } + thisDialog, err := cfd.NewSelectFolderDialog(config) + if err != nil { + log.Fatal() + } + thisDialog.SetParentWindowHandle(uintptr(C.GetWindowHandle(c.app.app))) + defer func(thisDialog cfd.SelectFolderDialog) { + err := thisDialog.Release() + if err != nil { + log.Fatal(err) + } + }(thisDialog) + result, err := thisDialog.ShowAndGetResult() + resultJSON, err := json.Marshal(result) + if err != nil { + log.Fatal(err) + } + dispatcher.DispatchMessage("DD" + callbackID + "|" + string(resultJSON)) +} + +// OpenMultipleFilesDialog will open a dialog with the given title and filter +func (c *Client) OpenMultipleFilesDialog(dialogOptions *dialog.OpenDialog, callbackID string) { + config := cfd.DialogConfig{ + Title: dialogOptions.Title, + Role: "OpenMultipleFiles", + FileFilters: convertFilters(dialogOptions.Filters), + FileName: dialogOptions.DefaultFilename, + Folder: dialogOptions.DefaultDirectory, + } + thisdialog, err := cfd.NewOpenMultipleFilesDialog(config) + if err != nil { + log.Fatal(err) + } + handle := uintptr(C.GetWindowHandle(c.app.app)) + thisdialog.SetParentWindowHandle(handle) + defer func(thisdialog cfd.OpenMultipleFilesDialog) { + err := thisdialog.Release() + if err != nil { + log.Fatal(err) + } + }(thisdialog) + result, err := thisdialog.ShowAndGetResults() + if err != nil { + log.Fatal(err) + } + resultJSON, err := json.Marshal(result) + if err != nil { + log.Fatal(err) + } + dispatcher.DispatchMessage("D*" + callbackID + "|" + string(resultJSON)) +} + +// SaveDialog will open a dialog with the given title and filter +func (c *Client) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) { + filters := []string{} + if runtime.GOOS == "darwin" { + for _, filter := range dialogOptions.Filters { + filters = append(filters, strings.Split(filter.Pattern, ",")...) + } + } + C.SaveDialog(c.app.app, + c.app.string2CString(callbackID), + c.app.string2CString(dialogOptions.Title), + c.app.string2CString(strings.Join(filters, ";")), + c.app.string2CString(dialogOptions.DefaultFilename), + c.app.string2CString(dialogOptions.DefaultDirectory), + c.app.bool2Cint(dialogOptions.ShowHiddenFiles), + c.app.bool2Cint(dialogOptions.CanCreateDirectories), + c.app.bool2Cint(dialogOptions.TreatPackagesAsDirectories), + ) +} + +// MessageDialog will open a message dialog with the given options +func (c *Client) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) { + + // Sanity check button length + if len(dialogOptions.Buttons) > 4 { + c.app.logger.Error("Given %d message dialog buttons. Maximum is 4", len(dialogOptions.Buttons)) + return + } + + // Process buttons + buttons := []string{"", "", "", ""} + for i, button := range dialogOptions.Buttons { + buttons[i] = button + } + + C.MessageDialog(c.app.app, + c.app.string2CString(callbackID), + c.app.string2CString(string(dialogOptions.Type)), + c.app.string2CString(dialogOptions.Title), + c.app.string2CString(dialogOptions.Message), + c.app.string2CString(dialogOptions.Icon), + c.app.string2CString(buttons[0]), + c.app.string2CString(buttons[1]), + c.app.string2CString(buttons[2]), + c.app.string2CString(buttons[3]), + c.app.string2CString(dialogOptions.DefaultButton), + c.app.string2CString(dialogOptions.CancelButton)) +} + +func (c *Client) DarkModeEnabled(callbackID string) { + C.DarkModeEnabled(c.app.app, c.app.string2CString(callbackID)) +} + +func (c *Client) SetApplicationMenu(applicationMenuJSON string) { + C.SetApplicationMenu(c.app.app, c.app.string2CString(applicationMenuJSON)) +} + +func (c *Client) SetTrayMenu(trayMenuJSON string) { + C.SetTrayMenu(c.app.app, c.app.string2CString(trayMenuJSON)) +} + +func (c *Client) UpdateTrayMenuLabel(JSON string) { + C.UpdateTrayMenuLabel(c.app.app, c.app.string2CString(JSON)) +} + +func (c *Client) UpdateContextMenu(contextMenuJSON string) { + C.UpdateContextMenu(c.app.app, c.app.string2CString(contextMenuJSON)) +} + +func (c *Client) DeleteTrayMenuByID(id string) { + C.DeleteTrayMenuByID(c.app.app, c.app.string2CString(id)) +} diff --git a/v2/internal/ffenestri/ffenestri_windows.cpp b/v2/internal/ffenestri/ffenestri_windows.cpp index bec59cb4e..36f25e734 100644 --- a/v2/internal/ffenestri/ffenestri_windows.cpp +++ b/v2/internal/ffenestri/ffenestri_windows.cpp @@ -88,6 +88,10 @@ struct Application *NewApplication(const char *title, int width, int height, int return result; } +void* GetWindowHandle(struct Application *app) { + return (void*)app->window; +} + void SetMinWindowSize(struct Application* app, int minWidth, int minHeight) { app->minWidth = (LONG)minWidth; app->minHeight = (LONG)minHeight; diff --git a/v2/internal/ffenestri/ffenestri_windows.h b/v2/internal/ffenestri/ffenestri_windows.h index b4a008f37..1da22dc9b 100644 --- a/v2/internal/ffenestri/ffenestri_windows.h +++ b/v2/internal/ffenestri/ffenestri_windows.h @@ -72,6 +72,7 @@ void completed(struct Application* app); extern "C" { void DisableWindowIcon(struct Application* app); void messageFromWindowCallback(const char *); + void* GetWindowHandle(struct Application*); } #endif \ No newline at end of file diff --git a/v2/internal/messagedispatcher/dispatchclient.go b/v2/internal/messagedispatcher/dispatchclient.go index 1c398c420..edf559ced 100644 --- a/v2/internal/messagedispatcher/dispatchclient.go +++ b/v2/internal/messagedispatcher/dispatchclient.go @@ -14,7 +14,9 @@ type Client interface { Quit() NotifyEvent(message string) CallResult(message string) - OpenDialog(dialogOptions *dialog.OpenDialog, callbackID string) + OpenFileDialog(dialogOptions *dialog.OpenDialog, callbackID string) + OpenMultipleFilesDialog(dialogOptions *dialog.OpenDialog, callbackID string) + OpenDirectoryDialog(dialogOptions *dialog.OpenDialog, callbackID string) SaveDialog(dialogOptions *dialog.SaveDialog, callbackID string) MessageDialog(dialogOptions *dialog.MessageDialog, callbackID string) WindowSetTitle(title string) diff --git a/v2/internal/messagedispatcher/message/dialog.go b/v2/internal/messagedispatcher/message/dialog.go index 7fdf66952..666436563 100644 --- a/v2/internal/messagedispatcher/message/dialog.go +++ b/v2/internal/messagedispatcher/message/dialog.go @@ -32,13 +32,29 @@ func dialogMessageParser(message string) (*parsedMessage, error) { switch dialogType { case 'O': - var data []string + var data string topic = "dialog:openselected:" + callbackID err := json.Unmarshal([]byte(payloadData), &data) if err != nil { return nil, err } responseMessage = &parsedMessage{Topic: topic, Data: data} + case 'D': + var data string + topic = "dialog:opendirectoryselected:" + callbackID + err := json.Unmarshal([]byte(payloadData), &data) + if err != nil { + return nil, err + } + responseMessage = &parsedMessage{Topic: topic, Data: data} + case '*': + var data []string + topic = "dialog:openmultipleselected:" + callbackID + err := json.Unmarshal([]byte(payloadData), &data) + if err != nil { + return nil, err + } + responseMessage = &parsedMessage{Topic: topic, Data: data} case 'S': topic = "dialog:saveselected:" + callbackID responseMessage = &parsedMessage{Topic: topic, Data: payloadData} diff --git a/v2/internal/messagedispatcher/messagedispatcher.go b/v2/internal/messagedispatcher/messagedispatcher.go index 5411bc22d..095fd38de 100644 --- a/v2/internal/messagedispatcher/messagedispatcher.go +++ b/v2/internal/messagedispatcher/messagedispatcher.go @@ -422,7 +422,35 @@ func (d *Dispatcher) processDialogMessage(result *servicebus.Message) { // TODO: Work out what we mean in a multi window environment... // For now we will just pick the first one for _, client := range d.clients { - client.frontend.OpenDialog(dialogOptions, callbackID) + client.frontend.OpenFileDialog(dialogOptions, callbackID) + } + case "openmultiple": + dialogOptions, ok := result.Data().(*dialog.OpenDialog) + if !ok { + d.logger.Error("Invalid data for 'dialog:select:openmultiple' : %#v", result.Data()) + return + } + // This is hardcoded in the sender too + callbackID := splitTopic[3] + + // TODO: Work out what we mean in a multi window environment... + // For now we will just pick the first one + for _, client := range d.clients { + client.frontend.OpenMultipleFilesDialog(dialogOptions, callbackID) + } + case "directory": + dialogOptions, ok := result.Data().(*dialog.OpenDialog) + if !ok { + d.logger.Error("Invalid data for 'dialog:select:directory' : %#v", result.Data()) + return + } + // This is hardcoded in the sender too + callbackID := splitTopic[3] + + // TODO: Work out what we mean in a multi window environment... + // For now we will just pick the first one + for _, client := range d.clients { + client.frontend.OpenDirectoryDialog(dialogOptions, callbackID) } case "save": dialogOptions, ok := result.Data().(*dialog.SaveDialog) diff --git a/v2/internal/runtime/dialog.go b/v2/internal/runtime/dialog.go index fe11051aa..47bf025bd 100644 --- a/v2/internal/runtime/dialog.go +++ b/v2/internal/runtime/dialog.go @@ -1,5 +1,3 @@ -// +build !windows - package runtime import ( @@ -31,6 +29,7 @@ func newDialog(bus *servicebus.ServiceBus) Dialog { } } + // processTitleAndFilter return the title and filter from the given params. // title is the first string, filter is the second func (r *dialog) processTitleAndFilter(params ...string) (string, string) { @@ -48,7 +47,8 @@ func (r *dialog) processTitleAndFilter(params ...string) (string, string) { return title, filter } -func OpenDirectory(dialogOptions *dialogoptions.OpenDialog) (string, error) { +// OpenDirectory prompts the user to select a directory +func (r *dialog) OpenDirectory(dialogOptions *dialogoptions.OpenDialog) (string, error) { // Create unique dialog callback uniqueCallback := crypto.RandomID() @@ -57,14 +57,14 @@ func OpenDirectory(dialogOptions *dialogoptions.OpenDialog) (string, error) { responseTopic := "dialog:opendirectoryselected:" + uniqueCallback dialogResponseChannel, err := r.bus.Subscribe(responseTopic) if err != nil { - return nil, fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error()) + return "", fmt.Errorf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error()) } - message := "dialog:selectdirectory:open:" + uniqueCallback + message := "dialog:select:directory:" + uniqueCallback r.bus.Publish(message, dialogOptions) // Wait for result - var result *servicebus.Message = <-dialogResponseChannel + var result = <-dialogResponseChannel // Delete subscription to response topic r.bus.UnSubscribe(responseTopic) @@ -72,7 +72,7 @@ func OpenDirectory(dialogOptions *dialogoptions.OpenDialog) (string, error) { return result.Data().(string), nil } -// Open prompts the user to select a file +// OpenFile prompts the user to select a file func (r *dialog) OpenFile(dialogOptions *dialogoptions.OpenDialog) (string, error) { // Create unique dialog callback @@ -82,22 +82,28 @@ func (r *dialog) OpenFile(dialogOptions *dialogoptions.OpenDialog) (string, erro responseTopic := "dialog:openselected:" + uniqueCallback dialogResponseChannel, err := r.bus.Subscribe(responseTopic) if err != nil { - return nil, fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error()) + return "", fmt.Errorf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error()) } message := "dialog:select:open:" + uniqueCallback r.bus.Publish(message, dialogOptions) // Wait for result - var result *servicebus.Message = <-dialogResponseChannel + var result = <-dialogResponseChannel // Delete subscription to response topic r.bus.UnSubscribe(responseTopic) - return result.Data().(string), nil + files := result.Data().([]string) + res := "" + if len(files) > 0 { + res = files[0] + } + + return res, nil } -// OpenMultiple prompts the user to select a file +// OpenMultipleFiles prompts the user to select a file func (r *dialog) OpenMultipleFiles(dialogOptions *dialogoptions.OpenDialog) ([]string, error) { // Create unique dialog callback @@ -107,22 +113,22 @@ func (r *dialog) OpenMultipleFiles(dialogOptions *dialogoptions.OpenDialog) ([]s responseTopic := "dialog:openmultipleselected:" + uniqueCallback dialogResponseChannel, err := r.bus.Subscribe(responseTopic) if err != nil { - return nil, fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error()) + return nil, fmt.Errorf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error()) } message := "dialog:select:openmultiple:" + uniqueCallback r.bus.Publish(message, dialogOptions) // Wait for result - var result *servicebus.Message = <-dialogResponseChannel + var result = <-dialogResponseChannel // Delete subscription to response topic r.bus.UnSubscribe(responseTopic) - return result.Data().(string), nil + return result.Data().([]string), nil } -// Save prompts the user to select a file +// SaveFile prompts the user to select a file func (r *dialog) SaveFile(dialogOptions *dialogoptions.SaveDialog) (string, error) { // Create unique dialog callback @@ -132,14 +138,14 @@ func (r *dialog) SaveFile(dialogOptions *dialogoptions.SaveDialog) (string, erro responseTopic := "dialog:saveselected:" + uniqueCallback dialogResponseChannel, err := r.bus.Subscribe(responseTopic) if err != nil { - return nil, fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error()) + return "", fmt.Errorf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error()) } message := "dialog:select:save:" + uniqueCallback r.bus.Publish(message, dialogOptions) // Wait for result - var result *servicebus.Message = <-dialogResponseChannel + var result = <-dialogResponseChannel // Delete subscription to response topic r.bus.UnSubscribe(responseTopic) @@ -148,7 +154,7 @@ func (r *dialog) SaveFile(dialogOptions *dialogoptions.SaveDialog) (string, erro } // Message show a message to the user -func (r *dialog) Message(dialogOptions *dialogoptions.MessageDialog) string { +func (r *dialog) Message(dialogOptions *dialogoptions.MessageDialog) (string, error) { // Create unique dialog callback uniqueCallback := crypto.RandomID() @@ -157,17 +163,17 @@ func (r *dialog) Message(dialogOptions *dialogoptions.MessageDialog) string { responseTopic := "dialog:messageselected:" + uniqueCallback dialogResponseChannel, err := r.bus.Subscribe(responseTopic) if err != nil { - fmt.Printf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error()) + return "", fmt.Errorf("ERROR: Cannot subscribe to bus topic: %+v\n", err.Error()) } message := "dialog:select:message:" + uniqueCallback r.bus.Publish(message, dialogOptions) // Wait for result - var result *servicebus.Message = <-dialogResponseChannel + var result = <-dialogResponseChannel // Delete subscription to response topic r.bus.UnSubscribe(responseTopic) - return result.Data().(string) + return result.Data().(string), nil } diff --git a/v2/internal/runtime/dialog_windows.go b/v2/internal/runtime/dialog_windows.go deleted file mode 100644 index 373f3a4c1..000000000 --- a/v2/internal/runtime/dialog_windows.go +++ /dev/null @@ -1,141 +0,0 @@ -// +build windows - -package runtime - -import ( - "golang.org/x/sys/windows" - "syscall" - - "github.com/harry1453/go-common-file-dialog/cfd" - "github.com/harry1453/go-common-file-dialog/cfdutil" - "github.com/wailsapp/wails/v2/internal/servicebus" - dialogoptions "github.com/wailsapp/wails/v2/pkg/options/dialog" -) - -// Dialog defines all Dialog related operations -type Dialog interface { - OpenFile(dialogOptions *dialogoptions.OpenDialog) (string, error) - OpenMultipleFiles(dialogOptions *dialogoptions.OpenDialog) ([]string, error) - OpenDirectory(dialogOptions *dialogoptions.OpenDialog) (string, error) - Save(dialogOptions *dialogoptions.SaveDialog) (string, error) - Message(dialogOptions *dialogoptions.MessageDialog) (string, error) -} - -// dialog exposes the Dialog interface -type dialog struct { - bus *servicebus.ServiceBus -} - -// newDialogs creates a new Dialogs struct -func newDialog(bus *servicebus.ServiceBus) Dialog { - return &dialog{ - bus: bus, - } -} - -// processTitleAndFilter return the title and filter from the given params. -// title is the first string, filter is the second -func (r *dialog) processTitleAndFilter(params ...string) (string, string) { - - var title, filter string - - if len(params) > 0 { - title = params[0] - } - - if len(params) > 1 { - filter = params[1] - } - - return title, filter -} - -func convertFilters(filters []dialogoptions.FileFilter) []cfd.FileFilter { - var result []cfd.FileFilter - for _, filter := range filters { - result = append(result, cfd.FileFilter(filter)) - } - return result -} - -func pickMultipleFiles(options *dialogoptions.OpenDialog) ([]string, error) { - - results, err := cfdutil.ShowOpenMultipleFilesDialog(cfd.DialogConfig{ - Title: options.Title, - Role: "OpenMultipleFiles", - FileFilters: convertFilters(options.Filters), - FileName: options.DefaultFilename, - Folder: options.DefaultDirectory, - }) - return results, err -} - -func (r *dialog) OpenMultipleFiles(options *dialogoptions.OpenDialog) ([]string, error) { - return pickMultipleFiles(options) -} - -func (r *dialog) OpenDirectory(options *dialogoptions.OpenDialog) (string, error) { - return cfdutil.ShowPickFolderDialog(cfd.DialogConfig{ - Title: options.Title, - Role: "PickFolder", - Folder: options.DefaultDirectory, - }) -} - -func (r *dialog) OpenFile(options *dialogoptions.OpenDialog) (string, error) { - result, err := cfdutil.ShowOpenFileDialog(cfd.DialogConfig{ - Folder: options.DefaultDirectory, - FileFilters: convertFilters(options.Filters), - FileName: options.DefaultFilename, - }) - return result, err -} - -// Save prompts the user to select a file -func (r *dialog) Save(options *dialogoptions.SaveDialog) (string, error) { - - result, err := cfdutil.ShowSaveFileDialog(cfd.DialogConfig{ - Title: options.Title, - Role: "SaveFile", - FileName: options.DefaultFilename, - FileFilters: convertFilters(options.Filters), - }) - return result, err -} - -// Message show a message to the user -func (r *dialog) Message(options *dialogoptions.MessageDialog) (string, error) { - - // TODO: error handling - title, err := syscall.UTF16PtrFromString(options.Title) - if err != nil { - return "", err - } - message, err := syscall.UTF16PtrFromString(options.Message) - if err != nil { - return "", err - } - var flags uint32 - switch options.Type { - case dialogoptions.InfoDialog: - flags = windows.MB_OK | windows.MB_ICONINFORMATION - case dialogoptions.ErrorDialog: - flags = windows.MB_ICONERROR | windows.MB_OK - case dialogoptions.QuestionDialog: - flags = windows.MB_YESNO - case dialogoptions.WarningDialog: - flags = windows.MB_OK | windows.MB_ICONWARNING - } - - result, _ := windows.MessageBox(0, message, title, flags|windows.MB_SYSTEMMODAL) - if options.Type == dialogoptions.QuestionDialog { - if result == 6 { // IDYES - return "Yes", nil - } - if result == 7 { // IDNO - return "No", nil - } - } - return "", nil - -} diff --git a/v2/internal/subsystem/call.go b/v2/internal/subsystem/call.go index 03808fc38..393773940 100644 --- a/v2/internal/subsystem/call.go +++ b/v2/internal/subsystem/call.go @@ -151,7 +151,7 @@ func (c *Call) processSystemCall(payload *message.CallMessage, clientID string) if err != nil { c.logger.Error("Error decoding: %s", err) } - result, err := c.runtime.Dialog.Save(dialogOptions) + result, err := c.runtime.Dialog.SaveFile(dialogOptions) if err != nil { c.logger.Error("Error: %s", err) } diff --git a/v2/internal/subsystem/runtime.go b/v2/internal/subsystem/runtime.go index 3f890ed96..2c67a29cb 100644 --- a/v2/internal/subsystem/runtime.go +++ b/v2/internal/subsystem/runtime.go @@ -3,12 +3,11 @@ package subsystem import ( "context" "fmt" - "strings" - "sync" - "github.com/wailsapp/wails/v2/internal/logger" "github.com/wailsapp/wails/v2/internal/runtime" "github.com/wailsapp/wails/v2/internal/servicebus" + "strings" + "sync" ) // Runtime is the Runtime subsystem. It handles messages with topics starting