[quote='806791022, torarnv, /thread/765060?answerId=806791022#806791022, /profile/torarnv']
What infrastructure would this be? I know of open, but are there others?
[/quote]
I tried to launch an executable with open --stdout /tmp/stdout --stderr /tmp/stderr -W ./foo --args bar baz but that doesn't seem to give the expected result:
The executable is launched via Terminal.app, instead of directly (like an application bundle would have)
The --args are not propagated to the executable. Presumably they are passed to Terminal.app, which ignores them (?)
The executable's stdout/stderr is not written to the files. Presumably it's the output of Terminal.app that gets written to these files (?).
Is there a way to launch an executable via the Launch Services APIs or otherwise, that:
Passes on arguments
Propagates stdout/stderr
Waits for the executable to end
? Perhaps via launchctl bsexec? Or registering another application as the default handler of public.unix-executable instead of Terminal, which does the above?
Post
Replies
Boosts
Views
Activity
Thanks Quinn!
The use-case here is UI testing, but not necessarily though xcodebuild, so any magic that xcodebuild does internally to handle being launched from a SSH session would not help in the general case.
The general case would be a suite of app bundles or standalone executables, launched via e.g. CTest. For example test-window-positioning.app which would be launched by executing test-window-positioning.app/Contents/MacOS/test-window-positioning and checking its exit code.
[quote='806773022, DTS Engineer, /thread/765060?answerId=806773022#806773022']
Running GUI code in an SSH session is always a bad idea. [/quote]
The original Technical Note TN2083 lists some cases of why running GUI code from an SSH session is a bad idea (such as not being able to interact with the Dock), but these cases seem to work fine in macOS 12-15. Do you know of any concrete cases where this will be an issue? Knowing these will help analyze the potential risk of current solutions based on SSH sessions.
[quote='806773022, DTS Engineer, /thread/765060?answerId=806773022#806773022']
However, modern versions of macOS [1] provide the infrastructure that makes it possible to cross sessions, that is, starting code in the GUI session from the SSH session.
[/quote]
What infrastructure would this be? I know of open, but are there others?
Of course the responsible process of the test suite will be different in the two cases, either ci-agent-runner if launched via a Launch Agent, or sshd-keygen-wrapper if launched via SSH.
Let's assume for this topic that the test suite is launched disclaimed, making it its own responsible process regardless of the parent process hierarchy.
The end goal is to automate UI testing in the general sense, in a way that does not result in false positives/negatives due to running in a slightly different environment than what an end user would.
Most CI/CD tools (GitHub, GitLab, CircleCI, Jenkins, etc) provide some sort of runner/agent that acts as the parent process of the test suites being run (which can be implemented via xcodebuild, CTest, GoogleTest, etc.).
Sometimes these runners/agents are launched as LaunchAgents, and sometimes they are running on a separate machine (host), and use SSH to run the command on the "worker node" (VM).
The latter (using SSH) has the advantage that it simplifies provisioning and CI integration quite a bit. It also matches the situation when a developer is investigating a CI test failure, as they will typically SSH into the CI node and run the test there. Having the two situations run the test in a similar fashion is advantageous.
So if running UI tests via SSH is close enough to what would be the environment when run via a LaunchAgent that would be great.
If that's not the case, then some details on why this is problematic (in practice) would be appreciated, to be able to analyze the impact and importance of moving existing CI solutions over to a different approach.
Thanks!
I have yet to find where macOS stores the state that I've extended the screen capture period by another week/month. It doesn't seem to be stored in the TCC databases. If there was a place I could adjust this programatically (with SIP) disabled, it would help a lot.
The UUID behavior is also described here https://forums.developer.apple.com/forums/thread/737416
Filed FB14587785 to track the confusing situation of the UI reflecting granted permission, while not working in practice.
Thanks Quinn! Sorry for hijacking the thread 😬
I've tried to reproduce this using the minimal sample from the Triggering the Local Network Privacy Alert, but launctrl load ~/Library/LaunchAgents/foo.plist doesn't bring up any privacy dialog.
<?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>Label</key>
<string>org.foo.test</string>
<key>ProgramArguments</key>
<array>
<string>/Users/admin/foo</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<false/>
</dict>
</plist>
foo.m: https://gist.github.com/torarnv/7db4278860d06dce49179322031f8cea
Using launchctl load to load the launch agent successfully brings up the permission dialog.
And accepting the permission is reflected in the Settings app for the executable.
However there doesn't seem to be any change recorded in the global or user-specific TCC database (TCC.db).
And the CI agent complains about not having a route to the host it's trying to reach.
With Console.app reporting:
runningboardd _executablePath = /Users/qt/ci-agent
runningboardd Resolved pid 811 to [osservice<org.qt.io.ci-agent(501)>:811]
runningboardd [osservice<org.qt.io.ci-agent(501)>:811] is not RunningBoard jetsam managed.
runningboardd [osservice<org.qt.io.ci-agent(501)>:811] This process will not be managed.
runningboardd [osservice<com.apple.nehelper>:179] query resolved to [osservice<org.qt.io.ci-agent(501)>:811]
nehelper No team ID found for (bundleID: a.out, name: ci-agent)
nehelper Got message for: ci-agent (TEAMID.a.out PID 811)
nehelper Path /Users/qt/ci-agent for PID 811 matches existing rule
nehelper Local network allowed by preference for ci-agent (a.out)
nehelper Local network allowed by preference for ci-agent (a.out), but received prompt. Clearing cached UUIDs and restarting session.
nehelper +[NEProcessInfo copyUUIDsForExecutable:]_block_invoke: failed to get UUIDs for /Users/qt/ci-agent
We are hitting this as well, when our CI agent running as a Launch Agent on macOS 15 VMs tries to connect to the CI scheduler on the same network.
I haven't tested it yet, but I assume that using launchctrl to load/start the agent as part of manual provisioning of the macOS 15 CI VM will trigger the permission dialog (as long as the VM is running in the same network conditions as it will be when part of the CI pool and handling jobs), and that this permission will persist for later.
Ideally there would be a programatic way to provision this.
I tried
[UIApplication.sharedApplication activateSceneSessionForRequest:[UISceneSessionActivationRequest requestWithRole:UISceneSessionRoleImmersiveSpaceApplication] errorHandler:nil]
But doing so results in
Error Domain=UISceneErrorDomain Code=1 "Scene session activation failed because the requested role "UISceneSessionRoleImmersiveSpaceApplication" is not supported.”
Not sure why?
However, using a manifest of
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationPreferredDefaultSceneSessionRole</key>
<string>UISceneSessionRoleImmersiveSpaceApplication</string>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UISceneSessionRoleImmersiveSpaceApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>ImmersiveApp</string>
</dict>
</array>
</dict>
</dict>
Seems to work, giving callbacks to application:configurationForConnectingSceneSession:options with
<UISceneSession: 0x60000179c540; role: UISceneSessionRoleImmersiveSpaceApplication; persistentIdentifier: org.qt-project.example-xrsimulator.rasterwindow:SFBSystemService-7F604A14-E3DE-43D2-86E0-05CC29DAE1DC; scene: (nil); configuration: <UISceneConfiguration: 0x60000179d140>; userInfo: <__NSDictionary0: 0x1e494e2e0>>
So I’m not sure why the explicit activateSceneSessionForRequest API doesn’t work?
To test things further, I've created a custom keyboard layout (attached), with the following mappings:
+----------+--------------+--------------------+----------+
| Key code | Physical key | No modifiers layer | ⌘ layer |
+----------+--------------+--------------------+----------+
| 7 | x | e | c |
+----------+--------------+--------------------+----------+
| 8 | c | a | b |
+----------+--------------+--------------------+----------+
| 9 | v | c | d |
+----------+--------------+--------------------+----------+
Testing with an NSButton with .keyEquivalent = @"c" and .keyEquivalentModifierMask = NSEventModifierFlagCommand, I'm observing:
Pressing the c key alone, or with ⌘, does not trigger the button, so the logic in NSButton does not seem to be based on the key code of the event (8)
Pressing the x key alone does not trigger the button, but pressing ⌘x does, so the logic does seems to match on NSEvent.characters
Pressing the v key alone does not trigger the button, nor does pressing ⌘v, so the logic does not to match on NSEvent.charactersIgnoringModifiers
The behavior observed for [NSEvent _matchesKeyEquivalent:modifierMask:] seems to be exactly the same.
This would indicate that a "correct and robust" implementation is:
- (BOOL)performKeyEquivalent:(NSEvent *)event
{
if (event.modifierFlags & self.keyEquivalentModifierMask
&& [event.characters isEqualToString:self.keyEquivalent])
return YES;
return NO;
}
It this the case? Are the more things to consider? Thanks!
test-keyboard.keylayout.txt
Thank you for your reply. I've observed the switch to a Latin/English-like layout (using Accessibility Keyboard) when pressing ⌘ for the Hebrew layout.
The question is how an implementation of performKeyEquivalent would correctly account for and support this?
Based on the performKeyEquivalent documentation here, and the Handling Key Events documentation here, the implementation should check the unmodified characters of the incoming events:
You should extract the characters for a key equivalent using the NSEvent method charactersIgnoringModifiers.
The implementation should extract the characters for a key equivalent from the passed-in NSEvent object using the charactersIgnoringModifiers method and then examine them to determine if they are a key equivalent it recognizes.
Which naively would be something like:
- (BOOL)performKeyEquivalent:(NSEvent *)event
{
if (event.modifierFlags & NSEventModifierFlagCommand) {
if ([event.charactersIgnoringModifiers isEqualToString:@"c"])
return YES;
}
return NO;
}
But this implementation would fail to catch the Latin layer of the Hebrew layout.
Assuming the documentation is incomplete, a more robust implementation would perhaps be something like:
- (BOOL)performKeyEquivalent:(NSEvent *)event
{
if (event.modifierFlags & NSEventModifierFlagCommand) {
if ([event.charactersIgnoringModifiers isEqualToString:@"c"])
return YES;
if ([event.characters isEqualToString:@"c"])
return YES;
}
return NO;
}
But is that correct? Are there cases where comparing against both the modified and unmodified characters will fail or produce unexpected matches? Should the behavior be different based on properties of the keyboard layout?
[NSEvent _matchesKeyEquivalent:modifierMask:] seems to handle this correctly, but contains references to text input APIs like TISCopyCurrentKeyboardInputSource, TSMGetInputSourceProperty, and TSMGetDeadKeyState, which seems to indicate the naive approach above is not sufficient to match AppKit's own behavior.
The goal would be for the custom control to respond to performKeyEquivalent the same way an NSButton or NSMenuItem would in terms of which events are considered matches and which are not. In particular, for a generic control that allows user defined key equivalents, so without relying on specifics of any particular key equivalent.