-
-
Save nathankerr/38d8b0d45590741b57f5f79be336f07c to your computer and use it in GitHub Desktop.
#import <Foundation/Foundation.h> | |
extern void HandleURL(char*); | |
@interface GoPasser : NSObject | |
+ (void)handleGetURLEvent:(NSAppleEventDescriptor *)event; | |
@end | |
void StartURLHandler(void); |
#include "handler.h" | |
@implementation GoPasser | |
+ (void)handleGetURLEvent:(NSAppleEventDescriptor *)event | |
{ | |
HandleURL([[[event paramDescriptorForKeyword:keyDirectObject] stringValue] UTF8String]); | |
} | |
@end | |
void StartURLHandler(void) { | |
NSAppleEventManager *appleEventManager = [NSAppleEventManager sharedAppleEventManager]; | |
[appleEventManager setEventHandler:[GoPasser class] | |
andSelector:@selector(handleGetURLEvent:) | |
forEventClass:kInternetEventClass andEventID:kAEGetURL]; | |
} |
<?xml version="1.0" encoding="UTF-8"?> | |
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | |
<plist version="1.0"> | |
<dict> | |
<key>CFBundleExecutable</key> | |
<string>myapp</string> | |
<key>CFBundleIdentifier</key> | |
<string>com.pocketgophers.myapp</string> | |
<key>CFBundleURLTypes</key> | |
<array> | |
<dict> | |
<key>CFBundleURLName</key> | |
<string>com.pocketgophers.myapp</string> | |
<key>CFBundleURLSchemes</key> | |
<array> | |
<string>myapp</string> | |
</array> | |
</dict> | |
</array> | |
<key>CFBundleInfoDictionaryVersion</key> | |
<string>6.0</string> | |
<key>CFBundleName</key> | |
<string>MyApp</string> | |
<key>CFBundlePackageType</key> | |
<string>APPL</string> | |
<key>CFBundleShortVersionString</key> | |
<string>1.0.0</string> | |
<key>CFBundleVersion</key> | |
<string>20</string> | |
</dict> | |
</plist> |
package main | |
/* | |
#cgo CFLAGS: -x objective-c | |
#cgo LDFLAGS: -framework Foundation | |
#include "handler.h" | |
*/ | |
import "C" | |
import ( | |
"log" | |
"github.com/andlabs/ui" | |
) | |
// Sources used to figure this out: | |
// Info.plist: https://gist.github.com/chrissnell/db95a3c5ad6ceca4c673e96cca0f7548 | |
// custom url handler example: http://fredandrandall.com/blog/2011/07/30/how-to-launch-your-macios-app-with-a-custom-url/ | |
// obj-c example: https://gist.github.com/leepro/016d7d6b61021dfc67daf61771c92b3c | |
// note: import .h, not .m | |
var labelText chan string | |
func main() { | |
log.SetFlags(log.Lshortfile) | |
labelText = make(chan string, 1) // the event handler blocks!, so buffer the channel at least once to get the first message | |
C.StartURLHandler() | |
err := ui.Main(func() { | |
greeting := ui.NewLabel("greeting\nline 2") | |
window := ui.NewWindow("Hello", 200, 100, true) | |
window.SetChild(greeting) | |
window.OnClosing(func(*ui.Window) bool { | |
ui.Quit() | |
return true | |
}) | |
window.Show() | |
go func() { | |
for text := range labelText { | |
ui.QueueMain(func() { | |
greeting.SetText(text) | |
}) | |
} | |
}() | |
}) | |
if err != nil { | |
log.Fatal(err) | |
} | |
} | |
//export HandleURL | |
func HandleURL(u *C.char) { | |
labelText <- C.GoString(u) | |
} |
myapp.app: *.go *.h *.m Makefile Info.plist | |
mkdir -p myapp.app/Contents/MacOS | |
go build -i -o myapp.app/Contents/MacOS/myapp | |
cp Info.plist myapp.app/Contents/Info.plist | |
.PHONY: open | |
open: myapp.app | |
open myapp.app | |
.PHONY: scheme | |
scheme: myapp.app | |
open myapp://hello/world | |
.PHONY: clean | |
clean: | |
rm -rf myapp.app | |
Hello Nathan,
I was looking to build a URL handler in go and this is pretty useful and works well. Thanks!
However one of the thing that's not very clear to me is the communication model between the app and OS. If I replace your ui.Main() with just a channel that is consuming from the handler and logging it out to a file, the main() method block and the app keeps spinning. My understanding is that main() should not return and ideally should have a goroutine that is receiving from thelabelText
channel which the HandleURL() writes to. I have tried standard stuff like using a channel to wait, timer, sleeping the program but in all cases main blocks and program never gets ready for HandlerURL.. How does using the UI avoid this main blocks?
e.g. this blocks -func main() { C.StartURLHandler() go func() { for text := range labelText { logToFile(text) } }() time.Sleep(20 * time.Second)// I don't do this or something else, main exits and all goroutines would too }
or this or essentially anything that doesn't terminate main
ticks := time.Tick(1 * time.Second) for { select { case text := <-labelText: doSomething(text) case <-ticks: //one second elapsed } }
I have the same problem. Do you found any solution for this?
@ManuelEberhardinger @monmohan (and more likely, anyone who stumbles across this later)
I used mainthread to workaround the issue by separating out the handler code into its own package, and calling its init function in a go routine.
import (
"github.com/golang-design/mainthread"
"example.com/module/ui"
"example.com/module/handler"
)
func main() { mainthread.Init(fn) }
// fn is the actual main function
func fn() {
// macos requires UI to be run on the main thread so schedule that here (in my case, a tray icon)
mainthread.Go(ui.Init)
// moved protocol handler out of main() and into its own package with an init function
go handler.Init()
// execute the rest of your main here
restOfMain()
}
fails on mac for me.
on 11.7.4 ( Big Sur )
make
mkdir -p myapp.app/Contents/MacOS
go build -o myapp.app/Contents/MacOS/myapp .
# myapp
handler.m:7:12: warning: passing 'NS_RETURNS_INNER_POINTER const char *' to parameter of type 'char *' discards qualifiers [-Wincompatible-pointer-types-discards-qualifiers]
./handler.h:3:28: note: passing argument to parameter here
handler.m:15:40: error: use of undeclared identifier 'kInternetEventClass'; did you mean 'kCoreEventClass'?
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/CoreServices.framework/Frameworks/AE.framework/Headers/AppleEvents.h:65:3: note: 'kCoreEventClass' declared here
handler.m:15:71: error: use of undeclared identifier 'kAEGetURL'; did you mean 'kAEISGetURL'?
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/CoreServices.framework/Frameworks/AE.framework/Headers/AERegistry.h:829:3: note: 'kAEISGetURL' declared here
fails on mac for me.
on 11.7.4 ( Big Sur )
make mkdir -p myapp.app/Contents/MacOS go build -o myapp.app/Contents/MacOS/myapp . # myapp handler.m:7:12: warning: passing 'NS_RETURNS_INNER_POINTER const char *' to parameter of type 'char *' discards qualifiers [-Wincompatible-pointer-types-discards-qualifiers] ./handler.h:3:28: note: passing argument to parameter here handler.m:15:40: error: use of undeclared identifier 'kInternetEventClass'; did you mean 'kCoreEventClass'? /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/CoreServices.framework/Frameworks/AE.framework/Headers/AppleEvents.h:65:3: note: 'kCoreEventClass' declared here handler.m:15:71: error: use of undeclared identifier 'kAEGetURL'; did you mean 'kAEISGetURL'? /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/CoreServices.framework/Frameworks/AE.framework/Headers/AERegistry.h:829:3: note: 'kAEISGetURL' declared here
You need to link the Carbon framework: -framework Carbon
and include its header in the obj-c file: #import <Carbon/Carbon.h>
Hello Nathan,
I was looking to build a URL handler in go and this is pretty useful and works well. Thanks!
However one of the thing that's not very clear to me is the communication model between the app and OS. If I replace your ui.Main() with just a channel that is consuming from the handler and logging it out to a file, the main() method block and the app keeps spinning. My understanding is that main() should not return and ideally should have a goroutine that is receiving from the
labelText
channel which the HandleURL() writes to. I have tried standard stuff like using a channel to wait, timer, sleeping the program but in all cases main blocks and program never gets ready for HandlerURL.. How does using the UI avoid this main blocks?e.g. this blocks -
or this or essentially anything that doesn't terminate main