Error 1002 in applescript that uses "key code" when called from macOS app - swift

I'm trying to run an AppleScript from my macOS app (Mojave, Xcode 10.3). However, when the AppleScript is run from the app, I get this error:
/Users/my_username/Desktop/test_apple_script.scpt: execution error: System Events got an error: osascript is not allowed to send keystrokes. (1002)
Based on other posts, I have made sure that my app can control my computer (System Preferences > Security & Privacy > Privacy > Accessibility), and it also has control over System Events (System Preferences > Security & Privacy > Privacy > Automation).
Here is my AppleScript:
tell application "System Events"
key code 48 using {command down}
end tell
The script works when run in Script Editor (I only had to give Script Editor the access in System Preferences > Security & Privacy > Privacy > Accessibility).
This is the code I am using to run the Apple Script from my app (via osascript):
let proc = Process()
proc.launchPath = "/usr/bin/env"
proc.arguments = ["/usr/bin/osascript","/Users/my_username/Desktop/test_apple_script.scpt"]
proc.launch()
This code works perfectly when I run a simple AppleScript that just displays a notification:
tell application "System Events"
display dialog "Test"
end tell
How can I make it so that my AppleScript runs from my macOS app just as it does when I run it in Script Editor?
Edit: For some more context, the command tab case here is just an example. I am trying to find a generalizable approach to simulating keyboard shortcuts by clicking buttons in my app (e.g., the user clicks on the "cut" button and the app acts as if the user has just pressed command + x). There may be a better approach to this than using an AppleScript, but I know that it is possible to do what I want using AppleScripts.
Edit: In case it wasn't clear from my original question, the simple AppleScript that displays a notification was already working from within my app. The problem arises when I try to run an AppleScript that simulates user input from the keyboard.
Edit: This code here worked for me temporarily, but it no longer works. I did not change the code at all. I simply ran my app again.
let PlayPauseScript = "tell application \"Spotify\"\n playpause\n end tell"\"test\"\n end tell"
if let scriptObject = NSAppleScript(source: PlayPauseScript) {
scriptObject.executeAndReturnError(nil)
}
Edit:
Here is where the problem currently stands. Based on suggestions, I added this to my info.plist, and now the Spotify AppleScript consistently works (when called via NSAppleScript):
<key>NSAppleEventsUsageDescription</key>
<string>...this is why I need permission...</string>
It seems my problem (now) is just with AppleScripts that use System Events. Despite making sure that both my app and osascript are allowed to control my computer (via security & privacy > privacy > accessibility), I now get the same error (1002) when using NEAppleScript and osascript. My app is also allowed to control System Events (security & privacy > privacy > automation). The osascript error prints to the console in Xcode, and I uncovered the error code for NEAppleScript by using this code (in the #IBAction of the button):
let script =
"""
try
tell application "System Events"
keystroke "c" using {command down}
end tell
on error errMsg number errorNumber
display dialog "An unknown error occurred: " & errorNumber as text
end try
"""
if let scriptObject = NSAppleScript(source: script) {
scriptObject.executeAndReturnError(nil)
}
The above code caused this to appear in the dialog box:
An unknown error occurred: 1002
When my app is run, it does prompt me to give it control over System Events, which I always do. My app is not sandboxed. I believe that both NSAppleScript and oascript did work for me once, but stopped working when I ran the app again (despite my not having made any changes to my code, I'm almost positive).
I don't know if this is informative, but the only other clue I have as to what might be going on is that I keep having to check and uncheck Script Editor's permission in security & privacy > privacy > accessibility to get it to run these AppleScripts because it tells me that Script Editor is not allowed to send keystrokes.

Security & Privacy -> Privacy tab -> Accessibility
Add /usr/bin/osascript
NOTE: You may also need to add your terminal (iTerm in my case)

To fix osascript 1002 permission issue.
Got to Security & Privacy -> Privacy tab -> Accessibility -> Add osascript (/usr/bin/osascript)
Or Use NSAppleScript

This is a couple of things for you to consider, more than a real answer to your question. If you'd like more details, I'll happily edit them in on request. (also, I'll put code examples in objective-C, because I'm still learning swift)
First, if you're going to be doing a lot of scripting in your app, it may be worth your while to switch over to Scripting Bridge. Scripting Bridge is a cocoa interface for applescripting other apps, so (to steal an example I found elsewhere) if you want to get the name of the current track in iTunes, you'd do something like:
iTuneApplication *iTunes = [SBApplication applicationWithBundleIdentifier:#"com.apple.iTunes"];
currentTrack = iTunes.currentTrack.name;
If you want specific code for Spotify I'll have to install that app; let me know.
Second, if you want to do keystrokes, you should look into Quartz Event Services. QES allows you to create, send, and capture input events. So if you want to send a ⌘C, you might do something like:
// '8' is the key code for 'c'
comCDownEvent = CGEventCreateKeyboardEvent(nil, 8, true);
CGEventSetFlags(comCDownEvent, kCGEventFlagMaskCommand);
comCUpEvent = CGEventCreateKeyboardEvent(nil, 8, false);
CGEventSetFlags(comCUpEvent, kCGEventFlagMaskCommand);
CGEventPostToPSN(targetAppProcessSerialNumber, comCDownEvent);
CGEventPostToPSN(targetAppProcessSerialNumber, comCUpEvent);
You can't run this code from sandboxed apps, but I think you'll have that problem any way you go.
The main advantage of these approaches is that they avoid switching contexts: it's all done from cocoa, so you don't need to invoke a shell or create an AppleScript environment, and avoid all the translation problems that involves.

Related

VBS: Word.Documents.Open fails unattended [duplicate]

I have an Access VBA macro that generates a report, saves it in .pdf and then sends it by e-mail using CDO. Everything works fine if I run it manually or if I set it to be run on Task Scheduler with the security option "Run only when user is logged on". However, if I set the option to "Run whether user is logged on or not" (even with the option "Run with highest privileges") the program crashes on the second line of:
strFileFullPath = CurrentProject.Path & "\Test Report.pdf"
DoCmd.OutputTo acOutputReport, strReportName, acFormatPDF, strFileFullPath
with the error
Microsoft Access can't save the output data to the file you've
selected.
I am pretty sure that this happens because the macro runs with other user in the background. I have been searching for a solution but all I have found is that it is not possible and that I should change for other printing methods, such as PDF Creator (which brings a lot of other issues).
I am using Access 2016 in Windows Server 2012 R2 Standard.
It sounds crazy, but after a few days with no results, I managed to solve my problem just by creating these two empty folders:
C:\Windows\System32\config\systemprofile\Desktop
C:\Windows\SysWOW64\config\systemprofile\Desktop
All the credits to Faye's comment on the bottom of this page: https://blogs.technet.microsoft.com/askperf/2015/02/18/help-my-scheduled-task-does-not-run/
Although the comment regards Excel, it solved my issue on Access. It seems that is related with Office having trouble with running some processes (in my case, a pdf generation) in non-interactive mode (which is the mode that Task Scheduler runs when "Run whether user is logged on or not" is checked).

How to detect keystrokes globally in Swift on macOS?

Here is what I tried:
NSEvent.addGlobalMonitorForEvents(matching: [.keyDown]) { (event) in
print(event.keyCode)
}
Unfortunately, it does not print anything.
And no, it's not a duplicate of this, that question is about modifier keys, my question is about keystrokes.
Looks like the "duplicate" mark got removed, but so has the answer that I kludged into the comments section. So, for posterity:
The reason this doesn't work is because global monitors for .keyDown events require more permissions than some of the other event handlers, including the one that somebody thought this was a duplicate of. This is mainly because global .keyDown monitors can be used for nefarious purposes, such as keyloggers. So there are additional security measures in place to make sure we're legit:
1) Your app needs to be code-signed.
2) Your app needs to not have the App Sandbox enabled, and:
3) Your app needs to be registered in the Security and Privacy preference pane, under Accessibility.
The third one of these things has to be enabled by the user, but you can nudge them in that direction with this code:
let options: NSDictionary = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String : true]
let accessEnabled = AXIsProcessTrustedWithOptions(options)
if !accessEnabled {
print("Access Not Enabled")
}
This will prompt the user, giving him/her the option to automatically open the appropriate preference pane where the user can allow your app to control the computer via the Accessibility API, which, assuming your app is signed and not sandboxed, will allow your global .keyDown monitor to work.
if you only want global hotkey support all this is unnecessary (and not all random key or mouse events) you can do that easily with the hotkey API. look at e.g. PTHotkey :)
or a newer api .. seee also: How to implement shortcut key input in Mac Cocoa App?

