comparison dep/animone/src/util/osx.cc @ 268:382b50754fe4

dep/animone: make osx code a bit less hacky it would be nice if macos actually provided a real API for getting window titles (outside of the accessibility api). the accessibility API is a real mess to work with; the user has to give permission to access it under newer versions.
author Paper <paper@paper.us.eu.org>
date Fri, 12 Apr 2024 05:21:45 -0400
parents 862d0d8619f6
children
comparison
equal deleted inserted replaced
266:1a6a5d3a94cd 268:382b50754fe4
6 #include <libproc.h> 6 #include <libproc.h>
7 #include <sys/sysctl.h> 7 #include <sys/sysctl.h>
8 8
9 namespace animone::internal::osx::util { 9 namespace animone::internal::osx::util {
10 10
11 typedef CFTypeRef (*LSASNCreateWithPidSpec)(CFAllocatorRef, pid_t); 11 static bool GetProcessNameFromProcPidPath(pid_t pid, std::string& result) {
12 typedef CFDictionaryRef (*LSCopyApplicationInformationSpec)(int, CFTypeRef, CFArrayRef); 12 result.assign(PROC_PIDPATHINFO_MAXSIZE, '\0');
13 13
14 /* retrieved dynamically from launchservices */ 14 int ret = proc_pidpath(pid, result.data(), result.size() * sizeof(char));
15 static LSCopyApplicationInformationSpec LSCopyApplicationInformation = nullptr; 15 if (ret <= 0)
16 static LSASNCreateWithPidSpec LSASNCreateWithPid = nullptr;
17
18 static CFStringRef kLSDisplayNameKey = nullptr;
19 static CFStringRef kLSPIDKey = nullptr;
20
21 /* retrieved from LaunchServicesSPI.h in WebKit */
22 static constexpr int kLSDefaultSessionID = -2;
23
24 static const CFStringRef kLaunchServicesBundleID = CFSTR("com.apple.LaunchServices");
25
26 static bool GetLaunchServicesPrivateSymbols() {
27 CFBundleRef launch_services_bundle = CFBundleGetBundleWithIdentifier(kLaunchServicesBundleID);
28 if (!launch_services_bundle)
29 return false; 16 return false;
30 17
31 LSCopyApplicationInformation = reinterpret_cast<LSCopyApplicationInformationSpec>( 18 /* find the last slash, if there's none, we're done here */
32 CFBundleGetFunctionPointerForName(launch_services_bundle, CFSTR("_LSCopyApplicationInformation"))); 19 size_t last_slash = result.rfind('/');
33 if (!LSCopyApplicationInformation) 20 if (last_slash == std::string::npos)
34 return false; 21 return true;
35 22
36 LSASNCreateWithPid = reinterpret_cast<LSASNCreateWithPidSpec>( 23 result.erase(0, last_slash + 1);
37 CFBundleGetFunctionPointerForName(launch_services_bundle, CFSTR("_LSASNCreateWithPid")));
38 if (!LSASNCreateWithPid)
39 return false;
40
41 CFStringRef* ptr_kLSDisplayNameKey = reinterpret_cast<CFStringRef*>(
42 CFBundleGetDataPointerForName(launch_services_bundle, CFSTR("_kLSDisplayNameKey")));
43 if (!ptr_kLSDisplayNameKey)
44 return false;
45 kLSDisplayNameKey = *ptr_kLSDisplayNameKey;
46
47 CFStringRef* ptr_kLSPIDKey =
48 reinterpret_cast<CFStringRef*>(CFBundleGetDataPointerForName(launch_services_bundle, CFSTR("_kLSPIDKey")));
49 if (!ptr_kLSPIDKey)
50 return false;
51 kLSPIDKey = *ptr_kLSPIDKey;
52
53 return true; 24 return true;
54 } 25 }
55 26
56 static bool LaunchServicesGetProcessName(pid_t pid, std::string& result) { 27 static bool GetProcessNameFromProcName(pid_t pid, std::string& result) {
57 if (!LSCopyApplicationInformation || !LSASNCreateWithPid || !kLSDisplayNameKey || !kLSPIDKey) 28 result.assign(2 * MAXCOMLEN, '\0');
58 if (!GetLaunchServicesPrivateSymbols())
59 return false;
60
61 /* what the hell is an `asn`? */
62 CFPtr<CFTypeRef> asn = LSASNCreateWithPid(kCFAllocatorDefault, pid);
63 if (!asn)
64 return false;
65
66 CFPtr<CFArrayRef> request_array = CFArrayCreate(NULL, (const void**)kLSDisplayNameKey, 1, NULL);
67 if (!request_array)
68 return false;
69
70 CFPtr<CFDictionaryRef> dictionary =
71 LSCopyApplicationInformation(kLSDefaultSessionID, asn.get(), request_array.get());
72 if (!dictionary)
73 return false;
74
75 {
76 /* this doesn't need to be free'd */
77 CFStringRef rstr;
78
79 if (!CFDictionaryGetValueIfPresent(dictionary, kLSDisplayNameKey, (CFTypeRef*)&rstr) || !rstr)
80 return false;
81
82 if (!StringFromCFString(rstr, result))
83 return false;
84 }
85
86 result.resize(result.find('\0'));
87
88 return true;
89 }
90
91 bool StringFromCFString(CFStringRef string, std::string& result) {
92 if (!string)
93 return false;
94
95 result.resize(CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8) + 1);
96 if (!CFStringGetCString(string, &result.front(), result.length(), kCFStringEncodingUTF8))
97 return false;
98
99 return true;
100 }
101
102 static bool GetProcessArgs(pid_t pid, std::string& args) {
103 /* sysctl shouldn't touch these, so we define them as const */
104 int mib[3] = {CTL_KERN, KERN_PROCARGS2, static_cast<int>(pid)};
105 const size_t mib_size = sizeof(mib) / sizeof(*mib);
106
107 /* Get the initial size of the array
108 *
109 * NOTE: it IS possible for this value to change inbetween calls to sysctl().
110 * Unfortunately, I couldn't care less about handling this. :)
111 *
112 * is that really true, though? these should be constant values. but are
113 * argc and argv *really* constant?
114 */
115 size_t size;
116 {
117 int ret = sysctl((int*)mib, mib_size, nullptr, &size, nullptr, 0);
118 if (ret)
119 return false;
120 }
121
122 /* Reserve the space for it in args */
123 args.resize(size);
124
125 /* Get the contents of argc and argv */
126 {
127 int ret = sysctl((int*)mib, mib_size, &args.front(), &size, NULL, 0);
128 if (ret)
129 return false;
130 }
131
132 /* Is the size big enough to hold at least argc? */
133 if (size < sizeof(int))
134 return false;
135
136 args.resize(size);
137 return true;
138 }
139
140 static bool GetProcessNameFromArgs(pid_t pid, std::string& result) {
141 if (!GetProcessArgs(pid, result))
142 return false;
143
144 /* Get argc using memcpy */
145 int argc = 0;
146 memcpy(&argc, &result.front(), sizeof(argc));
147
148 /* Do we even have argv[0]? */
149 if (argc < 1)
150 return false;
151
152 /* Find the first null character */
153 size_t null_pos = result.find('\0', sizeof(argc));
154 if (null_pos == std::string::npos)
155 return false;
156
157 /* Find the last slash */
158 size_t last_slash = result.rfind('/', null_pos);
159 if (last_slash == std::string::npos)
160 return false;
161
162 /* Return our result */
163 result = result.substr(last_slash + 1, null_pos - last_slash - 1);
164 return true;
165 }
166
167 static bool GetProcessNameFromKernel(pid_t pid, std::string& result) {
168 result.resize(2 * MAXCOMLEN);
169 29
170 int size = proc_name(pid, &result.front(), result.length()); 30 int size = proc_name(pid, &result.front(), result.length());
171 if (!size) 31
32 /* if size is MAXCOMLEN or 2 * MAXCOMLEN, assume
33 * this method won't work and our result is truncated */
34 if (size <= 0 || size == MAXCOMLEN || size == 2 * MAXCOMLEN)
172 return false; 35 return false;
173 36
174 result.resize(size); 37 result.resize(size);
175 return true; 38 return true;
176 } 39 }
177 40
178 bool GetProcessName(pid_t pid, std::string& result) { 41 bool GetProcessName(pid_t pid, std::string& result) {
179 if (LaunchServicesGetProcessName(pid, result)) 42 if (GetProcessNameFromProcName(pid, result))
180 return true; 43 return true;
181 44
182 /* Try parsing the arguments, this prevents the process name being 45 if (GetProcessNameFromProcPidPath(pid, result))
183 * cut off to 2*MAXCOMLEN (32 chars) */
184 if (GetProcessNameFromArgs(pid, result))
185 return true;
186
187 /* Then attempt getting it from the kernel, which results in the
188 * process name being cut to 32 chars (worse, 16 chars if p_name is
189 * unavailable) */
190 if (GetProcessNameFromKernel(pid, result))
191 return true; 46 return true;
192 47
193 return false; 48 return false;
194 } 49 }
195 50