diff --git a/pkg/browser/service.go b/pkg/browser/service.go index b1b6a82f..ce2a289d 100644 --- a/pkg/browser/service.go +++ b/pkg/browser/service.go @@ -14,18 +14,22 @@ type Service struct { } func (s *Service) OnStartup(_ context.Context) core.Result { - s.Core().Action("browser.openURL", func(_ context.Context, opts core.Options) core.Result { + openURL := func(_ context.Context, opts core.Options) core.Result { if err := s.platform.OpenURL(opts.String("url")); err != nil { return core.Result{Value: err, OK: false} } return core.Result{OK: true} - }) - s.Core().Action("browser.openFile", func(_ context.Context, opts core.Options) core.Result { + } + openFile := func(_ context.Context, opts core.Options) core.Result { if err := s.platform.OpenFile(opts.String("path")); err != nil { return core.Result{Value: err, OK: false} } return core.Result{OK: true} - }) + } + s.Core().Action("browser.openURL", openURL) + s.Core().Action("gui.browser.open", openURL) + s.Core().Action("browser.openFile", openFile) + s.Core().Action("gui.browser.openFile", openFile) return core.Result{OK: true} } diff --git a/pkg/clipboard/service.go b/pkg/clipboard/service.go index c0745b21..5091b7ee 100644 --- a/pkg/clipboard/service.go +++ b/pkg/clipboard/service.go @@ -27,11 +27,11 @@ func Register(p Platform) func(*core.Core) core.Result { func (s *Service) OnStartup(_ context.Context) core.Result { s.Core().RegisterQuery(s.handleQuery) - s.Core().Action("clipboard.setText", func(_ context.Context, opts core.Options) core.Result { + setText := func(_ context.Context, opts core.Options) core.Result { success := s.platform.SetText(opts.String("text")) return core.Result{Value: success, OK: true} - }) - s.Core().Action("clipboard.setImage", func(_ context.Context, opts core.Options) core.Result { + } + setImage := func(_ context.Context, opts core.Options) core.Result { imgPlatform, ok := s.platform.(ImagePlatform) if !ok { return core.Result{Value: false, OK: true} @@ -39,14 +39,24 @@ func (s *Service) OnStartup(_ context.Context) core.Result { data, _ := opts.Get("data").Value.([]byte) success := imgPlatform.SetImage(data) return core.Result{Value: success, OK: true} - }) - s.Core().Action("clipboard.clear", func(_ context.Context, _ core.Options) core.Result { + } + clear := func(_ context.Context, _ core.Options) core.Result { success := s.platform.SetText("") if imgPlatform, ok := s.platform.(ImagePlatform); ok { success = imgPlatform.SetImage(nil) && success } return core.Result{Value: success, OK: true} - }) + } + read := func(_ context.Context, _ core.Options) core.Result { + text, ok := s.platform.Text() + return core.Result{Value: ClipboardContent{Text: text, HasContent: ok && text != ""}, OK: true} + } + s.Core().Action("clipboard.setText", setText) + s.Core().Action("gui.clipboard.write", setText) + s.Core().Action("clipboard.setImage", setImage) + s.Core().Action("clipboard.clear", clear) + s.Core().Action("gui.clipboard.clear", clear) + s.Core().Action("gui.clipboard.read", read) return core.Result{OK: true} } diff --git a/pkg/dialog/service.go b/pkg/dialog/service.go index 266e8d84..29190125 100644 --- a/pkg/dialog/service.go +++ b/pkg/dialog/service.go @@ -31,78 +31,78 @@ func Register(p Platform) func(*core.Core) core.Result { } func (s *Service) OnStartup(_ context.Context) core.Result { - s.Core().Action("dialog.openFile", func(_ context.Context, opts core.Options) core.Result { - var openOpts OpenFileOptions - switch v := opts.Get("task").Value.(type) { - case TaskOpenFile: - openOpts = v.Options - case TaskOpenFileWithOptions: - if v.Options != nil { - openOpts = *v.Options - } + openFile := func(_ context.Context, opts core.Options) core.Result { + openOpts, err := openFileOptionsFrom(opts) + if err != nil { + return core.Result{Value: err, OK: false} } paths, err := s.platform.OpenFile(openOpts) return core.Result{}.New(paths, err) - }) - s.Core().Action("dialog.saveFile", func(_ context.Context, opts core.Options) core.Result { - var saveOpts SaveFileOptions - switch v := opts.Get("task").Value.(type) { - case TaskSaveFile: - saveOpts = v.Options - case TaskSaveFileWithOptions: - if v.Options != nil { - saveOpts = *v.Options - } + } + saveFile := func(_ context.Context, opts core.Options) core.Result { + saveOpts, err := saveFileOptionsFrom(opts) + if err != nil { + return core.Result{Value: err, OK: false} } path, err := s.platform.SaveFile(saveOpts) return core.Result{}.New(path, err) - }) - s.Core().Action("dialog.openDirectory", func(_ context.Context, opts core.Options) core.Result { - t, _ := opts.Get("task").Value.(TaskOpenDirectory) - path, err := s.platform.OpenDirectory(t.Options) + } + openDirectory := func(_ context.Context, opts core.Options) core.Result { + openOpts, err := openDirectoryOptionsFrom(opts) + if err != nil { + return core.Result{Value: err, OK: false} + } + path, err := s.platform.OpenDirectory(openOpts) return core.Result{}.New(path, err) - }) - s.Core().Action("dialog.message", func(_ context.Context, opts core.Options) core.Result { - t, _ := opts.Get("task").Value.(TaskMessageDialog) - button, err := s.platform.MessageDialog(t.Options) + } + messageDialog := func(_ context.Context, opts core.Options) core.Result { + messageOpts, err := messageDialogOptionsFrom(opts) + if err != nil { + return core.Result{Value: err, OK: false} + } + button, err := s.platform.MessageDialog(messageOpts) return core.Result{}.New(button, err) - }) - s.Core().Action("dialog.info", func(_ context.Context, opts core.Options) core.Result { + } + info := func(_ context.Context, opts core.Options) core.Result { t, _ := opts.Get("task").Value.(TaskInfo) button, err := s.platform.MessageDialog(MessageDialogOptions{ Type: DialogInfo, Title: t.Title, Message: t.Message, Buttons: t.Buttons, }) return core.Result{}.New(button, err) - }) - s.Core().Action("dialog.question", func(_ context.Context, opts core.Options) core.Result { - t, _ := opts.Get("task").Value.(TaskQuestion) - button, err := s.platform.MessageDialog(MessageDialogOptions{ - Type: DialogQuestion, Title: t.Title, Message: t.Message, Buttons: t.Buttons, - }) + } + question := func(_ context.Context, opts core.Options) core.Result { + questionOpts, err := questionDialogOptionsFrom(opts) + if err != nil { + return core.Result{Value: err, OK: false} + } + button, err := s.platform.MessageDialog(questionOpts) return core.Result{}.New(button, err) - }) - s.Core().Action("dialog.warning", func(_ context.Context, opts core.Options) core.Result { + } + warning := func(_ context.Context, opts core.Options) core.Result { t, _ := opts.Get("task").Value.(TaskWarning) button, err := s.platform.MessageDialog(MessageDialogOptions{ Type: DialogWarning, Title: t.Title, Message: t.Message, Buttons: t.Buttons, }) return core.Result{}.New(button, err) - }) - s.Core().Action("dialog.error", func(_ context.Context, opts core.Options) core.Result { + } + errDialog := func(_ context.Context, opts core.Options) core.Result { t, _ := opts.Get("task").Value.(TaskError) button, err := s.platform.MessageDialog(MessageDialogOptions{ Type: DialogError, Title: t.Title, Message: t.Message, Buttons: t.Buttons, }) return core.Result{}.New(button, err) - }) - s.Core().Action("dialog.prompt", func(_ context.Context, opts core.Options) core.Result { - t, _ := opts.Get("task").Value.(TaskPrompt) + } + prompt := func(_ context.Context, opts core.Options) core.Result { + promptOpts, err := promptOptionsFrom(opts) + if err != nil { + return core.Result{Value: err, OK: false} + } windowName, err := s.promptWindowName() if err != nil { return core.Result{Value: err, OK: false} } - script := promptScript(t.Title, t.Message, t.DefaultValue) - result := s.Core().Action("webview.evaluate").Run(context.Background(), core.NewOptions( + script := promptScript(promptOpts.Title, promptOpts.Message, promptOpts.DefaultValue) + result := s.Core().Action("gui.webview.eval").Run(context.Background(), core.NewOptions( core.Option{Key: "task", Value: webview.TaskEvaluate{Window: windowName, Script: script}}, )) if !result.OK { @@ -119,7 +119,21 @@ func (s *Service) OnStartup(_ context.Context) core.Result { default: return core.Result{Value: PromptResult{Value: fmt.Sprint(value), Confirmed: true}, OK: true} } - }) + } + s.Core().Action("dialog.openFile", openFile) + s.Core().Action("gui.dialog.open", openFile) + s.Core().Action("dialog.saveFile", saveFile) + s.Core().Action("gui.dialog.save", saveFile) + s.Core().Action("dialog.openDirectory", openDirectory) + s.Core().Action("dialog.message", messageDialog) + s.Core().Action("gui.dialog.message", messageDialog) + s.Core().Action("dialog.info", info) + s.Core().Action("dialog.question", question) + s.Core().Action("gui.dialog.confirm", question) + s.Core().Action("dialog.warning", warning) + s.Core().Action("dialog.error", errDialog) + s.Core().Action("dialog.prompt", prompt) + s.Core().Action("gui.dialog.prompt", prompt) return core.Result{OK: true} } @@ -127,6 +141,115 @@ func (s *Service) HandleIPCEvents(_ *core.Core, _ core.Message) core.Result { return core.Result{OK: true} } +func openFileOptionsFrom(opts core.Options) (OpenFileOptions, error) { + if task := opts.Get("task"); task.OK { + switch v := task.Value.(type) { + case TaskOpenFile: + return v.Options, nil + case TaskOpenFileWithOptions: + if v.Options != nil { + return *v.Options, nil + } + case OpenFileOptions: + return v, nil + } + } + return decodeOptions[OpenFileOptions](opts) +} + +func saveFileOptionsFrom(opts core.Options) (SaveFileOptions, error) { + if task := opts.Get("task"); task.OK { + switch v := task.Value.(type) { + case TaskSaveFile: + return v.Options, nil + case TaskSaveFileWithOptions: + if v.Options != nil { + return *v.Options, nil + } + case SaveFileOptions: + return v, nil + } + } + return decodeOptions[SaveFileOptions](opts) +} + +func openDirectoryOptionsFrom(opts core.Options) (OpenDirectoryOptions, error) { + if task := opts.Get("task"); task.OK { + switch v := task.Value.(type) { + case TaskOpenDirectory: + return v.Options, nil + case OpenDirectoryOptions: + return v, nil + } + } + return decodeOptions[OpenDirectoryOptions](opts) +} + +func messageDialogOptionsFrom(opts core.Options) (MessageDialogOptions, error) { + if task := opts.Get("task"); task.OK { + switch v := task.Value.(type) { + case TaskMessageDialog: + return v.Options, nil + case MessageDialogOptions: + return v, nil + } + } + return decodeOptions[MessageDialogOptions](opts) +} + +func questionDialogOptionsFrom(opts core.Options) (MessageDialogOptions, error) { + if task := opts.Get("task"); task.OK { + switch v := task.Value.(type) { + case TaskQuestion: + return MessageDialogOptions{ + Type: DialogQuestion, + Title: v.Title, + Message: v.Message, + Buttons: v.Buttons, + }, nil + case MessageDialogOptions: + return v, nil + } + } + if direct, err := decodeOptions[TaskQuestion](opts); err == nil && (direct.Title != "" || direct.Message != "" || len(direct.Buttons) > 0) { + return MessageDialogOptions{ + Type: DialogQuestion, + Title: direct.Title, + Message: direct.Message, + Buttons: direct.Buttons, + }, nil + } + return decodeOptions[MessageDialogOptions](opts) +} + +func promptOptionsFrom(opts core.Options) (TaskPrompt, error) { + if task := opts.Get("task"); task.OK { + if v, ok := task.Value.(TaskPrompt); ok { + return v, nil + } + } + return decodeOptions[TaskPrompt](opts) +} + +func decodeOptions[T any](opts core.Options) (T, error) { + var input T + items := make(map[string]any, opts.Len()) + for _, item := range opts.Items() { + items[item.Key] = item.Value + } + if len(items) == 0 { + return input, nil + } + result := core.JSONUnmarshalString(core.JSONMarshalString(items), &input) + if !result.OK { + if err, ok := result.Value.(error); ok { + return input, err + } + return input, coreerr.E("dialog.decodeOptions", "failed to decode dialog options", nil) + } + return input, nil +} + func (s *Service) promptWindowName() (string, error) { r := s.Core().QUERY(window.QueryWindowList{}) if !r.OK { diff --git a/pkg/display/preload.go b/pkg/display/preload.go index 494bddc7..a642bbdc 100644 --- a/pkg/display/preload.go +++ b/pkg/display/preload.go @@ -309,18 +309,23 @@ func (s *Service) injectElectronShim() string { }; const shell = { openExternal(url) { - return invokeBridge('browser.openURL', { url }).then(() => undefined); + return invokeBridge('gui.browser.open', { url }).then(() => undefined); }, openPath(path) { - return invokeBridge('browser.openFile', { path }).then(() => ""); + return invokeBridge('gui.browser.openFile', { path }).then(() => ""); } }; const clipboard = { readText() { - return globalThis.navigator?.clipboard?.readText?.() ?? Promise.resolve(""); + return invokeBridge('gui.clipboard.read', {}).then((value) => { + if (typeof value === "string") { + return value; + } + return value?.text ?? value?.Text ?? ""; + }); }, writeText(text) { - return invokeBridge('clipboard.setText', { text }).then(() => undefined); + return invokeBridge('gui.clipboard.write', { text }).then(() => undefined); } }; const invokeBridge = (route, payload) => (globalThis.__coreBridge?.invoke?.(route, payload) ?? Promise.resolve({ route, payload })); diff --git a/pkg/notification/service.go b/pkg/notification/service.go index 84bc28d2..faacc0a2 100644 --- a/pkg/notification/service.go +++ b/pkg/notification/service.go @@ -8,6 +8,7 @@ import ( "time" core "dappco.re/go/core" + coreerr "dappco.re/go/core/log" "forge.lthn.ai/core/gui/pkg/dialog" ) @@ -34,10 +35,13 @@ func Register(p Platform) func(*core.Core) core.Result { func (s *Service) OnStartup(_ context.Context) core.Result { s.Core().RegisterQuery(s.handleQuery) - s.Core().Action("notification.send", func(_ context.Context, opts core.Options) core.Result { - t, _ := opts.Get("task").Value.(TaskSend) - return core.Result{Value: nil, OK: true}.New(s.send(t.Options)) - }) + send := func(_ context.Context, opts core.Options) core.Result { + options, err := notificationOptionsFrom(opts) + if err != nil { + return core.Result{Value: err, OK: false} + } + return core.Result{Value: nil, OK: true}.New(s.send(options)) + } s.Core().Action("notification.requestPermission", func(_ context.Context, _ core.Options) core.Result { granted, err := s.platform.RequestPermission() return core.Result{}.New(granted, err) @@ -54,6 +58,8 @@ func (s *Service) OnStartup(_ context.Context) core.Result { t, _ := opts.Get("task").Value.(TaskClear) return core.Result{Value: nil, OK: true}.New(s.clear(t.ID)) }) + s.Core().Action("notification.send", send) + s.Core().Action("gui.notification.send", send) return core.Result{OK: true} } @@ -162,3 +168,34 @@ func (s *Service) removeActive(id string) []string { clear(s.active) return ids } + +func notificationOptionsFrom(opts core.Options) (NotificationOptions, error) { + if task := opts.Get("task"); task.OK { + switch v := task.Value.(type) { + case TaskSend: + return v.Options, nil + case NotificationOptions: + return v, nil + } + } + return decodeOptions[NotificationOptions](opts) +} + +func decodeOptions[T any](opts core.Options) (T, error) { + var input T + items := make(map[string]any, opts.Len()) + for _, item := range opts.Items() { + items[item.Key] = item.Value + } + if len(items) == 0 { + return input, nil + } + result := core.JSONUnmarshalString(core.JSONMarshalString(items), &input) + if !result.OK { + if err, ok := result.Value.(error); ok { + return input, err + } + return input, coreerr.E("notification.decodeOptions", "failed to decode notification options", nil) + } + return input, nil +} diff --git a/pkg/webview/service.go b/pkg/webview/service.go index 721816f4..5111262f 100644 --- a/pkg/webview/service.go +++ b/pkg/webview/service.go @@ -301,7 +301,12 @@ func (s *Service) handleQuery(_ *core.Core, q core.Query) core.Result { // registerTaskActions registers all webview task handlers as named Core actions. func (s *Service) registerTaskActions() { c := s.Core() - c.Action("webview.evaluate", func(_ context.Context, opts core.Options) core.Result { + register := func(names []string, handler func(context.Context, core.Options) core.Result) { + for _, name := range names { + c.Action(name, handler) + } + } + register([]string{"webview.evaluate", "gui.webview.eval"}, func(_ context.Context, opts core.Options) core.Result { t, _ := opts.Get("task").Value.(TaskEvaluate) conn, err := s.getConn(t.Window) if err != nil { @@ -310,7 +315,7 @@ func (s *Service) registerTaskActions() { result, err := conn.Evaluate(t.Script) return core.Result{}.New(result, err) }) - c.Action("webview.click", func(_ context.Context, opts core.Options) core.Result { + register([]string{"webview.click", "gui.webview.click"}, func(_ context.Context, opts core.Options) core.Result { t, _ := opts.Get("task").Value.(TaskClick) conn, err := s.getConn(t.Window) if err != nil { @@ -318,7 +323,7 @@ func (s *Service) registerTaskActions() { } return core.Result{Value: nil, OK: true}.New(conn.Click(t.Selector)) }) - c.Action("webview.type", func(_ context.Context, opts core.Options) core.Result { + register([]string{"webview.type", "gui.webview.type"}, func(_ context.Context, opts core.Options) core.Result { t, _ := opts.Get("task").Value.(TaskType) conn, err := s.getConn(t.Window) if err != nil { @@ -326,7 +331,7 @@ func (s *Service) registerTaskActions() { } return core.Result{Value: nil, OK: true}.New(conn.Type(t.Selector, t.Text)) }) - c.Action("webview.navigate", func(_ context.Context, opts core.Options) core.Result { + register([]string{"webview.navigate", "gui.webview.navigate"}, func(_ context.Context, opts core.Options) core.Result { t, _ := opts.Get("task").Value.(TaskNavigate) conn, err := s.getConn(t.Window) if err != nil { @@ -334,7 +339,7 @@ func (s *Service) registerTaskActions() { } return core.Result{Value: nil, OK: true}.New(conn.Navigate(t.URL)) }) - c.Action("webview.screenshot", func(_ context.Context, opts core.Options) core.Result { + register([]string{"webview.screenshot", "gui.webview.screenshot"}, func(_ context.Context, opts core.Options) core.Result { t, _ := opts.Get("task").Value.(TaskScreenshot) conn, err := s.getConn(t.Window) if err != nil { @@ -349,7 +354,7 @@ func (s *Service) registerTaskActions() { MimeType: "image/png", }, OK: true} }) - c.Action("webview.scroll", func(_ context.Context, opts core.Options) core.Result { + register([]string{"webview.scroll", "gui.webview.scroll"}, func(_ context.Context, opts core.Options) core.Result { t, _ := opts.Get("task").Value.(TaskScroll) conn, err := s.getConn(t.Window) if err != nil { @@ -358,7 +363,7 @@ func (s *Service) registerTaskActions() { _, err = conn.Evaluate("window.scrollTo(" + strconv.Itoa(t.X) + "," + strconv.Itoa(t.Y) + ")") return core.Result{Value: nil, OK: true}.New(err) }) - c.Action("webview.hover", func(_ context.Context, opts core.Options) core.Result { + register([]string{"webview.hover", "gui.webview.hover"}, func(_ context.Context, opts core.Options) core.Result { t, _ := opts.Get("task").Value.(TaskHover) conn, err := s.getConn(t.Window) if err != nil { @@ -366,7 +371,7 @@ func (s *Service) registerTaskActions() { } return core.Result{Value: nil, OK: true}.New(conn.Hover(t.Selector)) }) - c.Action("webview.select", func(_ context.Context, opts core.Options) core.Result { + register([]string{"webview.select", "gui.webview.select"}, func(_ context.Context, opts core.Options) core.Result { t, _ := opts.Get("task").Value.(TaskSelect) conn, err := s.getConn(t.Window) if err != nil { @@ -374,7 +379,7 @@ func (s *Service) registerTaskActions() { } return core.Result{Value: nil, OK: true}.New(conn.Select(t.Selector, t.Value)) }) - c.Action("webview.check", func(_ context.Context, opts core.Options) core.Result { + register([]string{"webview.check", "gui.webview.check"}, func(_ context.Context, opts core.Options) core.Result { t, _ := opts.Get("task").Value.(TaskCheck) conn, err := s.getConn(t.Window) if err != nil { @@ -382,7 +387,7 @@ func (s *Service) registerTaskActions() { } return core.Result{Value: nil, OK: true}.New(conn.Check(t.Selector, t.Checked)) }) - c.Action("webview.uploadFile", func(_ context.Context, opts core.Options) core.Result { + register([]string{"webview.uploadFile", "gui.webview.uploadFile"}, func(_ context.Context, opts core.Options) core.Result { t, _ := opts.Get("task").Value.(TaskUploadFile) conn, err := s.getConn(t.Window) if err != nil { @@ -390,7 +395,7 @@ func (s *Service) registerTaskActions() { } return core.Result{Value: nil, OK: true}.New(conn.UploadFile(t.Selector, t.Paths)) }) - c.Action("webview.setViewport", func(_ context.Context, opts core.Options) core.Result { + register([]string{"webview.setViewport", "gui.webview.setViewport"}, func(_ context.Context, opts core.Options) core.Result { t, _ := opts.Get("task").Value.(TaskSetViewport) conn, err := s.getConn(t.Window) if err != nil { @@ -398,7 +403,7 @@ func (s *Service) registerTaskActions() { } return core.Result{Value: nil, OK: true}.New(conn.SetViewport(t.Width, t.Height)) }) - c.Action("webview.clearConsole", func(_ context.Context, opts core.Options) core.Result { + register([]string{"webview.clearConsole", "gui.webview.clearConsole"}, func(_ context.Context, opts core.Options) core.Result { t, _ := opts.Get("task").Value.(TaskClearConsole) conn, err := s.getConn(t.Window) if err != nil { @@ -407,7 +412,7 @@ func (s *Service) registerTaskActions() { conn.ClearConsole() return core.Result{OK: true} }) - c.Action("webview.setURL", func(_ context.Context, opts core.Options) core.Result { + register([]string{"webview.setURL", "gui.webview.setURL"}, func(_ context.Context, opts core.Options) core.Result { t, _ := opts.Get("task").Value.(TaskSetURL) conn, err := s.getConn(t.Window) if err != nil { @@ -415,7 +420,7 @@ func (s *Service) registerTaskActions() { } return core.Result{Value: nil, OK: true}.New(conn.Navigate(t.URL)) }) - c.Action("webview.setZoom", func(_ context.Context, opts core.Options) core.Result { + register([]string{"webview.setZoom", "gui.webview.setZoom"}, func(_ context.Context, opts core.Options) core.Result { t, _ := opts.Get("task").Value.(TaskSetZoom) conn, err := s.getConn(t.Window) if err != nil { @@ -423,7 +428,7 @@ func (s *Service) registerTaskActions() { } return core.Result{Value: nil, OK: true}.New(conn.SetZoom(t.Zoom)) }) - c.Action("webview.print", func(_ context.Context, opts core.Options) core.Result { + register([]string{"webview.print", "gui.webview.print"}, func(_ context.Context, opts core.Options) core.Result { t, _ := opts.Get("task").Value.(TaskPrint) conn, err := s.getConn(t.Window) if err != nil { @@ -441,11 +446,11 @@ func (s *Service) registerTaskActions() { MimeType: "application/pdf", }, OK: true} }) - c.Action("webview.devtoolsOpen", func(_ context.Context, opts core.Options) core.Result { + register([]string{"webview.devtoolsOpen", "gui.webview.devtoolsOpen"}, func(_ context.Context, opts core.Options) core.Result { t, _ := opts.Get("task").Value.(TaskDevToolsOpen) return core.Result{Value: nil, OK: true}.New(s.devToolsOpen(t.Window)) }) - c.Action("webview.devtoolsClose", func(_ context.Context, opts core.Options) core.Result { + register([]string{"webview.devtoolsClose", "gui.webview.devtoolsClose"}, func(_ context.Context, opts core.Options) core.Result { t, _ := opts.Get("task").Value.(TaskDevToolsClose) return core.Result{Value: nil, OK: true}.New(s.devToolsClose(t.Window)) })