Search code examples
swiftmacoscocoa

How to get arguments of NSRunningApplication?


How do I get the list of arguments using during launch for a NSRunningApplication, similar to the ones I see when I run ps aux:

let workspace = NSWorkspace.shared
let applications = workspace.runningApplications

for application in applications {
    // how do I get arguments that were used during application launch?
}

Solution

  • The "ps" tool uses sysctl() with KERN_PROCARGS2 to get the arguments of a running process. The following is an attempt to translate the code from adv_cmds-153/ps/print.c to Swift. That file also contains a documentation of the memory layout of the raw argument space and explains how to locate the string arguments in that memory.

    func processArguments(pid: pid_t) -> [String]? {
        
        // Determine space for arguments:
        var name : [CInt] = [ CTL_KERN, KERN_PROCARGS2, pid ]
        var length: size_t = 0
        if sysctl(&name, CUnsignedInt(name.count), nil, &length, nil, 0) == -1 {
            return nil
        }
        
        // Get raw arguments:
        var buffer = [CChar](repeating: 0, count: length)
        if sysctl(&name, CUnsignedInt(name.count), &buffer, &length, nil, 0) == -1 {
            return nil
        }
        
        // There should be at least the space for the argument count:
        var argc : CInt = 0
        if length < MemoryLayout.size(ofValue: argc) {
            return nil
        }
        
        var argv: [String] = []
        
        buffer.withUnsafeBufferPointer { bp in
            
            // Get argc:
            memcpy(&argc, bp.baseAddress, MemoryLayout.size(ofValue: argc))
            var pos = MemoryLayout.size(ofValue: argc)
            
            // Skip the saved exec_path.
            while pos < bp.count && bp[pos] != 0 {
                pos += 1
            }
            if pos == bp.count {
                return
            }
            
            // Skip trailing '\0' characters.
            while pos < bp.count && bp[pos] == 0 {
                pos += 1
            }
            if pos == bp.count {
                return
            }
            
            // Iterate through the '\0'-terminated strings.
            for _ in 0..<argc {
                let start = bp.baseAddress! + pos
                while pos < bp.count && bp[pos] != 0 {
                    pos += 1
                }
                if pos == bp.count {
                    return
                }
                argv.append(String(cString: start))
                pos += 1
            }
        }
        
        return argv.count == argc ? argv : nil
    }
    

    There is only a simple error handling: if anything goes wrong, the function returns nil.

    For an instance of NSRunningApplication you can then call

    processArguments(pid: application.processIdentifier)