diff --git a/app/os_macos.go b/app/os_macos.go index 3302b312c..1a26e0d22 100644 --- a/app/os_macos.go +++ b/app/os_macos.go @@ -8,6 +8,10 @@ package app import ( "errors" "image" + "io" + "mime" + "os" + "path" "runtime" "time" "unicode" @@ -18,6 +22,7 @@ import ( "gioui.org/io/key" "gioui.org/io/pointer" "gioui.org/io/system" + "gioui.org/io/transfer" "gioui.org/unit" _ "gioui.org/internal/cocoainit" @@ -261,6 +266,10 @@ var viewMap = make(map[C.CFTypeRef]*window) // launched is closed when applicationDidFinishLaunching is called. var launched = make(chan struct{}) +// openFiles captures all the openFile events happening +// applicationDidFinishLaunching is called. +var openFiles []string + // nextTopLeft is the offset to use for the next window's call to // cascadeTopLeftFromPoint. var nextTopLeft C.NSPoint @@ -844,6 +853,20 @@ func gio_onFinishLaunching() { close(launched) } +//export gio_openFile +func gio_openFile(cfile C.CFTypeRef) { + file := nsstringToString(cfile) + if len(viewMap) == 0 { + // Must do this because openFile is called before + // applicationDidFinishLaunching therefore no view is available here. + openFiles = append(openFiles, file) + return + } + for _, w := range viewMap { + sendOpenFileEvent(w, file) + } +} + func newWindow(win *callbacks, options []Option) error { <-launched errch := make(chan error) @@ -869,10 +892,29 @@ func newWindow(win *callbacks, options []Option) error { C.makeKeyAndOrderFront(window) layer := C.layerForView(w.view) w.w.Event(ViewEvent{View: uintptr(w.view), Layer: uintptr(layer)}) + // Now send DataTransfer events for any file which has been used to open + // this application from the OS. + for _, file := range openFiles { + sendOpenFileEvent(w, file) + } }) return <-errch } +func sendOpenFileEvent(w *window, file string) { + mimeType := mime.TypeByExtension(path.Ext(file)) + if mimeType == "" { + mimeType = "application/octet-stream" + } + w.w.Event(transfer.DataEvent{ + Type: mimeType, + URI: file, + Open: func() (io.ReadCloser, error) { + return os.Open(file) + }, + }) +} + func newOSWindow() (*window, error) { view := C.gio_createView() if view == 0 { diff --git a/app/os_macos.m b/app/os_macos.m index 71d28afa9..bfde107bd 100644 --- a/app/os_macos.m +++ b/app/os_macos.m @@ -390,6 +390,10 @@ - (void)applicationDidHide:(NSNotification *)aNotification { - (void)applicationWillUnhide:(NSNotification *)notification { gio_onAppShow(); } +- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename { + gio_openFile((__bridge CFTypeRef)filename); + return YES; +} @end void gio_main() { diff --git a/app/window.go b/app/window.go index 342d2f626..5239dc76f 100644 --- a/app/window.go +++ b/app/window.go @@ -932,6 +932,10 @@ func (w *Window) processEvent(d driver, e event.Event) bool { if handled { w.setNextFrame(time.Time{}) w.updateAnimation(d) + } else { + // Currently this can only happen with open file OS directives on + // macOS. + w.out <- e2 } return handled } diff --git a/io/router/pointer.go b/io/router/pointer.go index dd3269b0a..1fc4e1c0e 100644 --- a/io/router/pointer.go +++ b/io/router/pointer.go @@ -872,9 +872,9 @@ func (q *pointerQueue) deliverTransferDataEvent(p *pointerInfo, events *handlerE transferIdx := len(q.transfers) events.Add(p.dataTarget, transfer.DataEvent{ Type: src.offeredMime, - Open: func() io.ReadCloser { + Open: func() (io.ReadCloser, error) { q.transfers[transferIdx] = nil - return src.data + return src.data, nil }, }) q.transfers = append(q.transfers, src.data) diff --git a/io/router/pointer_test.go b/io/router/pointer_test.go index a8b0476c4..a150fb630 100644 --- a/io/router/pointer_test.go +++ b/io/router/pointer_test.go @@ -982,8 +982,12 @@ func TestTransfer(t *testing.T) { if got, want := dataEvent.Type, "file"; got != want { t.Fatalf("got %s; want %s", got, want) } - if got, want := dataEvent.Open(), ofr; got != want { - t.Fatalf("got %v; want %v", got, want) + gotReader, gotErr := dataEvent.Open() + if gotErr != nil { + t.Fatalf("got %s, expected nil", gotErr) + } + if gotReader != ofr { + t.Fatalf("got %v; want %v", gotReader, ofr) } // Drag and drop complete. diff --git a/io/transfer/transfer.go b/io/transfer/transfer.go index 78e2b9005..3781106de 100644 --- a/io/transfer/transfer.go +++ b/io/transfer/transfer.go @@ -14,6 +14,9 @@ // with the source. When a drag gesture completes, a CancelEvent is sent // to the source and all potential targets. // +// DataEvent is also sent when the application is asked by the operating system +// to open one or more files. +// // Note that the RequestEvent is sent to the source upon drop. package transfer @@ -100,10 +103,15 @@ func (CancelEvent) ImplementsEvent() {} // DataEvent is sent to the target receiving the transfer. type DataEvent struct { // Type is the MIME type of Data. + // Type will be "application/octet-stream" for unknown data types. Type string - // Open returns the transfer data. It is only valid to call Open in the frame - // the DataEvent is received. The caller must close the return value after use. - Open func() io.ReadCloser + // URI is the identifier of the resource being transferred. It can be set if + // the DataEvent concerns a file or an online resource. + URI string + // Open returns the transfer data. It is only valid to call Open in the + // frame the DataEvent is received. + // The caller must close the return value after use. + Open func() (io.ReadCloser, error) } func (DataEvent) ImplementsEvent() {} diff --git a/widget/example_test.go b/widget/example_test.go index b789c331d..42771667c 100644 --- a/widget/example_test.go +++ b/widget/example_test.go @@ -113,7 +113,11 @@ func ExampleDraggable_Layout() { for _, ev := range gtx.Events(&drop) { switch e := ev.(type) { case transfer.DataEvent: - data := e.Open() + data, err := e.Open() + if err != nil { + fmt.Println("DataEvent Open error:", err) + break + } fmt.Println(data.(offer).Data) } }