Major code restructure to allow Cocoa to work correctly. Cocoa requires that the application loop run on the very first OS thread created, not just an any ordinary thread. To support this, your code must now be run by the UI init code. Windows and Unix builds still work fine; Mac OS X fails for reasons I now have to debug.

This commit is contained in:
Pietro Gagliardi 2014-03-01 15:18:29 -05:00
parent c8c257f8c8
commit be5458c0a3
6 changed files with 69 additions and 74 deletions

23
init.go
View File

@ -1,20 +1,11 @@
// 11 february 2014 // 11 february 2014
package ui package ui
import ( // Go sets up the UI environment and runs main in a goroutine.
"os" // 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.
//
func init() { // This model is undesirable, but Cocoa limitations require it.
initDone := make(chan error) func Go(main func()) error {
go ui(initDone) return ui(main)
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)
}
} }

2
test.sh Executable file
View File

@ -0,0 +1,2 @@
cd test
go build main.go

View File

@ -1,9 +1,9 @@
// 11 february 2014 // 11 february 2014
package ui package main
import ( import (
"fmt" "fmt"
"testing" . ".."
) )
func gridWindow() (*Window, error) { func gridWindow() (*Window, error) {
@ -25,7 +25,7 @@ func gridWindow() (*Window, error) {
return w, w.Open(g) return w, w.Open(g)
} }
func TestMain(t *testing.T) { func myMain() {
w := NewWindow("Main Window", 320, 240) w := NewWindow("Main Window", 320, 240)
w.Closing = Event() w.Closing = Event()
b := NewButton("Click Me") b := NewButton("Click Me")
@ -119,3 +119,10 @@ mainloop:
gw.Hide() gw.Hide()
w.Hide() w.Hide()
} }
func main() {
err := Go(myMain)
if err != nil {
panic(err)
}
}

View File

@ -17,7 +17,6 @@ We will create an Objective-C class goAppDelegate. It contains two methods:
// #cgo LDFLAGS: -lobjc -framework Foundation -framework AppKit // #cgo LDFLAGS: -lobjc -framework Foundation -framework AppKit
// #include "objc_darwin.h" // #include "objc_darwin.h"
// extern void appDelegate_applicationDidFinishLaunching(id, SEL, id);
// extern void appDelegate_uitask(id, SEL, id); // extern void appDelegate_uitask(id, SEL, id);
import "C" import "C"
@ -27,8 +26,6 @@ func msgBoxError(string, string){}
var uitask chan func() var uitask chan func()
var mtret chan interface{}
var ( var (
_NSAutoreleasePool = objc_getClass("NSAutoreleasePool") _NSAutoreleasePool = objc_getClass("NSAutoreleasePool")
_NSValue = objc_getClass("NSValue") _NSValue = objc_getClass("NSValue")
@ -38,36 +35,44 @@ var (
_performSelectorOnMainThread = _performSelectorOnMainThread =
sel_getUid("performSelectorOnMainThread:withObject:waitUntilDone:") sel_getUid("performSelectorOnMainThread:withObject:waitUntilDone:")
_pointerValue = sel_getUid("pointerValue") _pointerValue = sel_getUid("pointerValue")
_run = sel_getUid("run")
) )
func ui(initDone chan error) { func ui(main func()) error {
runtime.LockOSThread() runtime.LockOSThread()
uitask = make(chan func()) 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 { NSApp, appDelegate, err := initCocoa()
// we need to make an NSAutoreleasePool, otherwise we get leak warnings on stderr if err != nil {
pool := objc_new(_NSAutoreleasePool) return err
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)
} }
// 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 ( const (
_goAppDelegate = "goAppDelegate" _goAppDelegate = "goAppDelegate"
) )
@ -76,41 +81,22 @@ var (
_NSApplication = objc_getClass("NSApplication") _NSApplication = objc_getClass("NSApplication")
_sharedApplication = sel_getUid("sharedApplication") _sharedApplication = sel_getUid("sharedApplication")
_applicationDidFinishLaunching = sel_getUid("applicationDidFinishLaunching:")
_run = sel_getUid("run")
) )
func mainThread() { func initCocoa() (NSApp C.id, appDelegate C.id, err error) {
runtime.LockOSThread() var appdelegateclass C.Class
_NSApp := C.objc_msgSend_noargs(_NSApplication, _sharedApplication) 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
}
err = addDelegateMethod(appdelegateclass, _uitask, C.appDelegate_uitask) err = addDelegateMethod(appdelegateclass, _uitask, C.appDelegate_uitask)
if err != nil { 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 return
} }
// TODO using objc_new() causes a segfault; find out why // TODO using objc_new() causes a segfault; find out why
// TODO make alloc followed by init (I thought NSObject provided its own init?) // TODO make alloc followed by init (I thought NSObject provided its own init?)
appDelegate := objc_alloc(objc_getClass(_goAppDelegate)) appDelegate = objc_alloc(objc_getClass(_goAppDelegate))
objc_setDelegate(_NSApp, appDelegate)
// and that's it, really
C.objc_msgSend_noargs(_NSApp, _run)
}
//export appDelegate_applicationDidFinishLaunching return
func appDelegate_applicationDidFinishLaunching(self C.id, sel C.SEL, arg C.id) {
mtret <- self
} }
//export appDelegate_uitask //export appDelegate_uitask

View File

@ -10,15 +10,13 @@ import (
var uitask chan func() var uitask chan func()
func ui(initDone chan error) { func ui(main func()) error {
runtime.LockOSThread() runtime.LockOSThread()
uitask = make(chan func()) uitask = make(chan func())
if gtk_init() != true { if gtk_init() != true {
initDone <- fmt.Errorf("gtk_init failed (reason unknown; TODO)") return fmt.Errorf("gtk_init failed (reason unknown; TODO)")
return
} }
initDone <- nil
// thanks to tristan in irc.gimp.net/#gtk // thanks to tristan in irc.gimp.net/#gtk
gdk_threads_add_idle(func() bool { gdk_threads_add_idle(func() bool {
@ -29,5 +27,9 @@ func ui(initDone chan error) {
} }
return true // don't destroy the callback return true // don't destroy the callback
}) })
go main()
gtk_main() gtk_main()
return nil
} }

View File

@ -35,17 +35,22 @@ var (
_postThreadMessage = user32.NewProc("PostThreadMessageW") _postThreadMessage = user32.NewProc("PostThreadMessageW")
) )
func ui(initDone chan error) { func ui(main func()) error {
runtime.LockOSThread() runtime.LockOSThread()
uitask = make(chan *uimsg) uitask = make(chan *uimsg)
initDone <- doWindowsInit() err := doWindowsInit()
if err != nil {
return err
}
threadIDReq := make(chan uintptr) threadIDReq := make(chan uintptr)
msglooperrs := make(chan error) msglooperrs := make(chan error)
go msgloop(threadIDReq, msglooperrs) go msgloop(threadIDReq, msglooperrs)
threadID := <-threadIDReq threadID := <-threadIDReq
go main()
quit := false quit := false
for !quit { for !quit {
select { select {
@ -66,6 +71,8 @@ func ui(initDone chan error) {
} }
} }
} }
return nil
} }
var ( var (