diff --git a/init.go b/init.go index 1f6d542..a47266c 100644 --- a/init.go +++ b/init.go @@ -1,20 +1,11 @@ // 11 february 2014 package ui -import ( - "os" -) - -func init() { - initDone := make(chan error) - go ui(initDone) - err := <-initDone - if err != nil { - // TODO provide copying instructions? will need to be system-specific - MsgBoxError("UI Library Init Failure", - "A failure occured during UI library initialization:\n%v\n" + - "Please report this to the application developer or on http://github.com/andlabs/ui.", - err) - os.Exit(1) - } +// Go sets up the UI environment and runs main in a goroutine. +// If initialization fails, Go returns an error. +// Otherwise, Go does not return to its caller until (unless? TODO) the application loop exits, at which point it returns nil. +// +// This model is undesirable, but Cocoa limitations require it. +func Go(main func()) error { + return ui(main) } diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..7667318 --- /dev/null +++ b/test.sh @@ -0,0 +1,2 @@ +cd test +go build main.go diff --git a/main_test.go b/test/main.go similarity index 96% rename from main_test.go rename to test/main.go index 163024e..1984e58 100644 --- a/main_test.go +++ b/test/main.go @@ -1,9 +1,9 @@ // 11 february 2014 -package ui +package main import ( "fmt" - "testing" + . ".." ) func gridWindow() (*Window, error) { @@ -25,7 +25,7 @@ func gridWindow() (*Window, error) { return w, w.Open(g) } -func TestMain(t *testing.T) { +func myMain() { w := NewWindow("Main Window", 320, 240) w.Closing = Event() b := NewButton("Click Me") @@ -119,3 +119,10 @@ mainloop: gw.Hide() w.Hide() } + +func main() { + err := Go(myMain) + if err != nil { + panic(err) + } +} diff --git a/uitask_darwin.go b/uitask_darwin.go index 3a6d633..2fb6b45 100644 --- a/uitask_darwin.go +++ b/uitask_darwin.go @@ -17,7 +17,6 @@ We will create an Objective-C class goAppDelegate. It contains two methods: // #cgo LDFLAGS: -lobjc -framework Foundation -framework AppKit // #include "objc_darwin.h" -// extern void appDelegate_applicationDidFinishLaunching(id, SEL, id); // extern void appDelegate_uitask(id, SEL, id); import "C" @@ -27,8 +26,6 @@ func msgBoxError(string, string){} var uitask chan func() -var mtret chan interface{} - var ( _NSAutoreleasePool = objc_getClass("NSAutoreleasePool") _NSValue = objc_getClass("NSValue") @@ -38,36 +35,44 @@ var ( _performSelectorOnMainThread = sel_getUid("performSelectorOnMainThread:withObject:waitUntilDone:") _pointerValue = sel_getUid("pointerValue") + _run = sel_getUid("run") ) -func ui(initDone chan error) { +func ui(main func()) error { runtime.LockOSThread() uitask = make(chan func()) - mtret = make(chan interface{}) - go mainThread() - v := <-mtret - if err, ok := v.(error); ok { - initDone <- fmt.Errorf("error initializing Cocoa: %v", err) - return - } - appDelegate := v.(C.id) - for f := range uitask { - // we need to make an NSAutoreleasePool, otherwise we get leak warnings on stderr - pool := objc_new(_NSAutoreleasePool) - fp := C.objc_msgSend_ptr(_NSValue, _valueWithPointer, - unsafe.Pointer(&f)) - C.objc_msgSend_sel_id_bool( - appDelegate, - _performSelectorOnMainThread, - _uitask, - fp, - C.BOOL(C.YES)) // wait so we can properly drain the autorelease pool; on other platforms we wind up waiting anyway (since the main thread can only handle one thing at a time) so - objc_release(pool) + NSApp, appDelegate, err := initCocoa() + if err != nil { + return err } + + // Cocoa must run on the first thread created by the program, so we run our dispatcher on another thread instead + go func() { + for f := range uitask { + // we need to make an NSAutoreleasePool, otherwise we get leak warnings on stderr + pool := objc_new(_NSAutoreleasePool) + fp := C.objc_msgSend_ptr(_NSValue, _valueWithPointer, + unsafe.Pointer(&f)) + C.objc_msgSend_sel_id_bool( + appDelegate, + _performSelectorOnMainThread, + _uitask, + fp, + C.BOOL(C.YES)) // wait so we can properly drain the autorelease pool; on other platforms we wind up waiting anyway (since the main thread can only handle one thing at a time) so + objc_release(pool) + } + }() + + go main() + + C.objc_msgSend_noargs(NSApp, _run) + return nil } +// TODO move to init_darwin.go? + const ( _goAppDelegate = "goAppDelegate" ) @@ -76,41 +81,22 @@ var ( _NSApplication = objc_getClass("NSApplication") _sharedApplication = sel_getUid("sharedApplication") - _applicationDidFinishLaunching = sel_getUid("applicationDidFinishLaunching:") - _run = sel_getUid("run") ) -func mainThread() { - runtime.LockOSThread() +func initCocoa() (NSApp C.id, appDelegate C.id, err error) { + var appdelegateclass C.Class - _NSApp := C.objc_msgSend_noargs(_NSApplication, _sharedApplication) - appdelegateclass, err := makeDelegateClass(_goAppDelegate) - if err != nil { - mtret <- fmt.Errorf("error creating NSApplication delegate: %v", err) - return - } - err = addDelegateMethod(appdelegateclass, _applicationDidFinishLaunching, - C.appDelegate_applicationDidFinishLaunching) - if err != nil { - mtret <- fmt.Errorf("error adding NSApplication delegate applicationDidFinishLaunching: method (to start UI loop): %v", err) - return - } + NSApp = C.objc_msgSend_noargs(_NSApplication, _sharedApplication) err = addDelegateMethod(appdelegateclass, _uitask, C.appDelegate_uitask) if err != nil { - mtret <- fmt.Errorf("error adding NSApplication delegate uitask: method (to do UI tasks): %v", err) + err = fmt.Errorf("error adding NSApplication delegate uitask: method (to do UI tasks): %v", err) return } // TODO using objc_new() causes a segfault; find out why // TODO make alloc followed by init (I thought NSObject provided its own init?) - appDelegate := objc_alloc(objc_getClass(_goAppDelegate)) - objc_setDelegate(_NSApp, appDelegate) - // and that's it, really - C.objc_msgSend_noargs(_NSApp, _run) -} + appDelegate = objc_alloc(objc_getClass(_goAppDelegate)) -//export appDelegate_applicationDidFinishLaunching -func appDelegate_applicationDidFinishLaunching(self C.id, sel C.SEL, arg C.id) { - mtret <- self + return } //export appDelegate_uitask diff --git a/uitask_unix.go b/uitask_unix.go index fad1503..759ac1d 100644 --- a/uitask_unix.go +++ b/uitask_unix.go @@ -10,15 +10,13 @@ import ( var uitask chan func() -func ui(initDone chan error) { +func ui(main func()) error { runtime.LockOSThread() uitask = make(chan func()) if gtk_init() != true { - initDone <- fmt.Errorf("gtk_init failed (reason unknown; TODO)") - return + return fmt.Errorf("gtk_init failed (reason unknown; TODO)") } - initDone <- nil // thanks to tristan in irc.gimp.net/#gtk gdk_threads_add_idle(func() bool { @@ -29,5 +27,9 @@ func ui(initDone chan error) { } return true // don't destroy the callback }) + + go main() + gtk_main() + return nil } diff --git a/uitask_windows.go b/uitask_windows.go index c1f867c..33ab0ad 100644 --- a/uitask_windows.go +++ b/uitask_windows.go @@ -35,17 +35,22 @@ var ( _postThreadMessage = user32.NewProc("PostThreadMessageW") ) -func ui(initDone chan error) { +func ui(main func()) error { runtime.LockOSThread() uitask = make(chan *uimsg) - initDone <- doWindowsInit() + err := doWindowsInit() + if err != nil { + return err + } threadIDReq := make(chan uintptr) msglooperrs := make(chan error) go msgloop(threadIDReq, msglooperrs) threadID := <-threadIDReq + go main() + quit := false for !quit { select { @@ -66,6 +71,8 @@ func ui(initDone chan error) { } } } + + return nil } var (