-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmac_impl.c
More file actions
242 lines (196 loc) · 7.34 KB
/
mac_impl.c
File metadata and controls
242 lines (196 loc) · 7.34 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
#include "../os_interface.h"
#include <ApplicationServices/ApplicationServices.h> // For Window detection
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/ps/IOPSKeys.h>
#include <IOKit/ps/IOPowerSources.h>
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <libproc.h> // For process info (name, memory)
#include <mach/mach.h>
#include <mach/mach_host.h>
#include <signal.h> // For kill(), SIGSTOP, SIGCONT
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/sysctl.h> // For swap usage
// --- 1. WINDOW DETECTION (NSWorkspace via Obj-C Runtime) ---
// We use the Runtime to avoid compiling as Objective-C (.m)
// This accesses [NSWorkspace
// sharedWorkspace].frontmostApplication.processIdentifier
#include <objc/objc-runtime.h>
int32_t os_get_active_pid(void) {
// 1. Get the NSWorkspace class
Class nsWorkspaceClass = objc_getClass("NSWorkspace");
if (!nsWorkspaceClass)
return -1;
// 2. Get the specific selector IDs we need
SEL sharedWorkspaceSel = sel_registerName("sharedWorkspace");
SEL frontmostAppSel = sel_registerName("frontmostApplication");
SEL processIdentifierSel = sel_registerName("processIdentifier");
if (!sharedWorkspaceSel || !frontmostAppSel || !processIdentifierSel)
return -1;
// 3. Call [NSWorkspace sharedWorkspace]
// casting objc_msgSend is required for safety in modern C
id (*msgSend1)(Class, SEL) = (id(*)(Class, SEL))objc_msgSend;
id workspace = msgSend1(nsWorkspaceClass, sharedWorkspaceSel);
if (!workspace)
return -1;
// 4. Call [workspace frontmostApplication]
id (*msgSend2)(id, SEL) = (id(*)(id, SEL))objc_msgSend;
id frontApp = msgSend2(workspace, frontmostAppSel);
if (!frontApp)
return -1; // No active app?
// 5. Call [frontApp processIdentifier]
// pid_t is effectively an integer, so we need a msgSend that returns int
int (*msgSend3)(id, SEL) = (int (*)(id, SEL))objc_msgSend;
int pid = msgSend3(frontApp, processIdentifierSel);
return (int32_t)pid;
}
// --- 2. PROCESS NAME (libproc) ---
void os_get_process_name(int32_t pid, char *buffer, size_t size) {
// proc_name copies the short name of the process into the buffer
int result = proc_name(pid, buffer, (uint32_t)size);
if (result <= 0) {
snprintf(buffer, size, "Unknown");
}
}
// --- 3. MEMORY USAGE (libproc) ---
uint64_t os_get_memory_usage(int32_t pid) {
struct proc_taskinfo pti;
// Query the kernel for task info
// PROC_PIDTASKINFO is a specific flavor of info that includes memory stats
int ret = proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti));
if (ret <= 0) {
return 0; // Failed to get info (process might have died)
}
// pti_resident_size is the physical RAM usage (RSS)
return pti.pti_resident_size;
}
// --- 4. FREEZE & THAW (Signals) ---
int os_freeze_process(int32_t pid) {
// 1. Get the Process Group ID (PGID)
pid_t pgid = getpgid(pid);
if (pgid < 0) {
// Fallback: If we can't find the group, just freeze the single PID
return kill(pid, SIGSTOP);
}
// Safety: Never freeze our own group (Cryo)!
// getpgid(0) returns the group of the calling process (us).
if (pgid == getpgid(0)) {
printf("[SAFETY] Prevented freezing of Cryo's own group.\n");
return -1;
}
// 2. Freeze the WHOLE Group (Parent + Children)
// killpg(pgid, signal) sends the signal to everyone in the family.
return killpg(pgid, SIGSTOP);
}
int os_thaw_process(int32_t pid) {
pid_t pgid = getpgid(pid);
if (pgid < 0) {
return kill(pid, SIGCONT);
}
// Thaw everyone in the family
return killpg(pgid, SIGCONT);
}
bool os_is_on_ac_power() {
// 1. Get a blob of power source info from the kernel
CFTypeRef powerInfo = IOPSCopyPowerSourcesInfo();
if (!powerInfo)
return true; // Assume AC if error
// 2. Get the list of power sources (Batteries, UPS, etc.)
CFArrayRef powerSourcesList = IOPSCopyPowerSourcesList(powerInfo);
if (!powerSourcesList) {
CFRelease(powerInfo);
return true;
}
bool is_ac = false;
// 3. Loop through sources (Usually just one battery)
for (CFIndex i = 0; i < CFArrayGetCount(powerSourcesList); i++) {
CFTypeRef source = CFArrayGetValueAtIndex(powerSourcesList, i);
CFDictionaryRef description =
IOPSGetPowerSourceDescription(powerInfo, source);
if (description) {
// Check the "Power Source State" key
CFStringRef state =
CFDictionaryGetValue(description, CFSTR(kIOPSPowerSourceStateKey));
// kIOPSACPowerValue means "Plugged In"
if (CFStringCompare(state, CFSTR(kIOPSACPowerValue), 0) ==
kCFCompareEqualTo) {
is_ac = true;
break;
}
}
}
// 4. Cleanup memory (Important in C!)
CFRelease(powerSourcesList);
CFRelease(powerInfo);
return is_ac;
}
// --- MEMORY PRESSURE ---
int os_get_memory_pressure() {
mach_msg_type_number_t count = HOST_VM_INFO64_COUNT;
vm_statistics64_data_t vm_stat;
// Ask the kernel for VM stats
if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info_t)&vm_stat,
&count) != KERN_SUCCESS) {
return 0; // Fail safe
}
// Calculate total pages
uint64_t total_pages = vm_stat.active_count + vm_stat.inactive_count +
vm_stat.free_count + vm_stat.wire_count;
uint64_t active_pages = vm_stat.active_count + vm_stat.wire_count;
if (total_pages == 0)
return 0;
// Return percentage used
return (int)((active_pages * 100) / total_pages);
}
uint64_t os_get_swap_usage(void) {
struct xsw_usage vmusage = {0};
size_t size = sizeof(vmusage);
if (sysctlbyname("vm.swapusage", &vmusage, &size, NULL, 0) == 0) {
return vmusage.xsu_used;
}
return 0;
}
double os_get_cpu_usage(int32_t pid) {
struct proc_taskinfo pti;
int ret = proc_pidinfo(pid, PROC_PIDTASKINFO, 0, &pti, sizeof(pti));
if (ret <= 0)
return 0.0;
// This is "CPU time total" / "Run time total" since start.
// It's an average, not instantaneous, but good enough to detect
// heavy workers like renders vs idle apps.
// Time in nanoseconds
double total_time = (double)pti.pti_total_system + (double)pti.pti_total_user;
if (total_time < 0.1)
return 0.0;
// We would need previous sample to get instantaneous load.
// For now, checks if the process has accumulated significant time recently
// is hard without state.
// ALTERNATIVE: Just check if it's "runnable" state?
// Let's stick to a placeholder returning 0 until we add stateful monitoring.
// ACTUALLY: Let's use the memory pressure + assertion check instead for now,
// as instantaneous CPU is hard in C without a monitoring thread per app.
// Implementation for Day 6: Simple "Running" check
return 0.0; // Placeholder until Day 7 Refactor
}
// --- ASSERTION CHECK ---
bool os_has_power_assertion(int32_t pid) {
CFDictionaryRef assertions = NULL;
// 1. Get all active power assertions from the OS
if (IOPMCopyAssertionsByProcess(&assertions) != kIOReturnSuccess) {
return false; // Could not check, assume safe to freeze
}
bool has_assertion = false;
// 2. The dictionary keys are PIDs (as Numbers)
long long pid_long = (long long)pid;
CFNumberRef pid_num =
CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &pid_long);
// 3. Check if our PID exists in the assertion list
if (CFDictionaryContainsKey(assertions, pid_num)) {
has_assertion = true;
}
// 4. Cleanup
CFRelease(pid_num);
CFRelease(assertions);
return has_assertion;
}