@opencoreai/opencore 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,493 @@
1
+ #import <Cocoa/Cocoa.h>
2
+ #import <QuartzCore/QuartzCore.h>
3
+
4
+ @interface OpenCoreIndicatorAppDelegate : NSObject <NSApplicationDelegate>
5
+ @property(nonatomic, strong) NSStatusItem *statusItem;
6
+ @property(nonatomic, strong) NSMenu *menu;
7
+ @property(nonatomic, strong) NSTimer *timer;
8
+ @property(nonatomic, copy) NSString *statePath;
9
+ @property(nonatomic, strong) NSDictionary *lastState;
10
+ @property(nonatomic, strong) CAGradientLayer *statusPillLayer;
11
+ @property(nonatomic, strong) CAGradientLayer *statusShineLayer;
12
+ @property(nonatomic, strong) CALayer *statusGlowLayer;
13
+ @property(nonatomic, strong) NSTrackingArea *statusTrackingArea;
14
+ @property(nonatomic, assign) BOOL hoverActive;
15
+ @end
16
+
17
+ @implementation OpenCoreIndicatorAppDelegate
18
+
19
+ - (BOOL)isDarkMode {
20
+ if (@available(macOS 10.14, *)) {
21
+ NSAppearanceName best = [NSApp.effectiveAppearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, NSAppearanceNameDarkAqua]];
22
+ return [best isEqualToString:NSAppearanceNameDarkAqua];
23
+ }
24
+ return NO;
25
+ }
26
+
27
+ - (NSColor *)menuHeaderTextColor {
28
+ return [self isDarkMode]
29
+ ? [NSColor colorWithCalibratedRed:0.96 green:0.93 blue:0.87 alpha:1.0]
30
+ : [NSColor colorWithCalibratedRed:0.18 green:0.12 blue:0.09 alpha:1.0];
31
+ }
32
+
33
+ - (NSColor *)menuBodyTextColor {
34
+ return [self isDarkMode]
35
+ ? [NSColor colorWithCalibratedRed:0.82 green:0.73 blue:0.65 alpha:1.0]
36
+ : [NSColor colorWithCalibratedRed:0.38 green:0.28 blue:0.22 alpha:1.0];
37
+ }
38
+
39
+ - (NSColor *)menuAccentTextColor {
40
+ return [self isDarkMode]
41
+ ? [NSColor colorWithCalibratedRed:1.0 green:0.69 blue:0.35 alpha:1.0]
42
+ : [NSColor colorWithCalibratedRed:0.74 green:0.37 blue:0.09 alpha:1.0];
43
+ }
44
+
45
+ - (NSColor *)activityBubbleFillColor {
46
+ return [self isDarkMode]
47
+ ? [NSColor colorWithCalibratedRed:0.15 green:0.11 blue:0.09 alpha:0.96]
48
+ : [NSColor colorWithCalibratedWhite:0.985 alpha:1.0];
49
+ }
50
+
51
+ - (NSColor *)activityBubbleFillEndColor {
52
+ return [self isDarkMode]
53
+ ? [NSColor colorWithCalibratedRed:0.20 green:0.14 blue:0.11 alpha:0.98]
54
+ : [NSColor colorWithCalibratedRed:0.995 green:0.965 blue:0.93 alpha:1.0];
55
+ }
56
+
57
+ - (NSColor *)activityBubbleBorderColor {
58
+ return [self isDarkMode]
59
+ ? [NSColor colorWithCalibratedRed:0.33 green:0.23 blue:0.17 alpha:1.0]
60
+ : [NSColor colorWithCalibratedRed:0.93 green:0.86 blue:0.79 alpha:1.0];
61
+ }
62
+
63
+ - (NSColor *)orangeBadgeFillColor {
64
+ return [NSColor colorWithCalibratedRed:0.95 green:0.55 blue:0.18 alpha:0.16];
65
+ }
66
+
67
+ - (NSColor *)pillStartColorForActive:(BOOL)active {
68
+ if ([self isDarkMode]) {
69
+ return active
70
+ ? [NSColor colorWithCalibratedRed:0.78 green:0.33 blue:0.09 alpha:0.98]
71
+ : [NSColor colorWithCalibratedRed:0.58 green:0.28 blue:0.10 alpha:0.94];
72
+ }
73
+ return active
74
+ ? [NSColor colorWithCalibratedRed:0.91 green:0.46 blue:0.10 alpha:0.98]
75
+ : [NSColor colorWithCalibratedRed:0.87 green:0.47 blue:0.16 alpha:0.92];
76
+ }
77
+
78
+ - (NSColor *)pillEndColorForActive:(BOOL)active {
79
+ if ([self isDarkMode]) {
80
+ return active
81
+ ? [NSColor colorWithCalibratedRed:1.0 green:0.58 blue:0.22 alpha:0.98]
82
+ : [NSColor colorWithCalibratedRed:0.84 green:0.45 blue:0.16 alpha:0.94];
83
+ }
84
+ return active
85
+ ? [NSColor colorWithCalibratedRed:0.99 green:0.66 blue:0.26 alpha:0.98]
86
+ : [NSColor colorWithCalibratedRed:0.95 green:0.59 blue:0.20 alpha:0.92];
87
+ }
88
+
89
+ - (NSColor *)orangeBadgeBorderColor {
90
+ return [self isDarkMode]
91
+ ? [NSColor colorWithCalibratedRed:1.0 green:0.70 blue:0.40 alpha:0.72]
92
+ : [NSColor colorWithCalibratedRed:0.96 green:0.54 blue:0.15 alpha:0.55];
93
+ }
94
+
95
+ - (NSColor *)orangeBadgeTextColor {
96
+ return [self isDarkMode]
97
+ ? [NSColor colorWithCalibratedRed:1.0 green:0.82 blue:0.63 alpha:1.0]
98
+ : [NSColor colorWithCalibratedRed:0.78 green:0.35 blue:0.04 alpha:1.0];
99
+ }
100
+
101
+ - (NSColor *)activityBubbleTextColor {
102
+ return [self isDarkMode]
103
+ ? [NSColor colorWithCalibratedRed:0.94 green:0.89 blue:0.84 alpha:1.0]
104
+ : [NSColor colorWithCalibratedRed:0.20 green:0.13 blue:0.09 alpha:1.0];
105
+ }
106
+
107
+ - (void)installWaveAnimationOnLayer:(CALayer *)layer key:(NSString *)key amplitude:(CGFloat)amplitude duration:(CFTimeInterval)duration {
108
+ if (layer == nil) return;
109
+ CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.translation.x"];
110
+ animation.fromValue = @(-amplitude);
111
+ animation.toValue = @(amplitude);
112
+ animation.duration = duration;
113
+ animation.repeatCount = HUGE_VALF;
114
+ animation.autoreverses = YES;
115
+ animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
116
+ [layer addAnimation:animation forKey:key];
117
+ }
118
+
119
+ - (void)ensureStatusLayers {
120
+ NSStatusBarButton *button = self.statusItem.button;
121
+ if (button == nil) return;
122
+
123
+ button.wantsLayer = YES;
124
+ button.layer.masksToBounds = NO;
125
+
126
+ if (self.statusGlowLayer == nil) {
127
+ self.statusGlowLayer = [CALayer layer];
128
+ self.statusGlowLayer.cornerRadius = 12.0;
129
+ self.statusGlowLayer.shadowOpacity = 0.34f;
130
+ self.statusGlowLayer.shadowRadius = 10.0f;
131
+ self.statusGlowLayer.shadowOffset = CGSizeMake(0, 0);
132
+ self.statusGlowLayer.zPosition = -20.0;
133
+ [button.layer insertSublayer:self.statusGlowLayer atIndex:0];
134
+ }
135
+
136
+ if (self.statusPillLayer == nil) {
137
+ self.statusPillLayer = [CAGradientLayer layer];
138
+ self.statusPillLayer.cornerRadius = 12.0;
139
+ self.statusPillLayer.borderWidth = 1.0;
140
+ self.statusPillLayer.startPoint = CGPointMake(0.0, 0.5);
141
+ self.statusPillLayer.endPoint = CGPointMake(1.0, 0.5);
142
+ self.statusPillLayer.zPosition = -10.0;
143
+ [button.layer insertSublayer:self.statusPillLayer above:self.statusGlowLayer];
144
+ }
145
+
146
+ if (self.statusShineLayer == nil) {
147
+ self.statusShineLayer = [CAGradientLayer layer];
148
+ self.statusShineLayer.startPoint = CGPointMake(0.0, 0.5);
149
+ self.statusShineLayer.endPoint = CGPointMake(1.0, 0.5);
150
+ self.statusShineLayer.colors = @[
151
+ (__bridge id)[NSColor colorWithCalibratedWhite:1.0 alpha:0.0].CGColor,
152
+ (__bridge id)[NSColor colorWithCalibratedWhite:1.0 alpha:0.32].CGColor,
153
+ (__bridge id)[NSColor colorWithCalibratedWhite:1.0 alpha:0.0].CGColor
154
+ ];
155
+ self.statusShineLayer.locations = @[@0.0, @0.45, @0.9];
156
+ self.statusShineLayer.zPosition = 1.0;
157
+ [self.statusPillLayer addSublayer:self.statusShineLayer];
158
+ [self installWaveAnimationOnLayer:self.statusShineLayer key:@"opencoreShimmer" amplitude:36.0 duration:2.2];
159
+ }
160
+
161
+ if ([self.statusGlowLayer animationForKey:@"opencoreGlowPulse"] == nil) {
162
+ CABasicAnimation *pulse = [CABasicAnimation animationWithKeyPath:@"shadowRadius"];
163
+ pulse.fromValue = @(8.0);
164
+ pulse.toValue = @(12.5);
165
+ pulse.duration = 1.8;
166
+ pulse.repeatCount = HUGE_VALF;
167
+ pulse.autoreverses = YES;
168
+ pulse.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
169
+ [self.statusGlowLayer addAnimation:pulse forKey:@"opencoreGlowPulse"];
170
+ }
171
+ }
172
+
173
+ - (void)configureStatusTracking {
174
+ NSStatusBarButton *button = self.statusItem.button;
175
+ if (button == nil) return;
176
+ if (self.statusTrackingArea != nil) {
177
+ [button removeTrackingArea:self.statusTrackingArea];
178
+ }
179
+ self.statusTrackingArea = [[NSTrackingArea alloc] initWithRect:button.bounds
180
+ options:NSTrackingMouseEnteredAndExited | NSTrackingActiveAlways | NSTrackingInVisibleRect
181
+ owner:self
182
+ userInfo:nil];
183
+ [button addTrackingArea:self.statusTrackingArea];
184
+ }
185
+
186
+ - (void)mouseEntered:(NSEvent *)event {
187
+ (void)event;
188
+ self.hoverActive = YES;
189
+ if (self.lastState != nil) {
190
+ [self applyState:self.lastState];
191
+ }
192
+ }
193
+
194
+ - (void)mouseExited:(NSEvent *)event {
195
+ (void)event;
196
+ self.hoverActive = NO;
197
+ if (self.lastState != nil) {
198
+ [self applyState:self.lastState];
199
+ }
200
+ }
201
+
202
+ - (void)updateStatusPillForTitle:(NSString *)title active:(BOOL)active {
203
+ NSStatusBarButton *button = self.statusItem.button;
204
+ if (button == nil) return;
205
+
206
+ [self ensureStatusLayers];
207
+
208
+ NSDictionary *attributes = @{
209
+ NSFontAttributeName: [NSFont systemFontOfSize:13 weight:NSFontWeightSemibold]
210
+ };
211
+ CGFloat textWidth = ceil([title sizeWithAttributes:attributes].width);
212
+ CGFloat iconWidth = button.image != nil ? 18.0 : 0.0;
213
+ CGFloat totalWidth = textWidth + iconWidth + 22.0;
214
+ CGFloat width = MAX(122.0, totalWidth);
215
+ CGFloat height = 24.0;
216
+ CGFloat x = floor((NSWidth(button.bounds) - width) / 2.0);
217
+ CGFloat y = floor((NSHeight(button.bounds) - height) / 2.0) - 1.0;
218
+ CGRect frame = CGRectMake(x, y, width, height);
219
+
220
+ self.statusGlowLayer.frame = CGRectInset(frame, -2.0, -1.0);
221
+ self.statusGlowLayer.backgroundColor = [NSColor clearColor].CGColor;
222
+ self.statusGlowLayer.shadowColor = [[self pillEndColorForActive:active] colorWithAlphaComponent:(active ? 0.92 : 0.66)].CGColor;
223
+ self.statusGlowLayer.shadowRadius = active ? (self.hoverActive ? 18.0f : 12.0f) : (self.hoverActive ? 13.0f : 8.0f);
224
+ self.statusGlowLayer.shadowOpacity = active ? (self.hoverActive ? 0.68f : 0.42f) : (self.hoverActive ? 0.46f : 0.26f);
225
+
226
+ self.statusPillLayer.frame = frame;
227
+ self.statusPillLayer.colors = @[
228
+ (__bridge id)[[self pillStartColorForActive:active] blendedColorWithFraction:(self.hoverActive ? 0.16 : 0.0) ofColor:[NSColor whiteColor]].CGColor,
229
+ (__bridge id)[[self pillEndColorForActive:active] blendedColorWithFraction:(self.hoverActive ? 0.12 : 0.0) ofColor:[NSColor whiteColor]].CGColor
230
+ ];
231
+ self.statusPillLayer.borderColor = [[self.orangeBadgeBorderColor colorWithAlphaComponent:(active ? (self.hoverActive ? 1.0 : 0.85) : (self.hoverActive ? 0.86 : 0.60))] CGColor];
232
+
233
+ self.statusShineLayer.frame = CGRectMake(8.0, 2.0, width - 16.0, height * 0.48);
234
+ self.statusShineLayer.cornerRadius = 9.0;
235
+ self.statusShineLayer.opacity = active ? (self.hoverActive ? 1.0f : 0.95f) : (self.hoverActive ? 0.84f : 0.62f);
236
+ }
237
+
238
+ - (void)applyStatusButtonTitle:(NSString *)title active:(BOOL)active {
239
+ NSStatusBarButton *button = self.statusItem.button;
240
+ if (button == nil) return;
241
+
242
+ NSDictionary *attributes = @{
243
+ NSFontAttributeName: [NSFont systemFontOfSize:13 weight:NSFontWeightSemibold],
244
+ NSForegroundColorAttributeName: [NSColor colorWithCalibratedWhite:1.0 alpha:0.98],
245
+ NSBaselineOffsetAttributeName: @0.5
246
+ };
247
+ button.attributedTitle = [[NSAttributedString alloc] initWithString:title attributes:attributes];
248
+ button.title = title;
249
+ button.imagePosition = NSImageLeft;
250
+ button.imageScaling = NSImageScaleProportionallyDown;
251
+ button.contentTintColor = [NSColor colorWithCalibratedWhite:1.0 alpha:0.96];
252
+ button.layer.zPosition = 10.0;
253
+ if ([button respondsToSelector:@selector(setImageHugsTitle:)]) {
254
+ button.imageHugsTitle = YES;
255
+ }
256
+ [self updateStatusPillForTitle:title active:active];
257
+ }
258
+
259
+ - (NSTextField *)labelWithFrame:(NSRect)frame text:(NSString *)text font:(NSFont *)font color:(NSColor *)color alignment:(NSTextAlignment)alignment {
260
+ NSTextField *label = [[NSTextField alloc] initWithFrame:frame];
261
+ label.stringValue = text ?: @"";
262
+ label.editable = NO;
263
+ label.bordered = NO;
264
+ label.bezeled = NO;
265
+ label.drawsBackground = NO;
266
+ label.selectable = NO;
267
+ label.alignment = alignment;
268
+ label.font = font;
269
+ label.textColor = color;
270
+ return label;
271
+ }
272
+
273
+ - (NSView *)activityRowViewWithText:(NSString *)text width:(CGFloat)width {
274
+ NSView *container = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, width, 42)];
275
+
276
+ CGFloat bubbleWidth = width - 34;
277
+ NSView *bubble = [[NSView alloc] initWithFrame:NSMakeRect(floor((width - bubbleWidth) / 2.0), 5, bubbleWidth, 32)];
278
+ bubble.wantsLayer = YES;
279
+ bubble.layer.cornerRadius = 12.0;
280
+ CAGradientLayer *bubbleGradient = [CAGradientLayer layer];
281
+ bubbleGradient.frame = bubble.bounds;
282
+ bubbleGradient.cornerRadius = 12.0;
283
+ bubbleGradient.startPoint = CGPointMake(0.0, 0.5);
284
+ bubbleGradient.endPoint = CGPointMake(1.0, 0.5);
285
+ bubbleGradient.colors = @[
286
+ (__bridge id)[self.activityBubbleFillColor CGColor],
287
+ (__bridge id)[self.activityBubbleFillEndColor CGColor]
288
+ ];
289
+ [bubble.layer addSublayer:bubbleGradient];
290
+ bubble.layer.borderWidth = 1.0;
291
+ bubble.layer.borderColor = [self.activityBubbleBorderColor CGColor];
292
+ bubble.layer.shadowColor = [[self pillEndColorForActive:YES] colorWithAlphaComponent:([self isDarkMode] ? 0.22 : 0.18)].CGColor;
293
+ bubble.layer.shadowOpacity = 1.0f;
294
+ bubble.layer.shadowRadius = 6.0f;
295
+ bubble.layer.shadowOffset = CGSizeMake(0, -1);
296
+ [container addSubview:bubble];
297
+
298
+ CGFloat dotSize = 6.0;
299
+ CGFloat spacing = 8.0;
300
+ CGFloat contentHeight = 16.0;
301
+ NSDictionary *textAttributes = @{
302
+ NSFontAttributeName: [NSFont systemFontOfSize:11 weight:NSFontWeightMedium]
303
+ };
304
+ CGFloat textWidth = MIN(ceil([text sizeWithAttributes:textAttributes].width), NSWidth(bubble.bounds) - 42.0);
305
+ CGFloat groupWidth = dotSize + spacing + textWidth;
306
+ CGFloat groupStartX = NSMinX(bubble.frame) + floor((NSWidth(bubble.bounds) - groupWidth) / 2.0);
307
+ CGFloat groupStartY = NSMinY(bubble.frame) + floor((NSHeight(bubble.bounds) - contentHeight) / 2.0);
308
+
309
+ NSView *group = [[NSView alloc] initWithFrame:NSMakeRect(groupStartX, groupStartY, groupWidth, contentHeight)];
310
+ [container addSubview:group];
311
+
312
+ NSView *dot = [[NSView alloc] initWithFrame:NSMakeRect(0, floor((contentHeight - dotSize) / 2.0), dotSize, dotSize)];
313
+ dot.wantsLayer = YES;
314
+ dot.layer.cornerRadius = 3.0;
315
+ dot.layer.backgroundColor = [self pillEndColorForActive:YES].CGColor;
316
+ [group addSubview:dot];
317
+
318
+ NSTextField *label = [self labelWithFrame:NSMakeRect(NSMaxX(dot.frame) + spacing, floor((contentHeight - 16.0) / 2.0), textWidth, 16.0)
319
+ text:text
320
+ font:[NSFont systemFontOfSize:11 weight:NSFontWeightMedium]
321
+ color:self.activityBubbleTextColor
322
+ alignment:NSTextAlignmentLeft];
323
+ label.cell.controlSize = NSControlSizeSmall;
324
+ label.lineBreakMode = NSLineBreakByTruncatingTail;
325
+ label.cell.wraps = NO;
326
+ label.cell.scrollable = NO;
327
+ label.cell.usesSingleLineMode = YES;
328
+ [group addSubview:label];
329
+
330
+ return container;
331
+ }
332
+
333
+ - (void)applicationDidFinishLaunching:(NSNotification *)notification {
334
+ (void)notification;
335
+ NSString *statePath = NSProcessInfo.processInfo.environment[@"OPENCORE_INDICATOR_STATE_PATH"];
336
+ if (statePath.length == 0) {
337
+ [NSApp terminate:nil];
338
+ return;
339
+ }
340
+
341
+ self.statePath = statePath;
342
+ self.statusItem = [[NSStatusBar systemStatusBar] statusItemWithLength:NSVariableStatusItemLength];
343
+ self.menu = [[NSMenu alloc] initWithTitle:@"OpenCore"];
344
+ self.statusItem.menu = self.menu;
345
+
346
+ NSStatusBarButton *button = self.statusItem.button;
347
+ if (button != nil) {
348
+ if (@available(macOS 11.0, *)) {
349
+ NSImage *image = [NSImage imageWithSystemSymbolName:@"laptopcomputer" accessibilityDescription:@"OpenCore"];
350
+ if (image != nil) {
351
+ image.size = NSMakeSize(14, 14);
352
+ button.image = image;
353
+ button.imagePosition = NSImageLeft;
354
+ }
355
+ }
356
+ [self configureStatusTracking];
357
+ [self applyStatusButtonTitle:@"OpenCore Idle" active:NO];
358
+ }
359
+
360
+ [self refreshState:nil];
361
+ self.timer = [NSTimer scheduledTimerWithTimeInterval:0.35
362
+ target:self
363
+ selector:@selector(refreshState:)
364
+ userInfo:nil
365
+ repeats:YES];
366
+ }
367
+
368
+ - (void)applicationWillTerminate:(NSNotification *)notification {
369
+ (void)notification;
370
+ [self.timer invalidate];
371
+ self.timer = nil;
372
+ if (self.statusItem != nil) {
373
+ [[NSStatusBar systemStatusBar] removeStatusItem:self.statusItem];
374
+ self.statusItem = nil;
375
+ }
376
+ }
377
+
378
+ - (NSDictionary *)fallbackState {
379
+ return @{
380
+ @"running": @YES,
381
+ @"active": @NO,
382
+ @"message": @"OpenCore is idle",
383
+ @"events": @[]
384
+ };
385
+ }
386
+
387
+ - (NSDictionary *)loadState {
388
+ NSData *data = [NSData dataWithContentsOfFile:self.statePath];
389
+ if (data == nil) {
390
+ return @{ @"running": @NO, @"active": @NO, @"message": @"OpenCore has stopped", @"events": @[] };
391
+ }
392
+
393
+ NSError *error = nil;
394
+ id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
395
+ if (error != nil || ![json isKindOfClass:[NSDictionary class]]) {
396
+ return [self fallbackState];
397
+ }
398
+ return (NSDictionary *)json;
399
+ }
400
+
401
+ - (NSMenuItem *)disabledItemWithTitle:(NSString *)title {
402
+ NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title action:nil keyEquivalent:@""];
403
+ item.enabled = NO;
404
+ return item;
405
+ }
406
+
407
+ - (NSString *)truncate:(NSString *)value limit:(NSUInteger)limit {
408
+ NSString *text = [[value ?: @"" stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] copy];
409
+ if (text.length <= limit) {
410
+ return text;
411
+ }
412
+ if (limit == 0) {
413
+ return @"";
414
+ }
415
+ return [[text substringToIndex:(limit - 1)] stringByAppendingString:@"…"];
416
+ }
417
+
418
+ - (void)rebuildMenuForState:(NSDictionary *)state {
419
+ [self.menu removeAllItems];
420
+ if (@available(macOS 10.14, *)) {
421
+ self.menu.appearance = [NSAppearance appearanceNamed:([self isDarkMode] ? NSAppearanceNameDarkAqua : NSAppearanceNameAqua)];
422
+ }
423
+
424
+ BOOL active = [state[@"active"] boolValue];
425
+ NSString *title = active ? @"OpenCore Active" : @"OpenCore Idle";
426
+ NSString *message = [self truncate:(state[@"message"] ?: (active ? @"OpenCore is working" : @"OpenCore is idle")) limit:88];
427
+
428
+ NSMenuItem *header = [self disabledItemWithTitle:title];
429
+ header.attributedTitle = [[NSAttributedString alloc] initWithString:title attributes:@{
430
+ NSFontAttributeName: [NSFont systemFontOfSize:13 weight:NSFontWeightSemibold],
431
+ NSForegroundColorAttributeName: [self menuHeaderTextColor]
432
+ }];
433
+ [self.menu addItem:header];
434
+ [self.menu addItem:[NSMenuItem separatorItem]];
435
+ NSMenuItem *messageItem = [self disabledItemWithTitle:message.length ? message : @"OpenCore is idle"];
436
+ messageItem.attributedTitle = [[NSAttributedString alloc] initWithString:(message.length ? message : @"OpenCore is idle") attributes:@{
437
+ NSFontAttributeName: [NSFont systemFontOfSize:11 weight:NSFontWeightRegular],
438
+ NSForegroundColorAttributeName: [self menuBodyTextColor]
439
+ }];
440
+ [self.menu addItem:messageItem];
441
+
442
+ NSArray *events = [state[@"events"] isKindOfClass:[NSArray class]] ? state[@"events"] : @[];
443
+ if (events.count > 0) {
444
+ [self.menu addItem:[NSMenuItem separatorItem]];
445
+ NSMenuItem *feedHeader = [self disabledItemWithTitle:@"Live Activity"];
446
+ feedHeader.attributedTitle = [[NSAttributedString alloc] initWithString:@"Live Activity" attributes:@{
447
+ NSFontAttributeName: [NSFont systemFontOfSize:11 weight:NSFontWeightSemibold],
448
+ NSForegroundColorAttributeName: [self menuAccentTextColor]
449
+ }];
450
+ [self.menu addItem:feedHeader];
451
+ NSUInteger start = events.count > 8 ? events.count - 8 : 0;
452
+ for (NSUInteger i = start; i < events.count; i += 1) {
453
+ NSString *line = [events[i] isKindOfClass:[NSString class]] ? events[i] : @"";
454
+ NSMenuItem *rowItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""];
455
+ rowItem.enabled = NO;
456
+ rowItem.view = [self activityRowViewWithText:[self truncate:line limit:96] width:300];
457
+ [self.menu addItem:rowItem];
458
+ }
459
+ }
460
+ }
461
+
462
+ - (void)applyState:(NSDictionary *)state {
463
+ BOOL active = [state[@"active"] boolValue];
464
+ NSString *title = active ? @"OpenCore Active" : @"OpenCore Idle";
465
+ [self applyStatusButtonTitle:title active:active];
466
+ [self rebuildMenuForState:state];
467
+ }
468
+
469
+ - (void)refreshState:(NSTimer *)timer {
470
+ (void)timer;
471
+ NSDictionary *state = [self loadState];
472
+ if ([state[@"running"] boolValue] == NO) {
473
+ [NSApp terminate:nil];
474
+ return;
475
+ }
476
+ self.lastState = state;
477
+ [self applyState:state];
478
+ }
479
+
480
+ @end
481
+
482
+ int main(int argc, const char *argv[]) {
483
+ (void)argc;
484
+ (void)argv;
485
+ @autoreleasepool {
486
+ NSApplication *app = [NSApplication sharedApplication];
487
+ OpenCoreIndicatorAppDelegate *delegate = [OpenCoreIndicatorAppDelegate new];
488
+ app.delegate = delegate;
489
+ [app setActivationPolicy:NSApplicationActivationPolicyAccessory];
490
+ [app run];
491
+ }
492
+ return 0;
493
+ }
@@ -0,0 +1,7 @@
1
+ # OpenCore Computer Profile
2
+
3
+ ## Machine Snapshot
4
+ - Pending first machine scan.
5
+
6
+ ## Learned Facts
7
+ - None yet.
@@ -66,6 +66,8 @@
66
66
  - Verify write destination before saving.
67
67
  - Avoid data loss conditions.
68
68
  - Prefer append or snapshot strategies when appropriate.
69
+ - Keep `~/.opencore` files and directories private to the local user where possible.
70
+ - Treat `computer-profile.md` as durable machine-state inventory, not a secret dump.
69
71
 
70
72
  ## Privacy Rules
71
73
  - Keep user data local unless user requests otherwise.
@@ -74,6 +76,7 @@
74
76
  - Do not include secrets in chat responses.
75
77
  - Do not include secrets in dashboard broadcasts.
76
78
  - Minimize retention of sensitive material.
79
+ - Do not store raw tokens, passwords, or secrets in `computer-profile.md`.
77
80
 
78
81
  ## Platform Rules
79
82
  - Assume macOS conventions only.
@@ -91,6 +94,7 @@
91
94
  - Validate outcomes where possible.
92
95
  - Prefer cron-backed scheduling for future-time or recurring tasks when a cron expression can represent the request.
93
96
  - For long-running tasks, keep only one active heartbeat step at a time and preserve durable progress in memory.md.
97
+ - For local shell work, reject dangerous commands instead of trying to execute around them.
94
98
 
95
99
  ## Chat and Control Plane Rules
96
100
  - Terminal and dashboard are equivalent user channels.
@@ -13,6 +13,7 @@
13
13
  08.1 For future-time or recurring requests, Manager should prefer cron-backed scheduling when possible.
