Can I program Apple Metal graphics without Xcode? - swift

Is it possible to code a Metal graphics program with only an editor an the terminal? If it is, What would be an example of a minimal application?
I'd like to try some generative coding but without Xcode. I was able to make small C or Python programs with OpenGL but its deprecation by Apple is making me consider Metal.
Can I use Swift and some header inclusions or something like that? or do I need a whole lot more?

You can compile and run an app that uses Metal from the command line with no Xcode project at all, but you still need the infrastructure (SDK and toolchain) provided by Xcode, so you'll need to have it installed.
It's quite straightforward to write a simple app in Swift that creates the requisite Metal objects, encodes some work, and writes the result to a file. For the purposes of this question, I'll provide the source for a simple Cocoa app that goes a bit further by creating a window that hosts a MetalKit view and draws to it. With this scaffolding, you could write a very sophisticated app without ever launching Xcode.
import Foundation
import Cocoa
import Metal
import MetalKit
class AppDelegate : NSObject, NSApplicationDelegate {
let window = NSWindow()
let windowDelegate = WindowDelegate()
var rootViewController: NSViewController?
func applicationDidFinishLaunching(_ notification: Notification) {
window.setContentSize(NSSize(width: 800, height: 600))
window.styleMask = [ .titled, .closable, .miniaturizable, .resizable ]
window.title = "Window"
window.level = .normal
window.delegate = windowDelegate
window.center()
let view = window.contentView!
rootViewController = ViewController(nibName: nil, bundle: nil)
rootViewController!.view.frame = view.bounds
view.addSubview(rootViewController!.view)
window.makeKeyAndOrderFront(window)
NSApp.activate(ignoringOtherApps: true)
}
}
class WindowDelegate : NSObject, NSWindowDelegate {
func windowWillClose(_ notification: Notification) {
NSApp.terminate(self)
}
}
class ViewController : NSViewController, MTKViewDelegate {
var device: MTLDevice!
var commandQueue: MTLCommandQueue!
override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func loadView() {
device = MTLCreateSystemDefaultDevice()!
commandQueue = device.makeCommandQueue()!
let metalView = MTKView(frame: .zero, device: device)
metalView.clearColor = MTLClearColorMake(0, 0, 1, 1)
metalView.delegate = self
self.view = metalView
}
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}
func draw(in view: MTKView) {
guard let commandBuffer = commandQueue.makeCommandBuffer(),
let passDescriptor = view.currentRenderPassDescriptor else { return }
if let commandEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: passDescriptor) {
// set state, issue draw calls, etc.
commandEncoder.endEncoding()
}
commandBuffer.present(view.currentDrawable!)
commandBuffer.commit()
}
}
func makeMainMenu() -> NSMenu {
let mainMenu = NSMenu()
let mainAppMenuItem = NSMenuItem(title: "Application", action: nil, keyEquivalent: "")
let mainFileMenuItem = NSMenuItem(title: "File", action: nil, keyEquivalent: "")
mainMenu.addItem(mainAppMenuItem)
mainMenu.addItem(mainFileMenuItem)
let appMenu = NSMenu()
mainAppMenuItem.submenu = appMenu
let appServicesMenu = NSMenu()
NSApp.servicesMenu = appServicesMenu
appMenu.addItem(withTitle: "Hide", action: #selector(NSApplication.hide(_:)), keyEquivalent: "h")
appMenu.addItem({ () -> NSMenuItem in
let m = NSMenuItem(title: "Hide Others", action: #selector(NSApplication.hideOtherApplications(_:)), keyEquivalent: "h")
m.keyEquivalentModifierMask = [.command, .option]
return m
}())
appMenu.addItem(withTitle: "Show All", action: #selector(NSApplication.unhideAllApplications(_:)), keyEquivalent: "")
appMenu.addItem(NSMenuItem.separator())
appMenu.addItem(withTitle: "Services", action: nil, keyEquivalent: "").submenu = appServicesMenu
appMenu.addItem(NSMenuItem.separator())
appMenu.addItem(withTitle: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q")
let fileMenu = NSMenu(title: "Window")
mainFileMenuItem.submenu = fileMenu
fileMenu.addItem(withTitle: "Close", action: #selector(NSWindowController.close), keyEquivalent: "w")
return mainMenu
}
let app = NSApplication.shared
NSApp.setActivationPolicy(.regular)
NSApp.mainMenu = makeMainMenu()
let appDelegate = AppDelegate()
NSApp.delegate = appDelegate
NSApp.run()
Although this looks like a lot of code, the bulk of it is Cocoa boilerplate for creating and interacting with a window and its main menu. The Metal code is only a few lines tucked away in the middle (see func draw).
To build and run this app, save the code to a Swift file (I called mine MinimalMetal.swift) and use xcrun to locate the tools and SDKs necessary to build:
xcrun -sdk macosx swiftc MinimalMetal.swift -o MinimalMetal
This creates an executable named "MinimalMetal" in the same directory, which you can run with
./MinimalMetal
This is a full-fledged app, with a window, menu, runloop, and 60 FPS drawing. From here, you can use all the features of Metal you might want to.

you can with python, try this:
https://github.com/wtnb75/runmetal you can compile a metal kernel in a string and link it with numpy arrays

Related

How can I create mainMenu and menu items in AppDelegate for Cocoa macOS?

I am trying build my mainMenu from AppDelegate, I already did disconnect my Storyboard file and I am using a main.swift file for loading my app as #mainI tried this codes for building my menu, but it does not work, the function does not create my items, also I got no error or issue, I think the created menu cannot plugged to my app.
my main.swift file:
import Cocoa
// 1
let app = NSApplication.shared
let delegate = AppDelegate()
app.delegate = delegate
// 2
_ = NSApplicationMain(CommandLine.argc, CommandLine.unsafeArgv)
import Cocoa
import SwiftUI
class AppDelegate: NSObject, NSApplicationDelegate {
private var window: NSWindow!
func applicationDidFinishLaunching(_ aNotification: Notification) {
window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 480, height: 300),
styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
backing: .buffered, defer: false)
window.center()
window.setFrameAutosaveName("Main Window")
window.title = "No Storyboard Window"
window.contentView = NSHostingView(rootView: ContentView())
window.makeKeyAndOrderFront(nil)
customMainMenu()
}
func customMainMenu() {
if let appMainMenu = NSApp.mainMenu {
let mainMenu = NSMenuItem()
mainMenu.submenu = NSMenu(title: "MainMenu")
let mainMenuItem1 = NSMenuItem(title: "Close", action: #selector(NSWindow.performClose(_:)), keyEquivalent: "w")
mainMenuItem1.keyEquivalentModifierMask = .command
let mainMenuItem2 = NSMenuItem(title: "Quit", action: #selector(NSApplication.shared.terminate(_:)), keyEquivalent: "q")
mainMenuItem2.keyEquivalentModifierMask = .command
mainMenu.submenu?.items = [mainMenuItem1, mainMenuItem2]
appMainMenu.items = [mainMenu]
}
}
}
The following source code will create a basic menu for swift. To run in Xcode create a Swift project and delete the contents of the AppDelegate file and replace it with the following. Then change the name of the AppDelegate.swift file to main.swift and run. The window is already closable so I didn't add a redundant menu item for this.
import Cocoa
// **** App Delegate **** //
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
func buildMenu() {
let mainMenu = NSMenu()
NSApp.mainMenu = mainMenu
// **** App menu **** //
let appMenuItem = NSMenuItem()
mainMenu.addItem(appMenuItem)
let appMenu = NSMenu()
appMenuItem.submenu = appMenu
appMenu.addItem(withTitle:"Quit", action:#selector(NSApplication.terminate), keyEquivalent: "q")
}
func buildWnd() {
let _wndW = CGFloat (440)
let _wndH = CGFloat (300)
// **** Window **** //
window = NSWindow(contentRect:NSMakeRect(0,0,_wndW,_wndH),styleMask:[.titled, .closable, .miniaturizable, .resizable], backing:.buffered, defer:false)
window.center()
window.title = "Swift Test Window"
window.makeKeyAndOrderFront(window)
}
func applicationDidFinishLaunching(_ notification: Notification) {
buildMenu()
buildWnd()
}
}
let appDelegate = AppDelegate()
// **** Main **** //
let application = NSApplication.shared
application.delegate = appDelegate
application.run()

How to make app start at login or add to login items after install like dropbox? Xcode 11, Swift 5, MacOS 10.13+

I am trying to have my app start automatically when a user logs out and back in or after a reboot. The objective is to have the app there indefinitely unless a user closes out, but, to have it start back up when they reboot.
There are several tutorials I have followed and threads I have read but they all seem outdated. I am using this app on 3 different OS' including 10.13, 10.14 and 10.15. I seem to have most of the issues on 10.15 machines. I can't figure out why it is hit or miss whether the app starts on login and why sometimes it does not.
https://martiancraft.com/blog/2015/01/login-items/
https://theswiftdev.com/how-to-launch-a-macos-app-at-login/
I have code signed the application, sandbox is enabled, but maybe since the tutorials and information I have found is outdated I am missing something outside the code. This app is for internal company use only, and thus, I will not be submitting to the Apple. I intend to deploy to machines using our management software, and have built a .pkg to deploy the application + launcher into the applications folder and to run automatically after install.
Any help, cleanup, explanations or suggestions are welcome and appreciated.
Main App:
extension Notification.Name{
static let killLauncher = Notification.Name("killLauncher")
}
extension AppDelegate: NSApplicationDelegate{
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
//assign variables for launcherhelper
let launcherAppId = "Kinetic.KTGHelperLauncher"
let runningApps = NSWorkspace.shared.runningApplications
let isRunning = !runningApps.filter {$0.bundleIdentifier == launcherAppId }.isEmpty
//set launcher to login item
SMLoginItemSetEnabled(launcherAppId as CFString, true)
//status check if running or not running
if isRunning {
DistributedNotificationCenter.default().post(name: .killLauncher, object: Bundle.main.bundleIdentifier!)
}
//configure button to display button in assets
if let button = statusItem.button {
button.image = NSImage(named:NSImage.Name("kinetic_websitemain_red"))
}
//builds menu on start
constructMenu()
}
}
#NSApplicationMain
class AppDelegate: NSObject {
let statusItem = NSStatusBar.system.statusItem(withLength:NSStatusItem.squareLength)
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
#objc func TakeScreenshot(_ sender: Any){
//get path to user download folder
let dirPath = FileManager().urls(for:.downloadsDirectory, in:.userDomainMask)[0]
//create time stamp of when picture is taken
func CreateTimeStamp() -> Int32
{
return Int32(Date().timeIntervalSince1970)
}
var displayCount: UInt32 = 0;
var result = CGGetActiveDisplayList(0, nil, &displayCount)
if (result != CGError.success) {
print("error: \(result)")
return
}
let allocated = Int(displayCount)
let activeDisplays = UnsafeMutablePointer<CGDirectDisplayID>.allocate(capacity: allocated)
result = CGGetActiveDisplayList(displayCount, activeDisplays, &displayCount)
if (result != CGError.success) {
print("error: \(result)")
return
}
for i in 1...displayCount {
let unixTimestamp = CreateTimeStamp()
let fileUrl = dirPath.appendingPathComponent("\(unixTimestamp)" + "_" + "\(i)" + ".jpg", isDirectory:false)
let screenShot:CGImage = CGDisplayCreateImage(activeDisplays[Int(i-1)])!
let bitmapRep = NSBitmapImageRep(cgImage: screenShot)
let jpegData = bitmapRep.representation(using: NSBitmapImageRep.FileType.jpeg, properties: [:])!
do {
try jpegData.write(to: fileUrl, options: .atomic)
}
catch {print("error: \(error)")}
}
}
#objc func kineticSelf(_ sender: Any){
let kineticSelfUrl = URL(string: "/Library/Addigy/macmanage/MacManage.app")
NSWorkspace.shared.openFile(kineticSelfUrl!.path)
// NSWorkspace.shared.open(URL(fileURLWithPath: "/Library/Addigy/macmanage/MacManage.app"))
}
//function that opens kinetic helpdesk website
#objc func kineticHelpdesk(_ sender: Any){
let kineticHelpdeskUrl = URL(string: "http://helpdesk.kinetictg.com")!
NSWorkspace.shared.open(kineticHelpdeskUrl)
}
//function that takes user to teamviewer ktg site
#objc func kineticRemote(_ sender: Any){
let kineticRemoteUrl = URL(string: "https://get.teamviewer.com/ktgsupport")!
NSWorkspace.shared.open(kineticRemoteUrl)
}
//call kinetic
#objc func kineticHomepage(_ sender: Any){
let url = URL(string: "https://kinetictg.com")!
NSWorkspace.shared.open(url)
}
//function to build menu
func constructMenu(){
let menu = NSMenu()
//section for "Request Support"
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Request Support", action: nil, keyEquivalent:""))
//support ticket
menu.addItem(NSMenuItem(title: "Support Ticket", action:
#selector(AppDelegate.kineticHelpdesk(_:)), keyEquivalent: ""))
//remote support
menu.addItem(NSMenuItem(title: "Remote Support", action:
#selector(AppDelegate.kineticRemote(_:)), keyEquivalent: ""))
//section for "Tools"
menu.addItem(NSMenuItem.separator( ))
menu.addItem(NSMenuItem(title: "Tools", action: nil, keyEquivalent:""))
//start agent installation audit
menu.addItem(NSMenuItem(title: "Take Screenshot", action:
#selector(AppDelegate.TakeScreenshot(_:)), keyEquivalent: ""))
//open self service
menu.addItem(NSMenuItem(title: "Open Self Service", action:
#selector(AppDelegate.kineticSelf(_:)), keyEquivalent: ""))
//Section for "Info"
menu.addItem(NSMenuItem.separator( ))
menu.addItem(NSMenuItem(title: "Info", action: nil, keyEquivalent:""))
//contact info
menu.addItem(NSMenuItem(title: "Kinetic Homepage", action:
#selector(AppDelegate.kineticHomepage(_:)), keyEquivalent: ""))
//quit app
menu.addItem(NSMenuItem(title: "Quit", action:
#selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
statusItem.menu = menu
}
}
Launcher Application:
import Cocoa
//extension variable for launcher to kill launcher
extension Notification.Name {
static let killLauncher = Notification.Name("killLauncher")
}
#NSApplicationMain
class HelperAppDelegate: NSObject {
//terminate object
#objc func terminate(){
NSApp.terminate(nil)
}
}
extension HelperAppDelegate: NSApplicationDelegate{
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
//main app identifier
let mainAppIdentifier = "Kinetic.KTG-Helper"
let runningApps = NSWorkspace.shared.runningApplications
let isRunning = !runningApps.filter { $0.bundleIdentifier == mainAppIdentifier }.isEmpty
//if app is running kill launcher entity and reset status of killlauncher
if !isRunning {
DistributedNotificationCenter.default().addObserver(self, selector: #selector(self.terminate), name: .killLauncher, object: mainAppIdentifier)
let path = Bundle.main.bundlePath as NSString
var components = path.pathComponents
components.removeLast(3)
components.append("MacOS")
components.append("KTG Helper")
let newPath = NSString.path(withComponents: components)
NSWorkspace.shared.launchApplication(newPath)
}
else{
self.terminate()
}
}
//func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
//}
}

NSSplitViewController does not call delegate methods

I'm using an NSSplitViewController subclass and the delegate methods are not being called.
This is purely programmatically without nib/storyboard.
The code can be copied to a new project. The file needs to be named main.swift. Also remove "Main interface" in the project settings.
// File: main.swift
import Cocoa
// AppDelegate
class AppDelegate: NSObject, NSApplicationDelegate {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 600, height: 400),
styleMask: [ .titled, .closable, .resizable ],
backing: .buffered,
defer: false
)
func applicationDidFinishLaunching(_ aNotification: Notification) {
let splitViewController = MySplitViewController()
window.contentView = splitViewController.view
window.makeKeyAndOrderFront(nil)
}
}
// NSSplitViewController
class MySplitViewController: NSSplitViewController {
convenience init() {
self.init(nibName: nil, bundle: nil)
// Left
let viewController1 = NSViewController()
viewController1.view = NSView()
let item1 = NSSplitViewItem(viewController: viewController1)
item1.minimumThickness = 100
item1.maximumThickness = 200
addSplitViewItem(item1)
// Right
let viewController2 = NSViewController()
viewController2.view = NSView()
let item2 = NSSplitViewItem(viewController: viewController2)
addSplitViewItem(item2)
}
override func viewDidLoad() {
super.viewDidLoad()
print(splitView.delegate!) // Logs "Project1.MySplitViewController"
}
// Never called
override func splitView(_ splitView: NSSplitView, additionalEffectiveRectOfDividerAt dividerIndex: Int) -> NSRect {
print("\(#function)")
return super.splitView(splitView, additionalEffectiveRectOfDividerAt: dividerIndex)
}
}
let application = NSApplication.shared
let applicationDelegate = AppDelegate()
application.delegate = applicationDelegate
application.run()
When running the code you can see that the split view shows up and works fine.
The viewDidLoad() method prints "Project1.MySplitViewController", so the delegate is set.
But the splitView(_:additionalEffectiveRectOfDividerAt:) method is not called (or any other NSSplitViewDelegate if implemented).
The delegate method is not called because the MySplitViewController is released at the end of applicationDidFinishLaunching. Let AppDelegate hold a strong reference to the MySplitViewController.

Cocoa Xcode: Open Window Controller from AppDelegate Programmatically using Swift 4 for Mac?

I am making a simple menu bar app which has 2 items - Preferences & Quit
I want to open a new Window when I click on Preferences
Currently I have
func applicationDidFinishLaunching(_ aNotification: Notification) {
constructMenu()
}
func constructMenu() {
let menu = NSMenu()
menu.addItem(NSMenuItem(
title: "Preferences...",
action: #selector(AppDelegate.preferencesWindow(_:)),
keyEquivalent: "P"))
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(
title: "Quit",
action: #selector(NSApplication.terminate(_:)),
keyEquivalent: "q"))
statusItem.menu = menu
}
#objc func preferencesWindow(_ sender: Any) {
print("open preference window here")
if let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil) {
let controller = storyboard.instantiateControllerWithIdentifier("preferencesWindow")
as NSViewController
if let window = NSApplication.shared.mainWindow {
window.contentViewController = controller // just swap
}
}
}
But it throws error on this line
if let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil) {
stating
Initializer for conditional binding must have Optional type, not 'NSStoryboard'
When I click Preferences the print statement gets logged but I don't know how to open Window programatically.
I have just dragged Window Controller from Object Library & given StoryBoard ID a value of preferencesWindow
I also tried the following because of the above error
#objc func preferencesWindow(_ sender: Any) {
print("open preference window here")
let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil)
let controller = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "preferencesWindow")) as! NSViewController
if let window = NSApplication.shared.mainWindow {
window.contentViewController = controller // just swap
}
}
but it only logs & never opens a window. What should I do?
I find answers fast when I post a question on SO. Here's what worked for me
#objc func preferencesWindow(_ sender: Any) {
var myWindow: NSWindow? = nil
let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"),bundle: nil)
let controller = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "preferencesWindow")) as! NSViewController
myWindow = NSWindow(contentViewController: controller)
NSApp.activate(ignoringOtherApps: true)
myWindow?.makeKeyAndOrderFront(self)
let vc = NSWindowController(window: myWindow)
vc.showWindow(self)
}

