Skip to content

Instantly share code, notes, and snippets.

@nathankerr
Last active December 19, 2023 08:47
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save nathankerr/38d8b0d45590741b57f5f79be336f07c to your computer and use it in GitHub Desktop.
Save nathankerr/38d8b0d45590741b57f5f79be336f07c to your computer and use it in GitHub Desktop.
Registering a Go app as a protocol handler under Mac OS X
#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
@xeijin
Copy link

xeijin commented Aug 29, 2021

@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()
}

@gedw99
Copy link

gedw99 commented Mar 21, 2023

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment