Skip to content
programmingkidx edited this page Jul 27, 2023 · 10 revisions

Overview
MacDriver takes Objective-c code and wraps it into C.
Then it wraps the C code into Go.
So it looks like this: Objective-c -> C -> Go
This wrapping magic is brought to you by the Cgo package in Go.

This example program demonstrates what wrapping code looks like:

package main

/*
 #cgo CFLAGS: -x objective-c          // Tells compiler we are using objective-c
 #cgo LDFLAGS: -framework Foundation  // Tells linker which framework to dynamically link to
 
 #include <Foundation/Foundation.h>   //  Gives the compiler the prototype for NSLog()
 
 void nslog(char *message)            // Implements the C version of the function
 {
	NSString *objcMessage = [NSString stringWithCString: message]; // Translates a C string to an Objective-c string
	NSLog(objcMessage);   // Calls NSLog()
 }
 */
import "C"  // Import the Cgo pseudo package

/* The Go implementation of the function */
func NSLog(message string) {
	cMessage := C.CString(message) // Translate from Go string to C string
	C.nslog(cMessage)  	       // Send the message to the C function
}

func main() {
	NSLog("Hello from Go") // Calls the Go implementation of NSLog
}

To run this program use this command: go run filename.go
It will print a date and after that "Hello from Go".

How Code In Wrapped
Code wrapping is automated by using a program called Macschema.
It generates a JSON file (referred to as a schema) by using online documentation.
Then this schema file is generated into wrapped code using the gen command.
Macschema can be found here: https://github.com/progrium/macschema
The gen command comes with MacDriver. More info on both below.

How To Use MacSchema

Building MacSchema
Open the Terminal application.
Change directories to a location you want to store MacSchema.
Run this command to download the program: git clone https://github.com/progrium/macschema
Enter the macschema folder by using this command: cd macschema
Then you would build it using this command: make
The binary will be located in the local/bin folder.

Running MacSchema
The format for running MacSchema is macschema <command>.
We want to create a new schema file so we would use the pull command to pull get information on a certain class.
In this example we will using the Appkit framework's class NSSlider.
The format for the pull command will be: macschema pull <framework>/<class> --show
Before we start we need to create two folders that MacSchema will be using: api and doc.
In the terminal you can use this command to create the folders: mkdir api doc
Execute this command to have MacSchema create the schema files: <path to binary>/macschema pull appkit/nsslider --show
The easy way to get the path to macschema is drag the binary file onto the terminal application.
When the command is executed a lot of output will be displayed.
Once it is done the folder api and doc will contain files.
The api folder will contain a folder with the name set to the framework's name used in the pull command.
Here it will be called appkit.
Inside the appkit folder will be a json file that has a name that begins with the class name.
It is called nsslider.objc.json in this example.
The doc folder will contain a folder with the name set to the framework's name used in the pull command too.
So it will contain a folder called appkit.
Inside the appkit folder is will be several json files.
If you look inside these files you should see descriptions of the methods in the NSSlider class.

Updating MacSchema
Inside of the macschema folder that was created by the git command run git pull followed by make.
This will retrieve changes on github and recreate the binary.

MacDriver Setup

Implementing Changes
If you don't already have MacDriver downloaded on your computer or if you want to fetch the latest version you can run this command:
git clone https://github.com/progrium/macdriver.git
Once all the work that MacSchema did is done you need to have MacDriver load the schema file for the class.
Open the file macdriver/gen/cmd/gen.go.
In the file are three groups of loadFile() calls: Foundation, Appkit, and Webkit.
Since the class we are adding is from Appkit we will add a call to loadFile() with this as the argument: "api/appkit/nsslider.objc.json".
The exact location in the group may take some experimenting to get right sometimes.
Other times adding the loadFile() call to the end of the group should be fine (remember the comma at the end).
Next we take the file nsslider.objc.json from our api/appkit folder and copy it into MacDriver's api/appkit folder.
In the terminal set the current directory to the root level of the macdriver folder.
Run this command: go run ./gen/cmd
This will load the new schema.
In the macdriver/cocoa folder there is a file called cocoa_objc.gen.go.
This file contains the generated wrappings for Cocoa classes.
It will now contain the wrapping for the NSSlider class in addition to the many other wrapped classes.
In this same folder there are files that have a name of a class as their file name.
They contain a struct for the class and can contain methods too.
The struct must have an anonymous field that has this name format: gen_<Class name>
There is also where changes to a method can be placed.
Create a new file called NSSlider.go.
Add this code to the file:

package cocoa

type NSSlider struct {
	gen_NSSlider
}

Testing Changes
After implementing the changes to MacDriver it is now time to test them.
This program below is a small starter program. It only displays a window.
You can change it to test your class.

package main

import "github.com/progrium/macdriver/cocoa"
import "github.com/progrium/macdriver/objc"

func main() {

	app := cocoa.NSApp_WithDidLaunch(func(n objc.Object) {
		win := cocoa.NSWindow_New()
		win.SetTitle("Hello world")
		win.MakeKeyAndOrderFront(nil)
	})

	app.SetActivationPolicy(cocoa.NSApplicationActivationPolicyRegular)
	app.ActivateIgnoringOtherApps(true)
	app.Run()
}

This should do as a starting point for testing the NSSlider class:

package main

import "github.com/progrium/macdriver/cocoa"
import "github.com/progrium/macdriver/objc"
import "github.com/progrium/macdriver/core"


func main() {

	app := cocoa.NSApp_WithDidLaunch(func(n objc.Object) {
		win := cocoa.NSWindow_New()
		win.SetTitle("NSSlider")

		/* NSSlider code */
		rect := core.NSMakeRect(10, 50, 70, 20)
		slider := cocoa.NSSlider_alloc()
		slider.InitWithFrame(rect)
		win.ContentView().AddSubview(slider)
		
		win.MakeKeyAndOrderFront(nil)
	})

	app.SetActivationPolicy(cocoa.NSApplicationActivationPolicyRegular)
	app.ActivateIgnoringOtherApps(true)
	app.Run()
}

Create a new folder called program. Create a new file called main.go.
Copy and paste the program above into main.go.
Set up a Go module by running this command: go mod init program
Open the go.mod file and add these two lines to the end of the file:

require github.com/progrium/macdriver v0.4.0
replace github.com/progrium/macdriver => <path to your Git downloaded MacDriver folder>

You need to replace <path to your Git downloaded MacDriver folder> with your folder's actual path.
The last path component should be "macdriver".
Example: replace github.com/progrium/macdriver => Users/yourname/MacDriver-git/macdriver

Note: the "v0.4.0" text should be changed to reflect the most recent tag for MacDriver.
You can find this information in the github page for MacDriver by clicking on "tags".

Then run these commands to run the program: go run main.go

This should be what you see:
image

How To Submit Your Code For Review
If you are new to the world of creating forks of projects and creating pull requests I suggest watching this quick video on how to do this in Github: https://www.youtube.com/watch?v=nT8KGYVurIU

You will probably need to setup token authentication too. This video should be helpful on how to do this: https://www.youtube.com/watch?v=W9zTttHeoHk

Here is some additional information on how to create a pull request: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request

The basics are you create a fork of the MacDriver repo.
You then make the changes you want in your own repo.
Then you create a pull request in Github.
What happens next would be up to the maintainer.

Last Updated: 7-27-2023