14
14
  09. Manager should keep responses concise and operational.
15
15
  10. Manager should follow soul.md, guidelines.md, instructions.md, memory.md, and config context.
16
+ 10.05 Manager should also use computer-profile.md for durable machine facts such as installed apps, default browser, workspace, hardware profile, and permission-related environment facts.
16
17
  10.1 Manager should inspect and follow installed OpenCore skills in ~/.opencore/skills when relevant.
17
18
  10.2 Built-in skills for skill creation, continuous operations, and internal tool building should be treated as always available if installed.
18
19
  10.3 The credential-operator skill should be treated as a built-in subsystem for login, sign-up, password reuse, verification handling, and saving credentials locally.
@@ -44,6 +45,7 @@
44
45
  33. Keep chat history synchronized across channels.
45
46
  34. Keep screenshots local and referenceable.
46
47
  35. Link screenshot paths to memory when useful.
48
+ 35.05 Keep computer-profile.md updated with durable machine facts learned during tasks, but never store raw secrets there.
47
49
  35.1 Manager may create, inspect, and remove cron-backed scheduled tasks.
48
50
  35.2 For multi-day or multi-week work, Manager should break the task into one active heartbeat step plus ordered upcoming steps.
49
51
  35.3 After a long-running step completes, Manager should refresh heartbeat.md so the next incomplete step becomes active instead of restarting the whole task.