How to open a locally saved PDF file with Swift

I've made a Swift menu bar app XCode 8.2.1. One of the menu bar options in this script successfully runs an AppleScript I created. I'd like to put another option in this menu bar app that opens a PDF instruction/read me file for the user. How do I open a locally saved PDF file in Swift?
I've included the code I have in the AppDelegate.swift file in XCode.
//
// AppDelegate.swift
// Layout Ad
//
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet weak var statusMenu: NSMenu!
let statusItem = NSStatusBar.system().statusItem(withLength: NSVariableStatusItemLength)
#IBAction func quitClicked(sender: NSMenuItem) {
NSApplication.shared().terminate(self)
}
func applicationDidFinishLaunching(_ aNotification: Notification) {
statusItem.menu = statusMenu
let icon = NSImage(named: "statusIcon")
//icon?.isTemplate = true // best for dark mode
statusItem.image = icon
statusItem.menu = statusMenu
constructMenu()
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
//Function to get the path to the applescript
func runApplescript(){
let task = Process()
task.launchPath = "/usr/bin/osascript"
task.arguments = ["/Library/Scripts/1_Page Layout Scripts/Layout Ad.scpt"]
task.launch()
}
//Function to open the script's instructions
func openPDF(){
//Code to open PDF file will go here
//"/Volumes/NAS/Advertising Department/16_SCRIPTS/*Instructions/2_Running Scripts/1_InDesign/Layout Ad Script Instructions.pdf"
}
//Function to make the menu work
func constructMenu() {
let menu = NSMenu()
menu.addItem(NSMenuItem(title: "Script Instructions", action: #selector(openPDF), keyEquivalent: ""))
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Layout Ad", action: #selector(runApplescript), keyEquivalent: ""))
menu.addItem(NSMenuItem.separator())
menu.addItem(NSMenuItem(title: "Quit", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
statusItem.menu = menu
}
}
NSWorkspace can do that.
let url = URL(fileURLWithPath: "/Volumes/NAS/Advertising Department/16_SCRIPTS/*Instructions/2_Running Scripts/1_InDesign/Layout Ad Script Instructions.pdf")
NSWorkspace.shared.open(url)