Skip to content

Commit ccb38cf

Browse files
committed
io/transfer,app: [macOS] add support for file opening events
Correctly handle macOS file opening events so that an app containing a binary using gio can be used to open certain files and even be set as default app for certain file extensions. This is done via implementing application:openFile in GioAppDelegate. File events issued this way will be made available to a gio through the io/transfer package. This package now also contains documentation for how to register a macOS app as able to support certain file extensions.
1 parent 27193ae commit ccb38cf

File tree

7 files changed

+74
-8
lines changed

7 files changed

+74
-8
lines changed

app/os_macos.go

+42
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ package app
88
import (
99
"errors"
1010
"image"
11+
"io"
12+
"mime"
13+
"os"
14+
"path"
1115
"runtime"
1216
"time"
1317
"unicode"
@@ -18,6 +22,7 @@ import (
1822
"gioui.org/io/key"
1923
"gioui.org/io/pointer"
2024
"gioui.org/io/system"
25+
"gioui.org/io/transfer"
2126
"gioui.org/unit"
2227

2328
_ "gioui.org/internal/cocoainit"
@@ -261,6 +266,10 @@ var viewMap = make(map[C.CFTypeRef]*window)
261266
// launched is closed when applicationDidFinishLaunching is called.
262267
var launched = make(chan struct{})
263268

269+
// openFiles captures all the openFile events happening
270+
// applicationDidFinishLaunching is called.
271+
var openFiles []string
272+
264273
// nextTopLeft is the offset to use for the next window's call to
265274
// cascadeTopLeftFromPoint.
266275
var nextTopLeft C.NSPoint
@@ -844,6 +853,20 @@ func gio_onFinishLaunching() {
844853
close(launched)
845854
}
846855

856+
//export gio_openFile
857+
func gio_openFile(cfile C.CFTypeRef) {
858+
file := nsstringToString(cfile)
859+
if len(viewMap) == 0 {
860+
// Must do this because openFile is called before
861+
// applicationDidFinishLaunching therefore no view is available here.
862+
openFiles = append(openFiles, file)
863+
return
864+
}
865+
for _, w := range viewMap {
866+
sendOpenFileEvent(w, file)
867+
}
868+
}
869+
847870
func newWindow(win *callbacks, options []Option) error {
848871
<-launched
849872
errch := make(chan error)
@@ -869,10 +892,29 @@ func newWindow(win *callbacks, options []Option) error {
869892
C.makeKeyAndOrderFront(window)
870893
layer := C.layerForView(w.view)
871894
w.w.Event(ViewEvent{View: uintptr(w.view), Layer: uintptr(layer)})
895+
// Now send DataTransfer events for any file which has been used to open
896+
// this application from the OS.
897+
for _, file := range openFiles {
898+
sendOpenFileEvent(w, file)
899+
}
872900
})
873901
return <-errch
874902
}
875903

904+
func sendOpenFileEvent(w *window, file string) {
905+
mimeType := mime.TypeByExtension(path.Ext(file))
906+
if mimeType == "" {
907+
mimeType = "application/octet-stream"
908+
}
909+
w.w.Event(transfer.DataEvent{
910+
Type: mimeType,
911+
URI: file,
912+
Open: func() (io.ReadCloser, error) {
913+
return os.Open(file)
914+
},
915+
})
916+
}
917+
876918
func newOSWindow() (*window, error) {
877919
view := C.gio_createView()
878920
if view == 0 {

app/os_macos.m

+4
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,10 @@ - (void)applicationDidHide:(NSNotification *)aNotification {
390390
- (void)applicationWillUnhide:(NSNotification *)notification {
391391
gio_onAppShow();
392392
}
393+
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename {
394+
gio_openFile((__bridge CFTypeRef)filename);
395+
return YES;
396+
}
393397
@end
394398

395399
void gio_main() {

app/window.go

+4
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,10 @@ func (w *Window) processEvent(d driver, e event.Event) bool {
932932
if handled {
933933
w.setNextFrame(time.Time{})
934934
w.updateAnimation(d)
935+
} else {
936+
// Currently this can only happen with open file OS directives on
937+
// macOS.
938+
w.out <- e2
935939
}
936940
return handled
937941
}

io/router/pointer.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -872,9 +872,9 @@ func (q *pointerQueue) deliverTransferDataEvent(p *pointerInfo, events *handlerE
872872
transferIdx := len(q.transfers)
873873
events.Add(p.dataTarget, transfer.DataEvent{
874874
Type: src.offeredMime,
875-
Open: func() io.ReadCloser {
875+
Open: func() (io.ReadCloser, error) {
876876
q.transfers[transferIdx] = nil
877-
return src.data
877+
return src.data, nil
878878
},
879879
})
880880
q.transfers = append(q.transfers, src.data)

io/router/pointer_test.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -982,8 +982,12 @@ func TestTransfer(t *testing.T) {
982982
if got, want := dataEvent.Type, "file"; got != want {
983983
t.Fatalf("got %s; want %s", got, want)
984984
}
985-
if got, want := dataEvent.Open(), ofr; got != want {
986-
t.Fatalf("got %v; want %v", got, want)
985+
gotReader, gotErr := dataEvent.Open()
986+
if gotErr != nil {
987+
t.Fatalf("got %s, expected nil", gotErr)
988+
}
989+
if gotReader != ofr {
990+
t.Fatalf("got %v; want %v", gotReader, ofr)
987991
}
988992

989993
// Drag and drop complete.

io/transfer/transfer.go

+11-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
// with the source. When a drag gesture completes, a CancelEvent is sent
1515
// to the source and all potential targets.
1616
//
17+
// DataEvent is also sent when the application is asked by the operating system
18+
// to open one or more files.
19+
//
1720
// Note that the RequestEvent is sent to the source upon drop.
1821
package transfer
1922

@@ -100,10 +103,15 @@ func (CancelEvent) ImplementsEvent() {}
100103
// DataEvent is sent to the target receiving the transfer.
101104
type DataEvent struct {
102105
// Type is the MIME type of Data.
106+
// Type will be "application/octet-stream" for unknown data types.
103107
Type string
104-
// Open returns the transfer data. It is only valid to call Open in the frame
105-
// the DataEvent is received. The caller must close the return value after use.
106-
Open func() io.ReadCloser
108+
// URI is the identifier of the resource being transferred. It can be set if
109+
// the DataEvent concerns a file or an online resource.
110+
URI string
111+
// Open returns the transfer data. It is only valid to call Open in the
112+
// frame the DataEvent is received.
113+
// The caller must close the return value after use.
114+
Open func() (io.ReadCloser, error)
107115
}
108116

109117
func (DataEvent) ImplementsEvent() {}

widget/example_test.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,11 @@ func ExampleDraggable_Layout() {
113113
for _, ev := range gtx.Events(&drop) {
114114
switch e := ev.(type) {
115115
case transfer.DataEvent:
116-
data := e.Open()
116+
data, err := e.Open()
117+
if err != nil {
118+
fmt.Println("DataEvent Open error:", err)
119+
break
120+
}
117121
fmt.Println(data.(offer).Data)
118122
}
119123
}

0 commit comments

Comments
 (0)