I'm using this function below under a button to relaunch the application.
func relaunchApp() {
let url = URL(fileURLWithPath: Bundle.main.resourcePath!)
let path = url.deletingLastPathComponent().deletingLastPathComponent().absoluteString
let task = Process()
task.launchPath = "/usr/bin/open"
task.arguments = [path]
task.launch()
exit(0) }
This works perfectly on my Macbook Air 2015 model. However, when I send this file to anybody else with the exact same model (or a Mac Mini I've also tried this on), nothing happens after the button is pressed. This is under both conditions of being completely compiled and just doing a run from Xcode, either work on my side. How can I solve this issue?
Maybe it's a timing issue... if the open command runs before your app is fully gone, open may think it has nothing to do. Anyway, your approach of using open seems unnecessarily complicated. Try this (pardon my Objective-C, I don't know Swift):
NSError* err = nil;
[[NSWorkspace sharedWorkspace]
launchApplicationAtURL: [[NSBundle mainBundle] bundleURL]]
options: NSWorkspaceLaunchAsync | NSWorkspaceLaunchNewInstance
configuration: nil
error: &err ];
[NSApp terminate: NSApp];
I was able to solve this by adding a / to the beginning of the resourcepath and using path rather than absoluteString.
func relaunchApp() {
let fullUrl = "/" + Bundle.main.resourcePath!
let url = URL(fileURLWithPath: fullUrl)
let path = url.deletingLastPathComponent().deletingLastPathComponent().path
let task = Process()
task.launchPath = "/usr/bin/open"
task.arguments = [path]
task.launch()
exit(0) }
I discovered this issue when another function I'm using that directly opens a file /System/Library/PreferencePanes/Bluetooth.prefPane, which while in Xcode outputted the following:
2016-12-09 11:51:56.749 Application[90077:1633677] launch path not accessible
Now, this function does work outside of Xcode but not within, and upon research of this error, I found this answer. Simply adding a forward slash and using the path rather than the absoluteString fixed the problem.
I'd like to thank #MartinR for mentioning the difference between path and absoluteString, #dfd, and #JWWalker for the help they provided.
Related
I have problems running a Terminal command from a Cocoa Application.
The input for the Terminal is real easy: /Users/.../Csvmidi </Users/.../test.csv> /Users/.../Melody.mid
These are three inputs- three actual paths - are just written in a row and seperated by a spac: the first "Csvmidi" runs a Unix Application which converts the test.csv to an actual hearable MIDI file. Through the terminal it works perfectly...
I just don't get it to work via a Cocoa Application.
let process = Process()
process.executableURL = URL(fileURLWithPath: "/bin/zsh/")
process.arguments = [lblCsvmidi.stringValue,"<"+lblURL.stringValue+">",lblMidi.stringValue]
//I saved the URL of the UNIX program, test.csv and Melody.mid in a lable just to be sure.
//lblCsvmidi --> URL of Csvmidi UNIX program
//lblURL --> URL of test.csv
//lblMidi --> URL of Melody.mid
print(lblCsvmidi.stringValue,"<" + lblURL.stringValue + ">",lblMidi.stringValue)
// this print command was only made to check the arguments in the terminal if they would work --> they do
process.terminationHandler = { (process) in
print("\ndidFinish: \(!process.isRunning)")
}
do {
try process.run()
} catch {}
When I run the code it gives me either the error of an 75: unmatched", but actually there isn't a quotation mark in the command - or I get "permission denied" errors.
I tried several bin folders like ( I really tried almost every possible):
- /bin/zsh
- /bin/csh
- /bin/ksh
- ...
What am i doing wrong --> I haven't found information in other Questions here and the Process, NSTask and Bundle informations from Apple haven't helped me so far.
Thanks!!
The following runs without error on my system:
import Cocoa
let process = Process()
process.executableURL = URL(fileURLWithPath: "/bin/zsh/")
var args : [String]!
args = []
args.append("-c")
args.append("open '/Users/xxxx/Desktop/myApp.app/Contents/MacOS/myApp'")
process.arguments = args
process.terminationHandler = { (process) in
print("\ndidFinish: \(!process.isRunning)")
}
do {
try process.run()
} catch {}
let app = NSApplication.shared
app.setActivationPolicy(.regular)
app.activate(ignoringOtherApps:true)
app.run()
The strings after "-c" would depend on what you are trying to accomplish.
I found article describing how to create plugin using Swift and Cocoa. It uses NSBundle to load plugin, but that, as far as I know, is not available in pure swift (no Cocoa). Is there way how to achieve same result without using Cocoa?
More info:
In case it's relevant, here is what I want to achieve. I create app in swift that runs on linux server. User can connect to it using their browser. I want to be able to have other people write "plugins" that will implement functionality itself (what user can see and do once they connect), from printing out hello world, through chat programs to games without having to worry about low level stuff provided by my app. Some sort of dll, that my server application loads and runs.
Solution to this is not trivial, but it's not impossible to do either. I prefer to use swift package manager to manage dependencies and Xcode as IDE. This combination is not perfect as it needs a lot of tinkering but there is not any other useable free swift IDE as of now.
You will need to set up two projects, let's call them Plugin (3rd party library) and PluginConsumer (app that uses other people plugins). You will also need to decide on API, for now we will use simple
TestPluginFunc()
Create Plugin.swift file with TestPluginFunc implementation in your Plugin project:
public func TestPluginFunc() {
print("Hooray!")
}
Set the project to build framework, not executable and build[1]. You will get Plugin.framework file which contains your plugin.
Now switch to your PluginConsumer project
Copy Plugin.framework from your Plugin project somewhere where you can easily find it. To actually load the framework and use it:
// we need to define how our plugin function looks like
typealias TestPluginFunc = #convention(c) ()->()
// and what is its name
let pluginFuncName = "TestPluginFunc"
func loadPlugin() {
let pluginName = "Plugin"
let openRes = dlopen("./\(pluginName).framework/\(pluginName)", RTLD_NOW|RTLD_LOCAL)
if openRes != nil {
// this is fragile
let symbolName = "_TF\(pluginName.utf8.count)\(pluginName)\(initFuncName.utf8.count)\(initFuncName)FT_T_"
let sym = dlsym(openRes, symbolName)
if sym != nil {
// here we load func from framework based on the name we constructed in "symbolName" variable
let f: TestPluginFunc = unsafeBitCast(sym, to: TestPluginFunc.self)
// and now all we need to do is execute our plugin function
f()
} else {
print("Error loading \(realPath). Symbol \(symbolName) not found.")
dlclose(openRes)
}
} else {
print("error opening lib")
}
}
If done correctly, you should see "Hooray!" being printed to your log.
There is a lot of room for improvement, first thing you should do is replace Plugin.framework string with parameter, preferably using some file library (I am using PerfectLib). Another thing to look at is defining plugin API in your PluginConsumer project as a protocol or base class, creating framework out of that, importing that framework in your plugin project and basing your implementation on that protocol/base class. I am trying to figure out exactly how to do that. I will update this post if I mange to do it properly.
[1]: I usually do this by creating Package.swift file and creating xcode project out of it using swift package generate-xcodeproj. If your project doesn't contain main.swift, xcode will create framework instead of executable
What you will want to do is create a folder your program will look in. Let's say it's called 'plugins'. It should make a list of names from the files in there, and then iterate through using them, passing parameters to the files and getting the output and making use of that in some way.
Activating a program and getting output:
func runCommand(cmd : String, args : String...) -> (output: [String], error: [String], exitCode: Int32) {
var output : [String] = []
var error : [String] = []
let task = Process()
task.launchPath = cmd
task.arguments = args
let outpipe = Pipe()
task.standardOutput = outpipe
let errpipe = Pipe()
task.standardError = errpipe
task.launch()
let outdata = outpipe.fileHandleForReading.readDataToEndOfFile()
if var string = String(data: outdata, encoding: .utf8) {
string = string.trimmingCharacters(in: .newlines)
output = string.components(separatedBy: "\n")
}
let errdata = errpipe.fileHandleForReading.readDataToEndOfFile()
if var string = String(data: errdata, encoding: .utf8) {
string = string.trimmingCharacters(in: .newlines)
error = string.components(separatedBy: "\n")
}
task.waitUntilExit()
let status = task.terminationStatus
return (output, error, status)
}
`
Here is how a swift plugin would accept arguments:
for i in 1..C_ARGC {
let index = Int(i);
let arg = String.fromCString(C_ARGV[index])
switch arg {
case 1:
println("1");
case 2:
println("2")
default:
println("3)
}
}
So once you have the program and plugin communicating you just have to add handling in your program based on the output so the plugins output can do something meaningful. Without cocoa libraries this seems the way to go, though if you use C there are a couple of other options available there as well. Hope this helps.
I try to start a command file from my app, so I need to use "posix_spawn". But usage of this requires strange use of pointers. I didn't find any example of Swift (3.0), only C++ or Objective C which I couldn't translate.
I simply need something like that (in old system call):
let err = system("ls -param >file.txt")
Any ideas?
Edit:
The linked solution didn't match.
First it does not use posix_spawn function, which was mentioned by the complier. It uses NSTask, which seems also be abandoned. But I tried this example and ended up in:
func shell(launchPath: String, arguments: [String]) -> String
{
let task = Process()
task.launchPath = launchPath
task.arguments = arguments
let pipe = Pipe()
task.standardOutput = pipe
task.launch()
let data = pipe.fileHandleForReading.readDataToEndOfFile()
let output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String
return output
}
Here all changes needed for XCode 8 were made. But when calling, it will never return from ".launch()".
Somewhere in Output debug window I found this line:
2016-09-15 15:06:36.793 DSRenamer[96562:2569582] launch path not accessible
Same command works fine in terminal window:
/usr/local/bin/ExifTool -ext .CR2
I use swift to call the objective-c menthod(though not the best way)
define an objective-c util menthod
#import "SystemTaskHelper.h"
#implementation SystemTaskHelper
+(void)performSystemTask:(NSString *)task
{
system([task UTF8String]);
}
#end
import objective-c util.h into Bridging-Header.h , and in swift use
SystemTaskHelper.performSystemTask("ls -param >file.txt")
In my app I would like to open another app that is installed on the User's Mac (such as iPhoto). I am not sure what I should be looking for in the documentation. What is this called and how should I do it?
Thank you
Swift 5 or later
import Cocoa
func openPhotos() -> Bool {
if let photosApp = FileManager.default.urls(
for: .applicationDirectory,
in: .systemDomainMask
).first?.appendingPathComponent("Photos.app") {
return NSWorkspace.shared.open(photosApp)
}
return false
}
Usage:
if openPhotos() {
print(true)
}
Or using launchApplication with the app name parameter in the method:
import Cocoa
func openApp(_ named: String) -> Bool {
NSWorkspace.shared.launchApplication(named)
}
Usage:
if openApp("Photos") {
print(true)
}
XCode 11 • MacOS Catalina 10.15 • Swift 5
NSWorkspace.shared.launchApplication is deprecated and starting from the MacOS 10.15 the new function NSWorkspace.shared.openApplication shall be used.
Example - open terminal application by its bundle id
guard let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: "com.apple.Terminal") else { return }
let path = "/bin"
let configuration = NSWorkspace.OpenConfiguration()
configuration.arguments = [path]
NSWorkspace.shared.openApplication(at: url,
configuration: configuration,
completionHandler: nil)
Example - open terminal application by its path
let url = NSURL(fileURLWithPath: "/System/Applications/Utilities/Terminal.app", isDirectory: true) as URL
let path = "/bin"
let configuration = NSWorkspace.OpenConfiguration()
configuration.arguments = [path]
NSWorkspace.shared.openApplication(at: url,
configuration: configuration,
completionHandler: nil)
You can use NSWorkspace class written by Swift/Cocoa.
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSWorkspace_Class/index.html#//apple_ref/occ/instm/NSWorkspace/launchApplication:
let task = NSTask.launchedTaskWithLaunchPath(<#path: String#>, arguments: <#[AnyObject]#>) will probably do what you want
I second the answer by vookimedlo - unfortunately, I cannot yet comment (silly reputation limit) so I post this as an extra answer.
This is just one caveat, which might not affect too many: while launchApplication() accepted a path to an executable (e.g. "MyApp.app/Contents/MacOS/MyApp"), this will result in an error (lacking privileges) with openApplication(::). You have to supply the path to the app bundle ("MyApp.app") instead.
Of particular interest when you try to make a helper ("launcher") to add as a login item. See the following and keep my comment in mind:
https://theswiftdev.com/2017/10/27/how-to-launch-a-macos-app-at-login/
(GREAT article by Tibor Bödecs)
BTW, as for vookimedlo's code: in my experience, you don't need to specify the OpenContext.arguments with [path], you can simply pass a default NSWorkspace.OpenContext()...
There are different ways to do that. The most efficient is to use fvork and execve - see man vfork and man execve.
Less efficient but more flexible is to use the system library call. What that actually does is runs a shell - like bash - then passes the string you provide, to bash. So you can set up pipelines, redirection and such.
Or you can send an Apple Event to the Finder: "Tell Finder Open iPhoto".
In the first two cases you want to launch the executable inside the bundle, that is, /Applications/iPhoto.app/Contents/MacOS/iPhoto.
Try the above from the command line, in the Terminal:
$ /Applications/iPhoto.app/Contents/MacOS/iPhoto
You'll see the iPhoto App launch.
I have a simple AppleScript that sends an email. How can I call it from within a Swift application?
(I wasn't able to find the answer via Google.)
As Kamaros suggests, you can call NSApplescript directly without having to launch a separate process via NSTask (as CRGreen suggests.)
Swift Code
let myAppleScript = "..."
var error: NSDictionary?
if let scriptObject = NSAppleScript(source: myAppleScript) {
if let output: NSAppleEventDescriptor = scriptObject.executeAndReturnError(
&error) {
print(output.stringValue)
} else if (error != nil) {
print("error: \(error)")
}
}
Tested: one can do something like this (arbitrary script path added):
import Foundation
let task = Process()
task.launchPath = "/usr/bin/osascript"
task.arguments = ["~/Desktop/testscript.scpt"]
task.launch()
For anyone who is getting the warning below for Swift 4, for the line while creating an NSAppleEventDescriptor from zekel's answer
Non-optional expression of type 'NSAppleEventDescriptor' used in a check for optionals
You can get rid of it with this edited short version:
let myAppleScript = "..."
var error: NSDictionary?
if let scriptObject = NSAppleScript(source: myAppleScript) {
if let outputString = scriptObject.executeAndReturnError(&error).stringValue {
print(outputString)
} else if (error != nil) {
print("error: ", error!)
}
}
However, you may have also realized; with this method, system logs this message to console everytime you run the script:
AppleEvents: received mach msg which wasn't complex type as expected
in getMemoryReference.
Apparently it is a declared bug by an Apple staff developer, and is said to be 'just' a harmless log spam and is scheduled to be removed on future OS updates, as you can see in this very long apple developer forum post and SO question below:
AppleEvents: received mach msg which wasn't complex type as expected in getMemoryReference
Thanks Apple, for those bazillions of junk console logs thrown around.
You can try NSAppleScript, from Apple's Technical Note TN2084
Using AppleScript Scripts in Cocoa Applications
https://developer.apple.com/library/mac/technotes/tn2084/_index.html
NSAppleScript* scriptObject = [[NSAppleScript alloc] initWithSource:
#"\
set app_path to path to me\n\
tell application \"System Events\"\n\
if \"AddLoginItem\" is not in (name of every login item) then\n\
make login item at end with properties {hidden:false, path:app_path}\n\
end if\n\
end tell"];
returnDescriptor = [scriptObject executeAndReturnError: &errorDict];
I struggled few hours, but nothing worked. Finally I managed to run AppleScript through shell:
let proc = Process()
proc.launchPath = "/usr/bin/env"
proc.arguments = ["/usr/bin/osascript", "scriptPath"]
proc.launch()
Dunno is this the best way to do it, but at least it works.
As of March 2018, I think the strongest answer on this thread is still the accepted answer from 2011. The implementations that involved using NSAppleScript or OSAScript suffered the drawbacks having some minor, but highly unpleasant, memory leaks without really providing any additional benefits. Anyone struggling with getting that answer to execute properly (in Swift 4) may want to try this:
let manager = FileManager()
// Note that this assumes your .scpt file is located somewhere in the Documents directory
let script: URL? = try? manager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
if let scriptPath = script?.appendingPathComponent("/path/to/scriptName").appendingPathExtension("scpt").path {
let process = Process()
if process.isRunning == false {
let pipe = Pipe()
process.launchPath = "/usr/bin/osascript"
process.arguments = [scriptPath]
process.standardError = pipe
process.launch()
}
}
Update
The simple, accepted answer from 2011 has gotten more complex. Apple has deprecated the launch() function as of as of 10.14, suggesting that we "use run() instead". Unfortunately, it's not a direct replacement. Fortunately, most of the original answer still holds, with a simple change.
Instead of the original line:
task.launch()
when you use run() you have to use it with try to catch possible errors. The line becomes:
try task.run()
The accepted answer above then becomes:
let task = Process()
task.launchPath = "/usr/bin/osascript"
task.arguments = ["~/Desktop/testscript.scpt"]
try task.run()
However, this only works if the code is at the top level, not if it is inside a function. run() throws errors while launch() does not. If you use try inside a function, the function also has to be declared to throw errors. Or you can handle possible errors from run() inside the function.
This page from "Hacking With Swift" has examples of how to catch STDOUT and STDERR from the process.
I hope this helps. I'm very new at Swift and this is what worked for me.