Activate App but keep script running Applescript

I would like to open another app (applescript applet) with Applescript but still move on to the next line in my script.
I though it would automatically do this but it does not for some reason.
Here is my code:
...
if selectedMenu is "1" then
set theDisplay to "⬟"
my display(theDisplay)
tell application "/Users/Patrick/Documents/Programming/Applescript/Applications/NoWatch.app/Contents/Resources/Both.app" to activate
tell application "/Users/Patrick/Documents/Programming/Applescript/Applications/NoWatch.app/Contents/Resources/VNC.app" to quit saving no
tell application "/Users/Patrick/Documents/Programming/Applescript/Applications/NoWatch.app/Contents/Resources/SSH.app" to quit saving no
...
I included the part after the activation just incase that is what is causing this.
Also, after closing the app I activated, the script continues like normal.
ignoring application responses
tell application "Finder" to activate
end ignoring

Cocoa invoke Service, do not overwrite Pasteboard

I have created a Service in Cocoa which grabs the selected Text and sends the result back to my Main App, so i can handle it there ( Couldn't find any other way to get current selection), now that the Service works and appears in the Service Menu, i tried to invoke the Service from my parent App to get current selection, after some goggling around i found this snippet:
NSPasteboard *pboard = [NSPasteboard pasteboardWithUniqueName];
[pboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
NSPerformService(#"PCB", pboard);
This one works as far as it triggers my Service, the Problem here is it redefines the NSPasteboard, so my service doesn't get the selected text, but a NIL Value Pasteboard which is blank, how can i prevent this?
And does someone know how to convert a .service bundle into an .app bundle that performs itself and sends the data and kills itself after finish?
thx for help
You want to get the text that is selected in another application, right? Probably in the front application, while your app is in the background.
For this to work, you'd have to have the Service be invoked by the front application. If you invoke it from your app in the background, it can't access the front app's text field that contains the selected text. Instead, it'll try to find a text field in your own app's responder chain (I believe – someone correct me if I'm wrong on this detail).
But for your code to run in the app's process, you'll have to inject it somehow, which is - out of security concerns - mostly prohibited by OS X, and especially with sandboxed apps.
There are ways to accomplish code injection, one that 1Password and other popular tools use it through an osax extension. But that's an entirely different topic.
Once you have your code running inside the other app's process, you should be able to copy the selected text (provided it's a Cocoa app) with [NSTextView writeSelectionToPasteboard:types:]. I haven't tested this myself, though, so this is just an assumption.

how to get asl_log to appear in the iOS system console output?

I'm investigating whether I can get better performance from asl_log than NSLog on iPhone/iOS (probably...) but I'm stuck at a point where it doesn't seem that asl's log output will show up in the System Console (as viewable by a number of apps like System Console, iConsole, etc). I know that I'm setting it up right since I open with ASL_OPT_STDERR, and I see the log entries in XCode when the device is tethered.
I've explored lots of interesting stuff online (e.g. http://boredzo.org/blog/archives/2008-01-20/asl-logging, https://github.com/robbiehanson/CocoaLumberjack) and the best hope seemed to be asl_open() with Facility of "com.apple.console" but alas, the output still doesn't show up in Console. Is NSLog the only option?
Add STDERR to ASL and then it shows up in the console
asl_add_log_file(NULL, STDERR_FILENO);
You need to set read privileges on the message, using either ReadUID or ReadGID. Setting either to -1 will allow any user/group to view the message according to the header file documentation.
aslmsg msg = asl_new(ASL_TYPE_MSG);
asl_set(msg, ASL_KEY_READ_UID, "-1");
asl_log(NULL, msg, ASL_LEVEL_NOTICE, "Hello, world!");
asl_free(msg);