@@ -1,140 +0,0 @@
1
- ObjC.import("AppKit");
2
- ObjC.import("Foundation");
3
-
4
- var env = $.NSProcessInfo.processInfo.environment;
5
- var statePathValue = env.objectForKey("OPENCORE_INDICATOR_STATE_PATH");
6
- if (!statePathValue) {
7
- throw new Error("Missing OPENCORE_INDICATOR_STATE_PATH");
8
- }
9
- var statePath = ObjC.unwrap(statePathValue);
10
-
11
- var width = 204;
12
- var height = 38;
13
- var app = $.NSApplication.sharedApplication;
14
- app.setActivationPolicy($.NSApplicationActivationPolicyAccessory);
15
-
16
- var statusItem = $.NSStatusBar.systemStatusBar.statusItemWithLength($.NSVariableStatusItemLength);
17
- statusItem.button.title = "OpenCore Idle";
18
-
19
- var styleMask = $.NSWindowStyleMaskBorderless | $.NSWindowStyleMaskNonactivatingPanel;
20
- var panel = $.NSPanel.alloc.initWithContentRectStyleMaskBackingDefer(
21
- $.NSMakeRect(0, 0, width, height),
22
- styleMask,
23
- $.NSBackingStoreBuffered,
24
- false
25
- );
26
- panel.floatingPanel = true;
27
- panel.level = $.NSStatusWindowLevel;
28
- panel.hidesOnDeactivate = false;
29
- panel.becomesKeyOnlyIfNeeded = true;
30
- panel.ignoresMouseEvents = true;
31
- panel.opaque = false;
32
- panel.backgroundColor = $.NSColor.clearColor;
33
- panel.hasShadow = false;
34
- panel.collectionBehavior =
35
- $.NSWindowCollectionBehaviorCanJoinAllSpaces |
36
- $.NSWindowCollectionBehaviorFullScreenAuxiliary |
37
- $.NSWindowCollectionBehaviorStationary;
38
-
39
- var content = $.NSView.alloc.initWithFrame($.NSMakeRect(0, 0, width, height));
40
- content.wantsLayer = true;
41
- content.layer.backgroundColor = $.NSColor.clearColor;
42
-
43
- var glow = $.NSView.alloc.initWithFrame($.NSMakeRect(10, 5, width - 20, height - 10));
44
- glow.wantsLayer = true;
45
- glow.layer.cornerRadius = (height - 10) / 2;
46
- glow.layer.backgroundColor = $.NSColor.colorWithCalibratedRedGreenBlueAlpha(1.0, 0.62, 0.18, 0.16).CGColor;
47
- glow.layer.shadowColor = $.NSColor.colorWithCalibratedRedGreenBlueAlpha(1.0, 0.55, 0.14, 0.98).CGColor;
48
- glow.layer.shadowRadius = 16;
49
- glow.layer.shadowOpacity = 0.95;
50
- glow.layer.shadowOffset = $.CGSizeMake(0, 0);
51
- content.addSubview(glow);
52
-
53
- var capsule = $.NSView.alloc.initWithFrame($.NSMakeRect(8, 4, width - 16, height - 8));
54
- capsule.wantsLayer = true;
55
- capsule.layer.cornerRadius = (height - 8) / 2;
56
- capsule.layer.borderWidth = 1.4;
57
- capsule.layer.borderColor = $.NSColor.colorWithCalibratedRedGreenBlueAlpha(0.96, 0.58, 0.22, 0.95).CGColor;
58
- capsule.layer.backgroundColor = $.NSColor.colorWithCalibratedRedGreenBlueAlpha(1.0, 0.80, 0.60, 0.52).CGColor;
59
- content.addSubview(capsule);
60
-
61
- function makeLabel(text, x, y, w, h, fontSize, color) {
62
- var label = $.NSTextField.alloc.initWithFrame($.NSMakeRect(x, y, w, h));
63
- var cell = $.NSTextFieldCell.alloc.initTextCell(text);
64
- label.stringValue = text;
65
- label.editable = false;
66
- label.bordered = false;
67
- label.drawsBackground = false;
68
- label.selectable = false;
69
- label.bezeled = false;
70
- label.font = $.NSFont.boldSystemFontOfSize(fontSize);
71
- label.textColor = color;
72
- label.alignment = $.NSTextAlignmentCenter;
73
- cell.wraps = false;
74
- cell.scrollable = false;
75
- cell.usesSingleLineMode = true;
76
- cell.lineBreakMode = $.NSLineBreakByClipping;
77
- cell.alignment = $.NSTextAlignmentCenter;
78
- label.cell = cell;
79
- return label;
80
- }
81
-
82
- var title = makeLabel(
83
- "OpenCore Active",
84
- 14,
85
- 7,
86
- width - 28,
87
- 24,
88
- 13,
89
- $.NSColor.colorWithCalibratedRedGreenBlueAlpha(0.42, 0.20, 0.02, 0.98)
90
- );
91
- content.addSubview(title);
92
-
93
- panel.contentView = content;
94
-
95
- function positionPanel() {
96
- var screen = $.NSScreen.mainScreen;
97
- if (!screen) return;
98
- var visible = screen.visibleFrame;
99
- var x = visible.origin.x + ((visible.size.width - width) / 2);
100
- var y = visible.origin.y + visible.size.height - height - 10;
101
- panel.setFrameOrigin($.NSMakePoint(x, y));
102
- }
103
-
104
- function readState() {
105
- try {
106
- var fileManager = $.NSFileManager.defaultManager;
107
- if (!fileManager.fileExistsAtPath($(statePath))) {
108
- return { running: false, active: false, message: "OpenCore has stopped" };
109
- }
110
- var data = $.NSData.dataWithContentsOfFile($(statePath));
111
- if (!data) {
112
- return { running: true, active: false, message: "OpenCore is idle" };
113
- }
114
- var text = ObjC.unwrap($.NSString.alloc.initWithDataEncoding(data, $.NSUTF8StringEncoding));
115
- return JSON.parse(text);
116
- } catch (_error) {
117
- return { running: true, active: false, message: "OpenCore is idle" };
118
- }
119
- }
120
-
121
- function applyState(state) {
122
- var active = !!state.active;
123
- statusItem.button.title = active ? "OpenCore Active" : "OpenCore Idle";
124
- title.stringValue = active ? "OpenCore Active" : "OpenCore Idle";
125
- if (active) {
126
- positionPanel();
127
- panel.makeKeyAndOrderFront(null);
128
- } else {
129
- panel.orderOut(null);
130
- }
131
- }
132
-
133
- while (true) {
134
- var state = readState();
135
- if (!state.running) break;
136
- applyState(state);
137
- $.NSRunLoop.currentRunLoop.runUntilDate($.NSDate.dateWithTimeIntervalSinceNow(0.35));
138
- }
139
-
140
- panel.orderOut(null);