From 9a9a0d6ce9f3370e7f650acfa30dcd9f9027f973 Mon Sep 17 00:00:00 2001 From: Jeff Lindsay Date: Wed, 23 Aug 2023 14:11:02 -0500 Subject: [PATCH] macos helper updates (#198) * have RunApp handle LockOSThread * updated examples to use RunApp API * added doc examples in macos package * also added MenuItem_SeparatorItem() * used proper New methods in Menu helpers --- README.md | 7 -- macos/_examples/form/main.go | 31 ++----- macos/_examples/helloworld/main.go | 7 -- macos/_examples/largetype/main.go | 37 ++++---- macos/_examples/layout/main.go | 26 ++---- macos/_examples/menu/main.go | 69 ++++++--------- macos/_examples/notification/main.go | 28 ++---- macos/_examples/pomodoro/main.go | 128 ++++++++++++--------------- macos/_examples/tabview/main.go | 33 +++---- macos/_examples/webshot/main.go | 32 +++---- macos/_examples/webview/main.go | 31 +++---- macos/_examples/widgets/main.go | 25 ++---- macos/_examples/workspace/main.go | 22 +++-- macos/appkit/appkit_custom.go | 24 +++-- macos/macos.go | 7 +- macos/macos_test.go | 74 ++++++++++++++++ 16 files changed, 284 insertions(+), 297 deletions(-) create mode 100644 macos/macos_test.go diff --git a/README.md b/README.md index cd955101..83fc8b22 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,6 @@ DarwinKit lets you work with [supported Apple frameworks](https://pkg.go.dev/git package main import ( - "runtime" - "github.com/progrium/macdriver/objc" "github.com/progrium/macdriver/macos" "github.com/progrium/macdriver/macos/appkit" @@ -28,11 +26,6 @@ import ( "github.com/progrium/macdriver/macos/webkit" ) -func init() { - // ensure main is run on the startup thread - runtime.LockOSThread() -} - func main() { // runs macOS application event loop with a callback on success macos.RunApp(func(app appkit.Application, delegate *appkit.ApplicationDelegate) { diff --git a/macos/_examples/form/main.go b/macos/_examples/form/main.go index b84426b0..460c397f 100644 --- a/macos/_examples/form/main.go +++ b/macos/_examples/form/main.go @@ -1,24 +1,21 @@ package main import ( - "runtime" - "github.com/progrium/macdriver/helper/layout" "github.com/progrium/macdriver/helper/widgets" + "github.com/progrium/macdriver/macos" "github.com/progrium/macdriver/macos/appkit" "github.com/progrium/macdriver/macos/foundation" + "github.com/progrium/macdriver/objc" ) -// Arrange that main.main runs on main thread. -func init() { - runtime.LockOSThread() +func main() { + macos.RunApp(launched) } -func initAndRun() { - app := appkit.Application_SharedApplication() - app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular) - app.ActivateIgnoringOtherApps(true) +func launched(app appkit.Application, delegate *appkit.ApplicationDelegate) { w := appkit.NewWindowWithSize(600, 400) + objc.Retain(&w) w.SetTitle("Form") fv := widgets.NewFormView() @@ -38,19 +35,9 @@ func initAndRun() { w.MakeKeyAndOrderFront(nil) w.Center() - ad := &appkit.ApplicationDelegate{} - ad.SetApplicationDidFinishLaunching(func(foundation.Notification) { - app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular) - app.ActivateIgnoringOtherApps(true) - }) - ad.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool { + delegate.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool { return true }) - app.SetDelegate(ad) - - app.Run() -} - -func main() { - initAndRun() + app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular) + app.ActivateIgnoringOtherApps(true) } diff --git a/macos/_examples/helloworld/main.go b/macos/_examples/helloworld/main.go index 0800937d..e90adc6e 100644 --- a/macos/_examples/helloworld/main.go +++ b/macos/_examples/helloworld/main.go @@ -1,8 +1,6 @@ package main import ( - "runtime" - "github.com/progrium/macdriver/macos" "github.com/progrium/macdriver/macos/appkit" "github.com/progrium/macdriver/macos/foundation" @@ -10,11 +8,6 @@ import ( "github.com/progrium/macdriver/objc" ) -func init() { - // ensure main is run on the startup thread - runtime.LockOSThread() -} - func main() { // runs macOS application event loop with a callback on success macos.RunApp(func(app appkit.Application, delegate *appkit.ApplicationDelegate) { diff --git a/macos/_examples/largetype/main.go b/macos/_examples/largetype/main.go index 18af3d11..99c2f4bc 100644 --- a/macos/_examples/largetype/main.go +++ b/macos/_examples/largetype/main.go @@ -3,31 +3,36 @@ package main import ( "flag" "fmt" - "runtime" "strings" + "github.com/progrium/macdriver/macos" "github.com/progrium/macdriver/macos/appkit" "github.com/progrium/macdriver/macos/foundation" "github.com/progrium/macdriver/objc" ) -func init() { - runtime.LockOSThread() -} +var ( + fontName *string + text string +) func main() { - fontName := flag.String("font", "Helvetica", "font to use") + fontName = flag.String("font", "Helvetica", "font to use") flag.Parse() - text := strings.Join(flag.Args(), " ") + + text = strings.Join(flag.Args(), " ") if text == "" { text = "Hello world" } - - app := appkit.Application_SharedApplication() - screen := appkit.Screen_MainScreen().Frame().Size text = fmt.Sprintf(" %s ", text) fmt.Println(text) + macos.RunApp(launched) +} + +func launched(app appkit.Application, delegate *appkit.ApplicationDelegate) { + screen := appkit.Screen_MainScreen().Frame().Size + tr, fontSize := func() (rect foundation.Rect, size float64) { t := appkit.NewTextViewWithFrame(rectOf(0, 0, 0, 0)) t.SetString(text) @@ -53,8 +58,8 @@ func main() { t.SetDrawsBackground(false) c := appkit.NewViewWithFrame(rectOf(0, 0, 0, 0)) - // deprecated... - // c.SetBackgroundColor(appkit.Color_ColorWithRedGreenBlueAlpha(0, 0, 0, 0.75)) + // deprecated call, no bindings + objc.Call[objc.Void](c, objc.Sel("setBackgroundColor:"), appkit.Color_ColorWithRedGreenBlueAlpha(0, 0, 0, 0.75)) c.SetWantsLayer(true) c.Layer().SetCornerRadius(32.0) c.AddSubviewPositionedRelativeTo(t, appkit.WindowAbove, nil) @@ -79,14 +84,8 @@ func main() { appkit.Application_SharedApplication().Terminate(nil) }) - ad := &appkit.ApplicationDelegate{} - ad.SetApplicationDidFinishLaunching(func(foundation.Notification) { - app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular) - app.ActivateIgnoringOtherApps(true) - }) - app.SetDelegate(ad) - - app.Run() + app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular) + app.ActivateIgnoringOtherApps(true) } func rectOf(x, y, width, height float64) foundation.Rect { diff --git a/macos/_examples/layout/main.go b/macos/_examples/layout/main.go index 0c7cad26..c99befe3 100644 --- a/macos/_examples/layout/main.go +++ b/macos/_examples/layout/main.go @@ -2,26 +2,26 @@ package main import ( "fmt" - "runtime" "github.com/progrium/macdriver/helper/action" "github.com/progrium/macdriver/helper/layout" "github.com/progrium/macdriver/helper/widgets" + "github.com/progrium/macdriver/macos" "github.com/progrium/macdriver/macos/appkit" "github.com/progrium/macdriver/macos/foundation" "github.com/progrium/macdriver/objc" ) -// Arrange that main.main runs on main thread. -func init() { - runtime.LockOSThread() +func main() { + macos.RunApp(launched) } -func initAndRun() { - app := appkit.Application_SharedApplication() +func launched(app appkit.Application, delegate *appkit.ApplicationDelegate) { app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular) app.ActivateIgnoringOtherApps(true) + w := appkit.NewWindowWithSize(600, 400) + objc.Retain(&w) w.SetTitle("Test Layout") label := appkit.NewLabel("label") @@ -71,19 +71,7 @@ func initAndRun() { w.MakeKeyAndOrderFront(nil) w.Center() - ad := &appkit.ApplicationDelegate{} - ad.SetApplicationDidFinishLaunching(func(foundation.Notification) { - app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular) - app.ActivateIgnoringOtherApps(true) - }) - ad.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool { + delegate.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool { return true }) - app.SetDelegate(ad) - - app.Run() -} - -func main() { - initAndRun() } diff --git a/macos/_examples/menu/main.go b/macos/_examples/menu/main.go index 8ed1b4b5..ded82e3a 100644 --- a/macos/_examples/menu/main.go +++ b/macos/_examples/menu/main.go @@ -8,50 +8,43 @@ import ( "github.com/progrium/macdriver/objc" ) -// menus and tray - -// Arrange that main.main runs on main thread. -func init() { +func main() { runtime.LockOSThread() -} -func initAndRun() { app := appkit.Application_SharedApplication() - w := appkit.NewWindowWithSize(600, 400) - w.SetTitle("Test") - // text field - textView := appkit.TextView_ScrollableTextView() - textView.SetTranslatesAutoresizingMaskIntoConstraints(false) - tv := appkit.TextViewFrom(textView.DocumentView().Ptr()) - tv.SetAllowsUndo(true) - tv.SetRichText(false) - w.ContentView().AddSubview(textView) - w.ContentView().LeadingAnchor().ConstraintEqualToAnchorConstant(textView.LeadingAnchor(), -10).SetActive(true) - w.ContentView().TopAnchor().ConstraintEqualToAnchorConstant(textView.TopAnchor(), -10).SetActive(true) - w.ContentView().TrailingAnchor().ConstraintEqualToAnchorConstant(textView.TrailingAnchor(), 10).SetActive(true) - w.ContentView().BottomAnchor().ConstraintEqualToAnchorConstant(textView.BottomAnchor(), 10).SetActive(true) + delegate := &appkit.ApplicationDelegate{} + delegate.SetApplicationDidFinishLaunching(func(foundation.Notification) { + w := appkit.NewWindowWithSize(600, 400) + objc.Retain(&w) + w.SetTitle("Test") + + textView := appkit.TextView_ScrollableTextView() + textView.SetTranslatesAutoresizingMaskIntoConstraints(false) + tv := appkit.TextViewFrom(textView.DocumentView().Ptr()) + tv.SetAllowsUndo(true) + tv.SetRichText(false) + w.ContentView().AddSubview(textView) + w.ContentView().LeadingAnchor().ConstraintEqualToAnchorConstant(textView.LeadingAnchor(), -10).SetActive(true) + w.ContentView().TopAnchor().ConstraintEqualToAnchorConstant(textView.TopAnchor(), -10).SetActive(true) + w.ContentView().TrailingAnchor().ConstraintEqualToAnchorConstant(textView.TrailingAnchor(), 10).SetActive(true) + w.ContentView().BottomAnchor().ConstraintEqualToAnchorConstant(textView.BottomAnchor(), 10).SetActive(true) + + w.MakeKeyAndOrderFront(nil) + w.Center() - w.MakeKeyAndOrderFront(nil) - w.Center() + setSystemBar(app) - ad := &appkit.ApplicationDelegate{} - ad.SetApplicationDidFinishLaunching(func(foundation.Notification) { app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular) app.ActivateIgnoringOtherApps(true) }) - ad.SetApplicationWillFinishLaunching(func(foundation.Notification) { - // should set menu bar at ApplicationWillFinishLaunching + delegate.SetApplicationWillFinishLaunching(func(foundation.Notification) { setMainMenu(app) }) - ad.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool { + delegate.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool { return true }) - app.SetDelegate(ad) - - // should set system bar after window show - setSystemBar(app) - + app.SetDelegate(delegate) app.Run() } @@ -69,8 +62,7 @@ func setMainMenu(app appkit.Application) { testMenuItem := appkit.NewMenuItemWithSelector("", "", objc.Selector{}) testMenu := appkit.NewMenuWithTitle("Edit") testMenu.AddItem(appkit.NewMenuItemWithSelector("Select All", "a", objc.Sel("selectAll:"))) - // missing symbol? - //testMenu.AddItem(appkit.MenuItem_SeparatorItem()) + testMenu.AddItem(appkit.MenuItem_SeparatorItem()) testMenu.AddItem(appkit.NewMenuItemWithSelector("Copy", "c", objc.Sel("copy:"))) testMenu.AddItem(appkit.NewMenuItemWithSelector("Paste", "v", objc.Sel("paste:"))) testMenu.AddItem(appkit.NewMenuItemWithSelector("Cut", "x", objc.Sel("cut:"))) @@ -81,17 +73,12 @@ func setMainMenu(app appkit.Application) { } func setSystemBar(app appkit.Application) { - bar := appkit.StatusBar_SystemStatusBar() - item := bar.StatusItemWithLength(appkit.VariableStatusItemLength) - button := item.Button() - button.SetTitle("TestTray") + item := appkit.StatusBar_SystemStatusBar().StatusItemWithLength(appkit.VariableStatusItemLength) + objc.Retain(&item) + item.Button().SetTitle("TestTray") menu := appkit.NewMenuWithTitle("main") menu.AddItem(appkit.NewMenuItemWithAction("Hide", "h", func(sender objc.Object) { app.Hide(nil) })) menu.AddItem(appkit.NewMenuItemWithAction("Quit", "q", func(sender objc.Object) { app.Terminate(nil) })) item.SetMenu(menu) } - -func main() { - initAndRun() -} diff --git a/macos/_examples/notification/main.go b/macos/_examples/notification/main.go index fc9b0a63..42fdee78 100644 --- a/macos/_examples/notification/main.go +++ b/macos/_examples/notification/main.go @@ -2,27 +2,25 @@ package main import ( "runtime" + "time" - "github.com/progrium/macdriver/macos/appkit" "github.com/progrium/macdriver/macos/foundation" "github.com/progrium/macdriver/objc" ) -func init() { - runtime.LockOSThread() -} - func main() { - app := appkit.Application_SharedApplication() + runtime.LockOSThread() + // notifications need a unique bundleIdentifier which we can define by + // replacing the bundleIdentifier method on the default main bundle nsbundle := foundation.Bundle_MainBundle().Class() objc.ReplaceMethod(nsbundle, objc.Sel("bundleIdentifier"), func(_ objc.IObject) string { return "com.example.fake2" // change this if you miss the allow notification }) - ad := &appkit.ApplicationDelegate{} - ad.SetApplicationDidFinishLaunching(func(foundation.Notification) { - + objc.WithAutoreleasePool(func() { + // this API is deprecated and we currently don't generate bindings for deprecated APIs, + // so this is what using an API without bindings looks like. notif := objc.Call[objc.Object](objc.GetClass("NSUserNotification"), objc.Sel("new")) notif.Autorelease() objc.Call[objc.Void](notif, objc.Sel("setTitle:"), "Hello, world!") @@ -30,16 +28,8 @@ func main() { center := objc.Call[objc.Object](objc.GetClass("NSUserNotificationCenter"), objc.Sel("defaultUserNotificationCenter")) objc.Call[objc.Void](center, objc.Sel("deliverNotification:"), notif) - - //app.Terminate(nil) - - }) - ad.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool { - return true }) - app.SetDelegate(ad) - app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular) - app.ActivateIgnoringOtherApps(true) - app.Run() + // give notification center a moment + <-time.After(1 * time.Second) } diff --git a/macos/_examples/pomodoro/main.go b/macos/_examples/pomodoro/main.go index 6ed4e54a..41eae327 100644 --- a/macos/_examples/pomodoro/main.go +++ b/macos/_examples/pomodoro/main.go @@ -2,93 +2,83 @@ package main import ( "fmt" - "runtime" "time" "github.com/progrium/macdriver/dispatch" "github.com/progrium/macdriver/helper/action" + "github.com/progrium/macdriver/macos" "github.com/progrium/macdriver/macos/appkit" - "github.com/progrium/macdriver/macos/foundation" "github.com/progrium/macdriver/objc" ) -func init() { - runtime.LockOSThread() -} - func main() { - app := appkit.Application_SharedApplication() + macos.RunApp(launched) +} - ad := &appkit.ApplicationDelegate{} - ad.SetApplicationDidFinishLaunching(func(foundation.Notification) { +func launched(app appkit.Application, delegate *appkit.ApplicationDelegate) { + delegate.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool { + return false + }) - item := appkit.StatusBar_SystemStatusBar().StatusItemWithLength(-1) - item.Retain() - item.Button().SetTitle("▶️ Ready") + item := appkit.StatusBar_SystemStatusBar().StatusItemWithLength(-1) + objc.Retain(&item) + item.Button().SetTitle("▶️ Ready") - nextClicked := make(chan bool) - go func() { - state := -1 - timer := 1500 - countdown := false - for { - select { - case <-time.After(1 * time.Second): - if timer > 0 && countdown { - timer = timer - 1 - } - if timer <= 0 && state%2 == 1 { - state = (state + 1) % 4 - } - case <-nextClicked: + nextClicked := make(chan bool) + go func() { + state := -1 + timer := 1500 + countdown := false + for { + select { + case <-time.After(1 * time.Second): + if timer > 0 && countdown { + timer = timer - 1 + } + if timer <= 0 && state%2 == 1 { state = (state + 1) % 4 - timer = map[int]int{ - 0: 1500, - 1: 1500, - 2: 0, - 3: 300, - }[state] - if state%2 == 1 { - countdown = true - } else { - countdown = false - } } - labels := map[int]string{ - 0: "▶️ Ready %02d:%02d", - 1: "✴️ Working %02d:%02d", - 2: "✅ Finished %02d:%02d", - 3: "⏸️ Break %02d:%02d", + case <-nextClicked: + state = (state + 1) % 4 + timer = map[int]int{ + 0: 1500, + 1: 1500, + 2: 0, + 3: 300, + }[state] + if state%2 == 1 { + countdown = true + } else { + countdown = false } - // updates to the ui should happen on the main thread to avoid segfaults - dispatch.MainQueue().DispatchAsync(func() { - item.Button().SetTitle(fmt.Sprintf(labels[state], timer/60, timer%60)) - }) } - }() - nextClicked <- true - - itemNext := appkit.NewMenuItem() - itemNext.SetTitle("Next") - action.Set(itemNext, func(sender objc.Object) { - nextClicked <- true - }) - - itemQuit := appkit.NewMenuItem() - itemQuit.SetTitle("Quit") - itemQuit.SetAction(objc.Sel("terminate:")) - - menu := appkit.NewMenu() - menu.AddItem(itemNext) - menu.AddItem(itemQuit) - item.SetMenu(menu) + labels := map[int]string{ + 0: "▶️ Ready %02d:%02d", + 1: "✴️ Working %02d:%02d", + 2: "✅ Finished %02d:%02d", + 3: "⏸️ Break %02d:%02d", + } + // updates to the ui should happen on the main thread to avoid segfaults + dispatch.MainQueue().DispatchAsync(func() { + item.Button().SetTitle(fmt.Sprintf(labels[state], timer/60, timer%60)) + }) + } + }() + nextClicked <- true + itemNext := appkit.NewMenuItem() + itemNext.SetTitle("Next") + action.Set(itemNext, func(sender objc.Object) { + nextClicked <- true }) - ad.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool { - return false - }) - app.SetDelegate(ad) - app.Run() + itemQuit := appkit.NewMenuItem() + itemQuit.SetTitle("Quit") + itemQuit.SetAction(objc.Sel("terminate:")) + + menu := appkit.NewMenu() + menu.AddItem(itemNext) + menu.AddItem(itemQuit) + item.SetMenu(menu) } diff --git a/macos/_examples/tabview/main.go b/macos/_examples/tabview/main.go index 66ad79a9..9ca970bd 100644 --- a/macos/_examples/tabview/main.go +++ b/macos/_examples/tabview/main.go @@ -1,21 +1,21 @@ package main import ( - "runtime" "strconv" + "github.com/progrium/macdriver/macos" "github.com/progrium/macdriver/macos/appkit" "github.com/progrium/macdriver/macos/foundation" + "github.com/progrium/macdriver/objc" ) -// Arrange that main.main runs on main thread. -func init() { - runtime.LockOSThread() +func main() { + macos.RunApp(launched) } -func initAndRun() { - app := appkit.Application_SharedApplication() +func launched(app appkit.Application, delegate *appkit.ApplicationDelegate) { w := appkit.NewWindowWithSize(720, 440) + objc.Retain(&w) w.SetTitle("Decoder") tabView := appkit.NewTabView() @@ -26,23 +26,18 @@ func initAndRun() { tabView.AddTabViewItem(createNewView(2)) w.SetContentView(tabView) + w.Center() + w.MakeKeyAndOrderFront(nil) - ad := &appkit.ApplicationDelegate{} - ad.SetApplicationDidFinishLaunching(func(foundation.Notification) { - app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular) - app.ActivateIgnoringOtherApps(true) - }) - ad.SetApplicationWillFinishLaunching(func(foundation.Notification) { + delegate.SetApplicationWillFinishLaunching(func(foundation.Notification) { w.SetFrameAutosaveName("tab-test") }) - ad.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool { + delegate.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool { return true }) - app.SetDelegate(ad) + app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular) + app.ActivateIgnoringOtherApps(true) - w.Center() - w.MakeKeyAndOrderFront(nil) - app.Run() } func createNewView(idx int) appkit.ITabViewItem { @@ -55,7 +50,3 @@ func createNewView(idx int) appkit.ITabViewItem { ti.SetView(sv) return ti } - -func main() { - initAndRun() -} diff --git a/macos/_examples/webshot/main.go b/macos/_examples/webshot/main.go index 629797a0..72aa8ee9 100644 --- a/macos/_examples/webshot/main.go +++ b/macos/_examples/webshot/main.go @@ -3,33 +3,32 @@ package main import ( "fmt" "os" - "runtime" "github.com/progrium/macdriver/dispatch" "github.com/progrium/macdriver/helper/action" + "github.com/progrium/macdriver/macos" "github.com/progrium/macdriver/macos/appkit" "github.com/progrium/macdriver/macos/foundation" "github.com/progrium/macdriver/macos/webkit" "github.com/progrium/macdriver/objc" ) -// Arrange that main.main runs on main thread. -func init() { - runtime.LockOSThread() +func main() { + macos.RunApp(launched) } -func initAndRun() { +func launched(app appkit.Application, delegate *appkit.ApplicationDelegate) { var html = "

Hello World!

" var url = "https://www.test.com" - app := appkit.Application_SharedApplication() w := appkit.NewWindowWithSize(600, 400) + objc.Retain(&w) w.SetTitle("Test widgets") sv := appkit.NewVerticalStackView() w.SetContentView(sv) - webView := webkit.WebViewClass.New() + webView := webkit.NewWebView() webView.SetTranslatesAutoresizingMaskIntoConstraints(false) webView.LoadHTMLStringBaseURL(html, foundation.URLClass.URLWithString(url)) sv.AddViewInGravity(webView, appkit.StackViewGravityTop) @@ -37,9 +36,10 @@ func initAndRun() { snapshotButton := appkit.NewButtonWithTitle("capture") snapshotWin := appkit.NewWindowWithSize(0, 0) + objc.Retain(&snapshotWin) snapshotWin.SetTitle("Test widgets") - snapshotWebView := webkit.WebViewClass.New() + snapshotWebView := webkit.NewWebView() snapshotWebView.SetTranslatesAutoresizingMaskIntoConstraints(false) snapshotWin.SetContentView(snapshotWebView) @@ -91,19 +91,9 @@ func initAndRun() { w.MakeKeyAndOrderFront(nil) w.Center() - ad := &appkit.ApplicationDelegate{} - ad.SetApplicationDidFinishLaunching(func(foundation.Notification) { - app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular) - app.ActivateIgnoringOtherApps(true) - }) - ad.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool { + delegate.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool { return true }) - app.SetDelegate(ad) - - app.Run() -} - -func main() { - initAndRun() + app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular) + app.ActivateIgnoringOtherApps(true) } diff --git a/macos/_examples/webview/main.go b/macos/_examples/webview/main.go index c6aba98e..a62597e2 100644 --- a/macos/_examples/webview/main.go +++ b/macos/_examples/webview/main.go @@ -4,9 +4,8 @@ import ( "embed" "fmt" "io/fs" - "runtime" - "time" + "github.com/progrium/macdriver/macos" "github.com/progrium/macdriver/macos/appkit" "github.com/progrium/macdriver/macos/foundation" "github.com/progrium/macdriver/macos/webkit" @@ -16,13 +15,14 @@ import ( //go:embed assets var assetsFS embed.FS -func init() { - runtime.LockOSThread() +func main() { + macos.RunApp(launched) } -func main() { - app := appkit.ApplicationClass.SharedApplication() +func launched(app appkit.Application, delegate *appkit.ApplicationDelegate) { w := appkit.NewWindowWithSize(600, 400) + objc.Retain(&w) + w.SetTitle("Test FS WebView") _fs, _ := fs.Sub(assetsFS, "assets") @@ -38,24 +38,15 @@ func main() { return foundation.NewStringWithString("hello: " + param).Object, nil }) w.SetContentView(view) - w.MakeKeyAndOrderFront(nil) w.Center() - ad := &appkit.ApplicationDelegate{} - ad.SetApplicationDidFinishLaunching(func(foundation.Notification) { - app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular) - app.ActivateIgnoringOtherApps(true) - webkit.LoadURL(view, "gofs:/index.html") - }) - ad.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool { + webkit.LoadURL(view, "gofs:/index.html") + + delegate.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool { return true }) - app.SetDelegate(ad) + app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular) + app.ActivateIgnoringOtherApps(true) - go func() { - time.Sleep(time.Second * 1) - runtime.GC() - }() - app.Run() } diff --git a/macos/_examples/widgets/main.go b/macos/_examples/widgets/main.go index 0d3c1a29..f767994b 100644 --- a/macos/_examples/widgets/main.go +++ b/macos/_examples/widgets/main.go @@ -2,25 +2,23 @@ package main import ( "fmt" - "runtime" "time" "github.com/progrium/macdriver/dispatch" "github.com/progrium/macdriver/helper/action" - + "github.com/progrium/macdriver/macos" "github.com/progrium/macdriver/macos/appkit" "github.com/progrium/macdriver/macos/foundation" "github.com/progrium/macdriver/objc" ) -// Arrange that main.main runs on main thread. -func init() { - runtime.LockOSThread() +func main() { + macos.RunApp(launched) } -func main() { - app := appkit.Application_SharedApplication() +func launched(app appkit.Application, delegate *appkit.ApplicationDelegate) { w := appkit.NewWindowWithSize(600, 400) + objc.Retain(&w) w.SetTitle("Test widgets") @@ -174,18 +172,11 @@ func main() { w.Center() w.MakeKeyAndOrderFront(nil) - ad := &appkit.ApplicationDelegate{} - ad.SetApplicationDidFinishLaunching(func(foundation.Notification) { - app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular) - app.ActivateIgnoringOtherApps(true) - fmt.Println("launched") - }) - ad.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool { + delegate.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool { return true }) - app.SetDelegate(ad) - - app.Run() + app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular) + app.ActivateIgnoringOtherApps(true) } func rectOf(x, y, width, height float64) foundation.Rect { diff --git a/macos/_examples/workspace/main.go b/macos/_examples/workspace/main.go index fdcb0d2b..689ea6d2 100644 --- a/macos/_examples/workspace/main.go +++ b/macos/_examples/workspace/main.go @@ -2,18 +2,24 @@ package main import ( "fmt" + "runtime" "github.com/progrium/macdriver/macos/appkit" + "github.com/progrium/macdriver/objc" ) func main() { - ws := appkit.Workspace_SharedWorkspace() - fmt.Println("Running:") - for _, app := range ws.RunningApplications() { - fmt.Println(app.LocalizedName()) - } + runtime.LockOSThread() + objc.WithAutoreleasePool(func() { + ws := appkit.Workspace_SharedWorkspace() - fmt.Println("\nFrontmost:") - frontmost := ws.FrontmostApplication() - fmt.Println(frontmost.LocalizedName()) + fmt.Println("Running:") + for _, app := range ws.RunningApplications() { + fmt.Println(app.LocalizedName()) + } + + fmt.Println("\nFrontmost:") + frontmost := ws.FrontmostApplication() + fmt.Println(frontmost.LocalizedName()) + }) } diff --git a/macos/appkit/appkit_custom.go b/macos/appkit/appkit_custom.go index 859fecff..bdc6f37f 100644 --- a/macos/appkit/appkit_custom.go +++ b/macos/appkit/appkit_custom.go @@ -44,6 +44,18 @@ func Font_FontWithNameSize(fontName string, fontSize float64) Font { return FontClass.FontWithNameSize(fontName, fontSize) } +func (ic _MenuItemClass) SeparatorItem() MenuItem { + rv := objc.Call[MenuItem](ic, objc.Sel("separatorItem")) + return rv +} + +// Returns a menu item that is used to separate logical groups of menu commands. +// +// [Full Topic]: https://developer.apple.com/documentation/appkit/nsmenuitem/1514838-separatoritem +func MenuItem_SeparatorItem() MenuItem { + return MenuItemClass.SeparatorItem() +} + func (w_ Window) InitWithContentRectStyleMaskBackingDefer(contentRect foundation.Rect, style WindowStyleMask, backingStoreType BackingStoreType, flag bool) Window { rv := objc.Call[Window](w_, objc.Sel("initWithContentRect:styleMask:backing:defer:"), contentRect, style, backingStoreType, flag) return rv @@ -74,19 +86,19 @@ func DisableAutoresizingTranslate[T IView](v T) T { // NewMenuItem create a new menu item, with selector func NewMenuItemWithSelector(title string, charCode string, selector objc.Selector) MenuItem { - return MenuItemClass.Alloc().InitWithTitleActionKeyEquivalent(title, selector, charCode) + return NewMenuItemWithTitleActionKeyEquivalent(title, selector, charCode) } // NewMenuItemWithAction create a new menu item with action func NewMenuItemWithAction(title string, charCode string, handler action.Handler) MenuItem { - item := MenuItemClass.Alloc().InitWithTitleActionKeyEquivalent(title, objc.Selector{}, charCode) + item := NewMenuItemWithTitleActionKeyEquivalent(title, objc.Selector{}, charCode) action.Set(item, handler) return item } // NewSubMenuItem create a menu item that hold a sub menu func NewSubMenuItem(menu IMenu) MenuItem { - item := MenuItemClass.Alloc().InitWithTitleActionKeyEquivalent("", objc.Selector{}, "") + item := NewMenuItemWithTitleActionKeyEquivalent("", objc.Selector{}, "") item.SetSubmenu(menu) return item } @@ -113,7 +125,7 @@ func NewRadioButton(title string) Button { // NewPushButton return a button that switches between on and off states with each click. func NewPushButton(title string) Button { - btn := ButtonClass.New() + btn := NewButton() btn.SetButtonType(ButtonTypePushOnPushOff) btn.SetTitle(title) return btn @@ -121,14 +133,14 @@ func NewPushButton(title string) Button { // NewVerticalStackView return a new vertical StackView func NewVerticalStackView() StackView { - sv := StackViewClass.New() + sv := NewStackView() sv.SetOrientation(UserInterfaceLayoutOrientationVertical) return sv } // NewHorizontalStackView return a new horizontal StackView func NewHorizontalStackView() StackView { - sv := StackViewClass.New() + sv := NewStackView() sv.SetOrientation(UserInterfaceLayoutOrientationHorizontal) return sv } diff --git a/macos/macos.go b/macos/macos.go index 77f60ce0..781d1e5e 100644 --- a/macos/macos.go +++ b/macos/macos.go @@ -4,13 +4,18 @@ package macos import ( + "runtime" + "github.com/progrium/macdriver/macos/appkit" "github.com/progrium/macdriver/macos/foundation" ) -// RunApp builds a delegate, sets it on the shared application, and runs. +// RunApp builds a delegate, sets it on the shared application, and runs the event loop. // It uses didLaunch to set ApplicationDidFinishLaunching on the delegate. +// It also wraps run with runtime.LockOSThread() and should be called from main. func RunApp(didLaunch func(appkit.Application, *appkit.ApplicationDelegate)) { + runtime.LockOSThread() + defer runtime.UnlockOSThread() app := appkit.Application_SharedApplication() delegate := &appkit.ApplicationDelegate{} delegate.SetApplicationDidFinishLaunching(func(notification foundation.Notification) { diff --git a/macos/macos_test.go b/macos/macos_test.go new file mode 100644 index 00000000..418da64d --- /dev/null +++ b/macos/macos_test.go @@ -0,0 +1,74 @@ +package macos_test + +import ( + "fmt" + "runtime" + + "github.com/progrium/macdriver/macos" + "github.com/progrium/macdriver/macos/appkit" + "github.com/progrium/macdriver/macos/foundation" + "github.com/progrium/macdriver/macos/webkit" + "github.com/progrium/macdriver/objc" +) + +// You normally want to start an application using RunApp. +// This is a simple webview window app. Note that we need to retain the window object. +func Example_mainWithRunApp() { + // RunApp should only be called from main on the startup thread + macos.RunApp(func(app appkit.Application, delegate *appkit.ApplicationDelegate) { + app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular) + app.ActivateIgnoringOtherApps(true) + + url := foundation.URL_URLWithString("http://progrium.com") + req := foundation.NewURLRequestWithURL(url) + frame := foundation.Rect{Size: foundation.Size{1440, 900}} + + config := webkit.NewWebViewConfiguration() + wv := webkit.NewWebViewWithFrameConfiguration(frame, config) + wv.LoadRequest(req) + + w := appkit.NewWindowWithContentRectStyleMaskBackingDefer(frame, + appkit.ClosableWindowMask|appkit.TitledWindowMask, + appkit.BackingStoreBuffered, false) + objc.Retain(&w) + w.SetContentView(wv) + w.MakeKeyAndOrderFront(w) + w.Center() + + delegate.SetApplicationShouldTerminateAfterLastWindowClosed(func(appkit.Application) bool { + return true + }) + }) +} + +// You can use appkit APIs directly to start an application, but you need to +// lock the main thread and build your own application delegate if desired. +func Example_mainWithoutRunApp() { + runtime.LockOSThread() + app := appkit.Application_SharedApplication() + delegate := &appkit.ApplicationDelegate{} + delegate.SetApplicationDidFinishLaunching(func(notification foundation.Notification) { + app.SetActivationPolicy(appkit.ApplicationActivationPolicyRegular) + app.ActivateIgnoringOtherApps(true) + + // ... app started code ... + + }) + app.SetDelegate(delegate) + app.Run() +} + +// You can use some Apple frameworks without starting an application, +// but you need to lock the main thread and wrap your code with an autorelease pool. +func Example_mainWithoutApplication() { + runtime.LockOSThread() + objc.WithAutoreleasePool(func() { + + ws := appkit.Workspace_SharedWorkspace() + + for _, app := range ws.RunningApplications() { + fmt.Println(app.LocalizedName()) + } + + }) +}