11import ArgumentParser
22import Foundation
3+ import CoreGraphics
4+ import Darwin
35
46fileprivate struct VMInfo : Encodable {
57 let OS : OS
@@ -11,6 +13,29 @@ fileprivate struct VMInfo: Encodable {
1113 let Display : String
1214 let Running : Bool
1315 let State : String
16+ let NoGraphics : Bool ?
17+
18+ enum CodingKeys : String , CodingKey {
19+ case OS, CPU, Memory, Disk, DiskFormat, Size, Display, Running, State, NoGraphics
20+ }
21+
22+ func encode( to encoder: Encoder ) throws {
23+ var container = encoder. container ( keyedBy: CodingKeys . self)
24+ try container. encode ( OS, forKey: . OS)
25+ try container. encode ( CPU, forKey: . CPU)
26+ try container. encode ( Memory, forKey: . Memory)
27+ try container. encode ( Disk, forKey: . Disk)
28+ try container. encode ( DiskFormat, forKey: . DiskFormat)
29+ try container. encode ( Size, forKey: . Size)
30+ try container. encode ( Display, forKey: . Display)
31+ try container. encode ( Running, forKey: . Running)
32+ try container. encode ( State, forKey: . State)
33+ if let noGraphics = NoGraphics {
34+ try container. encode ( noGraphics, forKey: . NoGraphics)
35+ } else {
36+ try container. encodeNil ( forKey: . NoGraphics)
37+ }
38+ }
1439}
1540
1641struct Get : AsyncParsableCommand {
@@ -27,7 +52,83 @@ struct Get: AsyncParsableCommand {
2752 let vmConfig = try VMConfig ( fromURL: vmDir. configURL)
2853 let memorySizeInMb = vmConfig. memorySize / 1024 / 1024
2954
30- let info = VMInfo ( OS: vmConfig. os, CPU: vmConfig. cpuCount, Memory: memorySizeInMb, Disk: try vmDir. sizeGB ( ) , DiskFormat: vmConfig. diskFormat. rawValue, Size: String ( format: " %.3f " , Float ( try vmDir. allocatedSizeBytes ( ) ) / 1000 / 1000 / 1000 ) , Display: vmConfig. display. description, Running: try vmDir. running ( ) , State: try vmDir. state ( ) . rawValue)
55+ // Check if VM is running without graphics (no windows)
56+ var noGraphics : Bool ? = nil
57+ if try vmDir. running ( ) {
58+ let lock = try vmDir. lock ( )
59+ let pid = try lock. pid ( )
60+ if pid > 0 {
61+ noGraphics = try hasNoWindows ( pid: pid)
62+ }
63+ }
64+
65+ let info = VMInfo ( OS: vmConfig. os, CPU: vmConfig. cpuCount, Memory: memorySizeInMb, Disk: try vmDir. sizeGB ( ) , DiskFormat: vmConfig. diskFormat. rawValue, Size: String ( format: " %.3f " , Float ( try vmDir. allocatedSizeBytes ( ) ) / 1000 / 1000 / 1000 ) , Display: vmConfig. display. description, Running: try vmDir. running ( ) , State: try vmDir. state ( ) . rawValue, NoGraphics: noGraphics)
3166 print ( format. renderSingle ( info) )
3267 }
68+
69+ private func hasNoWindows( pid: pid_t ) throws -> Bool {
70+ // Check if the process and its children have any windows using Core Graphics Window Server
71+ // This is more reliable than checking command-line arguments since there are
72+ // multiple ways a VM might run without graphics (--no-graphics flag, CI environment, etc.)
73+
74+ // Get all PIDs to check (parent + children)
75+ var pidsToCheck = [ pid]
76+ pidsToCheck. append ( contentsOf: try getChildProcesses ( of: pid) )
77+
78+ // Get all window information from the window server
79+ guard let windowList = CGWindowListCopyWindowInfo ( [ . optionOnScreenOnly, . excludeDesktopElements] , kCGNullWindowID) as? [ [ String : Any ] ] else {
80+ // If we can't get window info, assume no graphics
81+ return true
82+ }
83+
84+ // Check if any window belongs to our process or its children
85+ for windowInfo in windowList {
86+ if let windowPID = windowInfo [ kCGWindowOwnerPID as String ] as? Int32 ,
87+ pidsToCheck. contains ( windowPID) {
88+ // Found a window for this process tree, so it has graphics
89+ return false
90+ }
91+ }
92+
93+ // No windows found for this process tree
94+ return true
95+ }
96+
97+ private func getChildProcesses( of parentPID: pid_t ) throws -> [ pid_t ] {
98+ var children : [ pid_t ] = [ ]
99+
100+ // Use sysctl to get process information
101+ var mib : [ Int32 ] = [ CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 ]
102+ var size : size_t = 0
103+
104+ // Get size needed
105+ if sysctl ( & mib, 4 , nil , & size, nil , 0 ) != 0 {
106+ // If we can't get process list, return empty array
107+ return children
108+ }
109+
110+ // Allocate memory and get process list
111+ let count = size / MemoryLayout< kinfo_proc> . size
112+ var procs = Array < kinfo_proc > ( repeating: kinfo_proc ( ) , count: count)
113+
114+ if sysctl ( & mib, 4 , & procs, & size, nil , 0 ) != 0 {
115+ // If we can't get process list, return empty array
116+ return children
117+ }
118+
119+ // Find direct children of the given parent PID
120+ for proc in procs {
121+ let ppid = proc. kp_eproc. e_ppid
122+ let pid = proc. kp_proc. p_pid
123+ if ppid == parentPID && pid > 0 {
124+ children. append ( pid)
125+ // Recursively get children of children
126+ if let grandchildren = try ? getChildProcesses ( of: pid) {
127+ children. append ( contentsOf: grandchildren)
128+ }
129+ }
130+ }
131+
132+ return children
133+ }
33134}
0 commit comments