diff --git a/touchbar-macOS/build.sh b/touchbar-macOS/build.sh
new file mode 100644
index 0000000..69690c5
--- /dev/null
+++ b/touchbar-macOS/build.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+APP_NAME="GoogleBallsTouchBar.app"
+EXECUTABLE="GoogleBallsTouchBar"
+APP_DIR="${APP_NAME}/Contents"
+
+mkdir -p "${APP_DIR}/MacOS"
+
+clang \
+ -fobjc-arc \
+ -ObjC \
+ -framework Cocoa \
+ -o "${APP_DIR}/MacOS/${EXECUTABLE}" \
+ main.m
+
+cat > "${APP_DIR}/Info.plist" <<'PLIST'
+
+
+
+
+ CFBundleExecutable
+ GoogleBallsTouchBar
+ CFBundleIdentifier
+ app.googleballs.touchbar.macos
+ CFBundleName
+ Google Balls Touch Bar
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ 1.0
+ CFBundleVersion
+ 1
+ LSMinimumSystemVersion
+ 10.12.2
+ NSHighResolutionCapable
+
+
+
+PLIST
+
+echo "Built ${APP_NAME}"
diff --git a/touchbar-macOS/main.m b/touchbar-macOS/main.m
new file mode 100644
index 0000000..9cb97ad
--- /dev/null
+++ b/touchbar-macOS/main.m
@@ -0,0 +1,501 @@
+#import
+#import
+#import
+#import
+
+static NSTouchBarItemIdentifier const CustomTouchBarItemIdentifier = @"dev.codex.touchbar.fullscreen.item";
+static NSTouchBarItemIdentifier const CustomTrayItemIdentifier = @"dev.codex.touchbar.fullscreen.tray";
+
+typedef struct {
+ CGFloat x;
+ CGFloat y;
+} GBVector;
+
+typedef struct {
+ CGFloat r;
+ CGFloat g;
+ CGFloat b;
+} GBColor;
+
+typedef struct {
+ GBColor color;
+ GBVector pos;
+ GBVector originalPos;
+ GBVector velocity;
+ GBVector targetPos;
+ CGFloat radius;
+ CGFloat baseRadius;
+} GBBall;
+
+typedef struct {
+ CGFloat x;
+ CGFloat y;
+ CGFloat radius;
+ const char *hex;
+} GBBallSeed;
+
+static NSInteger GBHexValue(char value) {
+ if (value >= '0' && value <= '9') { return value - '0'; }
+ if (value >= 'a' && value <= 'f') { return value - 'a' + 10; }
+ if (value >= 'A' && value <= 'F') { return value - 'A' + 10; }
+ return 15;
+}
+
+static GBColor GBColorFromHex(const char *hex) {
+ if (!hex || strlen(hex) < 7 || hex[0] != '#') {
+ return (GBColor){1.0, 1.0, 1.0};
+ }
+ CGFloat r = (CGFloat)(GBHexValue(hex[1]) * 16 + GBHexValue(hex[2])) / 255.0;
+ CGFloat g = (CGFloat)(GBHexValue(hex[3]) * 16 + GBHexValue(hex[4])) / 255.0;
+ CGFloat b = (CGFloat)(GBHexValue(hex[5]) * 16 + GBHexValue(hex[6])) / 255.0;
+ return (GBColor){r, g, b};
+}
+
+static GBBall GBBallMake(CGFloat x, CGFloat y, CGFloat radius, const char *hex) {
+ GBVector point = {x, y};
+ return (GBBall){
+ .color = GBColorFromHex(hex),
+ .pos = point,
+ .originalPos = point,
+ .velocity = {0.0, 0.0},
+ .targetPos = point,
+ .radius = radius,
+ .baseRadius = radius,
+ };
+}
+
+@interface FullWidthTouchBarView : NSView
+@property(nonatomic) GBBall *balls;
+@property(nonatomic) NSUInteger ballCount;
+@property(nonatomic) CGFloat touchX;
+@property(nonatomic) CGFloat touchY;
+@property(nonatomic) BOOL touchActive;
+@property(nonatomic) BOOL darkMode;
+@property(nonatomic) CGFloat layoutWidth;
+@property(nonatomic) CGFloat layoutHeight;
+@property(nonatomic) CGFloat calibrationXOffset;
+@end
+
+@implementation FullWidthTouchBarView
+
+- (instancetype)initWithFrame:(NSRect)frame {
+ self = [super initWithFrame:frame];
+ if (!self) { return nil; }
+ self.wantsLayer = YES;
+ self.allowedTouchTypes = NSTouchTypeMaskDirect | NSTouchTypeMaskIndirect;
+ self.wantsRestingTouches = YES;
+ self.touchX = -1;
+ self.touchY = -1;
+ self.calibrationXOffset = -55.0;
+ [self setFrameSize:NSMakeSize(2000, 30)];
+ [NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0
+ target:self
+ selector:@selector(tick:)
+ userInfo:nil
+ repeats:YES];
+ return self;
+}
+
+- (BOOL)isFlipped {
+ return YES;
+}
+
+- (NSSize)intrinsicContentSize {
+ return NSMakeSize(2000, 30);
+}
+
+- (BOOL)acceptsFirstResponder {
+ return YES;
+}
+
+- (BOOL)acceptsFirstMouse:(NSEvent *)event {
+ return YES;
+}
+
+- (void)dealloc {
+ free(self.balls);
+}
+
+- (void)setCalibrationXOffset:(CGFloat)calibrationXOffset {
+ _calibrationXOffset = calibrationXOffset;
+ self.layoutWidth = 0.0;
+ self.layoutHeight = 0.0;
+ self.needsDisplay = YES;
+}
+
+- (void)tick:(NSTimer *)timer {
+ [self updateBalls];
+ self.needsDisplay = YES;
+}
+
+- (void)mouseDown:(NSEvent *)event {
+ [self handleMouseEvent:event togglesSwitch:YES];
+}
+
+- (void)mouseDragged:(NSEvent *)event {
+ [self handleMouseEvent:event togglesSwitch:NO];
+}
+
+- (void)mouseUp:(NSEvent *)event {
+ self.touchActive = NO;
+ self.touchX = -1;
+ self.touchY = -1;
+ self.needsDisplay = YES;
+}
+
+- (void)touchesBeganWithEvent:(NSEvent *)event {
+ [self handleTouches:[event touchesMatchingPhase:NSTouchPhaseAny inView:self] togglesSwitch:YES ended:NO];
+}
+
+- (void)touchesMovedWithEvent:(NSEvent *)event {
+ [self handleTouches:[event touchesMatchingPhase:NSTouchPhaseAny inView:self] togglesSwitch:NO ended:NO];
+}
+
+- (void)touchesEndedWithEvent:(NSEvent *)event {
+ [self handleTouches:[event touchesMatchingPhase:NSTouchPhaseAny inView:self] togglesSwitch:NO ended:YES];
+}
+
+- (void)touchesCancelledWithEvent:(NSEvent *)event {
+ [self handleTouches:[event touchesMatchingPhase:NSTouchPhaseAny inView:self] togglesSwitch:NO ended:YES];
+}
+
+- (void)handleMouseEvent:(NSEvent *)event togglesSwitch:(BOOL)togglesSwitch {
+ NSPoint point = [self convertPoint:event.locationInWindow fromView:nil];
+ [self handleInputAtPoint:point togglesSwitch:togglesSwitch ended:NO];
+}
+
+- (void)handleTouches:(NSSet *)touches togglesSwitch:(BOOL)togglesSwitch ended:(BOOL)ended {
+ if (ended) {
+ self.touchActive = NO;
+ self.touchX = -1;
+ self.touchY = -1;
+ self.needsDisplay = YES;
+ return;
+ }
+
+ NSTouch *touch = touches.anyObject;
+ if (!touch) {
+ return;
+ }
+
+ NSPoint point = touch.type == NSTouchTypeDirect
+ ? [touch locationInView:self]
+ : NSMakePoint(touch.normalizedPosition.x * NSWidth(self.bounds),
+ (1.0 - touch.normalizedPosition.y) * NSHeight(self.bounds));
+ [self handleInputAtPoint:point togglesSwitch:togglesSwitch ended:NO];
+}
+
+- (void)handleInputAtPoint:(NSPoint)point togglesSwitch:(BOOL)togglesSwitch ended:(BOOL)ended {
+ if (ended) {
+ self.touchActive = NO;
+ self.touchX = -1;
+ self.touchY = -1;
+ self.needsDisplay = YES;
+ return;
+ }
+
+ self.touchX = MAX(0.0, MIN(NSWidth(self.bounds), point.x));
+ self.touchY = MAX(0.0, MIN(NSHeight(self.bounds), point.y));
+ self.touchActive = YES;
+ if (togglesSwitch && self.touchX < [self switchWidth]) {
+ self.darkMode = !self.darkMode;
+ }
+ self.needsDisplay = YES;
+}
+
+- (CGFloat)switchWidth {
+ return 58.0;
+}
+
+- (CGFloat)layoutWidthForGoogleBalls {
+ // NSTouchBar can report a very wide custom-item view while only part of it
+ // is practically visible. Keep the port laid out in a stable visible band.
+ return MIN(NSWidth(self.bounds), 1024.0);
+}
+
+- (void)ensureBallsForCurrentSize {
+ NSRect bounds = self.bounds;
+ CGFloat width = NSWidth(bounds);
+ CGFloat height = NSHeight(bounds);
+ if (width <= 1.0 || height <= 1.0) {
+ return;
+ }
+ if (self.balls && fabs(self.layoutWidth - width) < 0.5 && fabs(self.layoutHeight - height) < 0.5) {
+ return;
+ }
+
+ static const GBBallSeed seeds[] = {
+ {202.0, 78.0, 9.0, "#ed9d33"}, {348.0, 83.0, 9.0, "#d44d61"},
+ {256.0, 69.0, 9.0, "#4f7af2"}, {214.0, 59.0, 9.0, "#ef9a1e"},
+ {265.0, 36.0, 9.0, "#4976f3"}, {300.0, 78.0, 9.0, "#269230"},
+ {294.0, 59.0, 9.0, "#1f9e2c"}, {45.0, 88.0, 9.0, "#1c48dd"},
+ {268.0, 52.0, 9.0, "#2a56ea"}, {73.0, 83.0, 9.0, "#3355d8"},
+ {294.0, 6.0, 9.0, "#36b641"}, {235.0, 62.0, 9.0, "#2e5def"},
+ {353.0, 42.0, 8.0, "#d53747"}, {336.0, 52.0, 8.0, "#eb676f"},
+ {208.0, 41.0, 8.0, "#f9b125"}, {321.0, 70.0, 8.0, "#de3646"},
+ {8.0, 60.0, 8.0, "#2a59f0"}, {180.0, 81.0, 8.0, "#eb9c31"},
+ {146.0, 65.0, 8.0, "#c41731"}, {145.0, 49.0, 8.0, "#d82038"},
+ {246.0, 34.0, 8.0, "#5f8af8"}, {169.0, 69.0, 8.0, "#efa11e"},
+ {273.0, 99.0, 8.0, "#2e55e2"}, {248.0, 120.0, 8.0, "#4167e4"},
+ {294.0, 41.0, 8.0, "#0b991a"}, {267.0, 114.0, 8.0, "#4869e3"},
+ {78.0, 67.0, 8.0, "#3059e3"}, {294.0, 23.0, 8.0, "#10a11d"},
+ {117.0, 83.0, 8.0, "#cf4055"}, {137.0, 80.0, 8.0, "#cd4359"},
+ {14.0, 71.0, 8.0, "#2855ea"}, {331.0, 80.0, 8.0, "#ca273c"},
+ {25.0, 82.0, 8.0, "#2650e1"}, {233.0, 46.0, 8.0, "#4a7bf9"},
+ {73.0, 13.0, 8.0, "#3d65e7"}, {327.0, 35.0, 6.0, "#f47875"},
+ {319.0, 46.0, 6.0, "#f36764"}, {256.0, 81.0, 6.0, "#1d4eeb"},
+ {244.0, 88.0, 6.0, "#698bf1"}, {194.0, 32.0, 6.0, "#fac652"},
+ {97.0, 56.0, 6.0, "#ee5257"}, {105.0, 75.0, 6.0, "#cf2a3f"},
+ {42.0, 4.0, 6.0, "#5681f5"}, {10.0, 27.0, 6.0, "#4577f6"},
+ {166.0, 55.0, 6.0, "#f7b326"}, {266.0, 88.0, 6.0, "#2b58e8"},
+ {178.0, 34.0, 6.0, "#facb5e"}, {100.0, 65.0, 6.0, "#e02e3d"},
+ {343.0, 32.0, 6.0, "#f16d6f"}, {59.0, 5.0, 6.0, "#507bf2"},
+ {27.0, 9.0, 6.0, "#5683f7"}, {233.0, 116.0, 6.0, "#3158e2"},
+ {123.0, 32.0, 6.0, "#f0696c"}, {6.0, 38.0, 6.0, "#3769f6"},
+ {63.0, 62.0, 6.0, "#6084ef"}, {6.0, 49.0, 6.0, "#2a5cf4"},
+ {108.0, 36.0, 6.0, "#f4716e"}, {169.0, 43.0, 6.0, "#f8c247"},
+ {137.0, 37.0, 6.0, "#e74653"}, {318.0, 58.0, 6.0, "#ec4147"},
+ {226.0, 100.0, 5.0, "#4876f1"}, {101.0, 46.0, 5.0, "#ef5c5c"},
+ {226.0, 108.0, 5.0, "#2552ea"}, {17.0, 17.0, 5.0, "#4779f7"},
+ {232.0, 93.0, 5.0, "#4b78f1"},
+ };
+
+ free(self.balls);
+ self.ballCount = sizeof(seeds) / sizeof(seeds[0]);
+ self.balls = calloc(self.ballCount, sizeof(GBBall));
+ self.layoutWidth = width;
+ self.layoutHeight = height;
+
+ CGFloat originalWidth = 360.0;
+ CGFloat originalHeight = 130.0;
+ CGFloat scale = height / originalHeight;
+ CGFloat clusterWidth = originalWidth * scale;
+ CGFloat clusterHeight = originalHeight * scale;
+ CGFloat visibleWidth = [self layoutWidthForGoogleBalls];
+ CGFloat contentMinX = [self switchWidth];
+ CGFloat contentWidth = MAX(1.0, visibleWidth - contentMinX);
+ CGFloat offsetX = contentMinX + MAX(0.0, (contentWidth - clusterWidth) / 2.0) + self.calibrationXOffset;
+ CGFloat offsetY = (height - clusterHeight) / 2.0;
+
+ for (NSUInteger i = 0; i < self.ballCount; i++) {
+ CGFloat x = offsetX + seeds[i].x * scale;
+ CGFloat y = offsetY + seeds[i].y * scale;
+ CGFloat radius = MAX(1.2, seeds[i].radius * scale);
+ self.balls[i] = GBBallMake(x, y, radius, seeds[i].hex);
+ }
+}
+
+- (void)updateBalls {
+ [self ensureBallsForCurrentSize];
+ const CGFloat interactionRadius = 56.0;
+ for (NSUInteger i = 0; i < self.ballCount; i++) {
+ GBBall *ball = &self.balls[i];
+ if (self.touchActive && self.touchX >= [self switchWidth]) {
+ CGFloat dx = self.touchX - ball->pos.x;
+ CGFloat dy = self.touchY - ball->pos.y;
+ CGFloat distance = sqrt(dx * dx + dy * dy);
+ if (distance < interactionRadius) {
+ ball->targetPos.x = ball->pos.x - dx;
+ ball->targetPos.y = ball->pos.y - dy;
+ } else {
+ ball->targetPos = ball->originalPos;
+ }
+ } else {
+ ball->targetPos = ball->originalPos;
+ }
+
+ CGFloat dx = ball->targetPos.x - ball->pos.x;
+ ball->velocity.x += dx * 0.1;
+ ball->velocity.x *= 0.8;
+ ball->pos.x += ball->velocity.x;
+
+ CGFloat dy = ball->targetPos.y - ball->pos.y;
+ ball->velocity.y += dy * 0.1;
+ ball->velocity.y *= 0.8;
+ ball->pos.y += ball->velocity.y;
+
+ CGFloat ox = ball->originalPos.x - ball->pos.x;
+ CGFloat oy = ball->originalPos.y - ball->pos.y;
+ CGFloat displaced = sqrt(ox * ox + oy * oy);
+ ball->radius = MAX(1.0, ball->baseRadius * (displaced / 100.0 + 1.0));
+ }
+}
+
+- (void)drawRect:(NSRect)dirtyRect {
+ [self ensureBallsForCurrentSize];
+
+ NSRect bounds = self.bounds;
+ NSColor *background = self.darkMode ? [NSColor colorWithCalibratedWhite:0.08 alpha:1.0] : NSColor.whiteColor;
+ [background setFill];
+ NSRectFill(bounds);
+
+ [self drawSwitch];
+
+ for (NSUInteger i = 0; i < self.ballCount; i++) {
+ GBBall ball = self.balls[i];
+ [[NSColor colorWithCalibratedRed:ball.color.r green:ball.color.g blue:ball.color.b alpha:1.0] setFill];
+ NSRect circleRect = NSMakeRect(ball.pos.x - ball.radius, ball.pos.y - ball.radius, ball.radius * 2.0, ball.radius * 2.0);
+ [[NSBezierPath bezierPathWithOvalInRect:circleRect] fill];
+ }
+}
+
+- (void)drawSwitch {
+ CGFloat width = 36.0;
+ CGFloat height = 16.0;
+ CGFloat x = ([self switchWidth] - width) / 2.0;
+ CGFloat y = (NSHeight(self.bounds) - height) / 2.0;
+ NSRect pillRect = NSMakeRect(x, y, width, height);
+
+ NSColor *pillColor = self.darkMode ? [NSColor colorWithCalibratedWhite:0.30 alpha:1.0] : [NSColor colorWithCalibratedWhite:0.78 alpha:1.0];
+ [pillColor setFill];
+ [[NSBezierPath bezierPathWithRoundedRect:pillRect xRadius:height / 2.0 yRadius:height / 2.0] fill];
+
+ CGFloat knobRadius = height - 5.0;
+ CGFloat knobX = self.darkMode ? NSMaxX(pillRect) - knobRadius - 2.5 : NSMinX(pillRect) + 2.5;
+ CGFloat knobY = NSMidY(pillRect) - knobRadius / 2.0;
+ NSColor *knobColor = self.darkMode ? NSColor.whiteColor : [NSColor colorWithCalibratedWhite:0.08 alpha:1.0];
+ [knobColor setFill];
+ [[NSBezierPath bezierPathWithOvalInRect:NSMakeRect(knobX, knobY, knobRadius, knobRadius)] fill];
+}
+
+@end
+
+@interface AppDelegate : NSObject
+@property(nonatomic, strong) NSWindow *window;
+@property(nonatomic, strong) NSTouchBar *bar;
+@property(nonatomic, strong) FullWidthTouchBarView *touchView;
+@end
+
+@implementation AppDelegate
+
+- (void)applicationDidFinishLaunching:(NSNotification *)notification {
+ [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+ [self buildWindow];
+ [self buildTouchBar];
+ [self.window makeKeyAndOrderFront:nil];
+ [NSApp activateIgnoringOtherApps:YES];
+
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.35 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+ [self tryPresentFullscreenTouchBar];
+ });
+}
+
+- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
+ return YES;
+}
+
+- (void)buildWindow {
+ NSRect frame = NSMakeRect(0, 0, 460, 160);
+ self.window = [[NSWindow alloc] initWithContentRect:frame
+ styleMask:(NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable)
+ backing:NSBackingStoreBuffered
+ defer:NO];
+ self.window.title = @"Google Balls Touch Bar";
+ [self.window center];
+
+ NSTextField *label = [NSTextField labelWithString:@"Look down since I'm not putting controls here."];
+ label.font = [NSFont systemFontOfSize:14 weight:NSFontWeightMedium];
+ label.alignment = NSTextAlignmentCenter;
+ label.translatesAutoresizingMaskIntoConstraints = NO;
+
+ NSButton *quit = [NSButton buttonWithTitle:@"Quit" target:NSApp action:@selector(terminate:)];
+ quit.translatesAutoresizingMaskIntoConstraints = NO;
+
+ NSStackView *stack = [NSStackView stackViewWithViews:@[label, quit]];
+ stack.orientation = NSUserInterfaceLayoutOrientationVertical;
+ stack.alignment = NSLayoutAttributeCenterX;
+ stack.spacing = 18;
+ stack.translatesAutoresizingMaskIntoConstraints = NO;
+
+ self.window.contentView = [[NSView alloc] initWithFrame:frame];
+ [self.window.contentView addSubview:stack];
+ [NSLayoutConstraint activateConstraints:@[
+ [stack.centerXAnchor constraintEqualToAnchor:self.window.contentView.centerXAnchor],
+ [stack.centerYAnchor constraintEqualToAnchor:self.window.contentView.centerYAnchor],
+ [label.widthAnchor constraintLessThanOrEqualToAnchor:self.window.contentView.widthAnchor constant:-48],
+ ]];
+}
+
+- (void)buildTouchBar {
+ self.touchView = [[FullWidthTouchBarView alloc] initWithFrame:NSMakeRect(0, 0, 2000, 30)];
+ self.bar = [[NSTouchBar alloc] init];
+ self.bar.delegate = self;
+ self.bar.defaultItemIdentifiers = @[CustomTouchBarItemIdentifier];
+ self.bar.principalItemIdentifier = CustomTouchBarItemIdentifier;
+ self.window.touchBar = self.bar;
+}
+
+- (NSTouchBarItem *)touchBar:(NSTouchBar *)touchBar makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier {
+ if (![identifier isEqualToString:CustomTouchBarItemIdentifier]) {
+ return nil;
+ }
+
+ NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier];
+ item.customizationLabel = @"Google Balls";
+ item.visibilityPriority = NSTouchBarItemPriorityHigh;
+ item.view = self.touchView;
+ return item;
+}
+
+- (void)tryPresentFullscreenTouchBar {
+ [self hideSystemModalCloseBoxIfAvailable];
+
+ SEL selectorWithPlacement = NSSelectorFromString(@"presentSystemModalTouchBar:placement:systemTrayItemIdentifier:");
+ SEL selectorWithoutPlacement = NSSelectorFromString(@"presentSystemModalTouchBar:systemTrayItemIdentifier:");
+ SEL legacySelectorWithPlacement = NSSelectorFromString(@"presentSystemModalFunctionBar:placement:systemTrayItemIdentifier:");
+ SEL privateLegacySelectorWithPlacement = NSSelectorFromString(@"_presentSystemModalFunctionBar:placement:systemTrayItemIdentifier:");
+
+ if ([NSTouchBar respondsToSelector:selectorWithPlacement]) {
+ void (*send)(id, SEL, NSTouchBar *, NSInteger, NSString *) = (void *)objc_msgSend;
+ send(NSTouchBar.class, selectorWithPlacement, self.bar, 1, CustomTrayItemIdentifier);
+ NSLog(@"Presented fullscreen Touch Bar using %@", NSStringFromSelector(selectorWithPlacement));
+ return;
+ }
+
+ if ([NSTouchBar respondsToSelector:legacySelectorWithPlacement]) {
+ void (*send)(id, SEL, NSTouchBar *, NSInteger, NSString *) = (void *)objc_msgSend;
+ send(NSTouchBar.class, legacySelectorWithPlacement, self.bar, 1, CustomTrayItemIdentifier);
+ NSLog(@"Presented fullscreen Touch Bar using %@", NSStringFromSelector(legacySelectorWithPlacement));
+ return;
+ }
+
+ if ([NSTouchBar respondsToSelector:privateLegacySelectorWithPlacement]) {
+ void (*send)(id, SEL, NSTouchBar *, NSInteger, NSString *) = (void *)objc_msgSend;
+ send(NSTouchBar.class, privateLegacySelectorWithPlacement, self.bar, 1, CustomTrayItemIdentifier);
+ NSLog(@"Presented fullscreen Touch Bar using %@", NSStringFromSelector(privateLegacySelectorWithPlacement));
+ return;
+ }
+
+ if ([NSTouchBar respondsToSelector:selectorWithoutPlacement]) {
+ void (*send)(id, SEL, NSTouchBar *, NSString *) = (void *)objc_msgSend;
+ send(NSTouchBar.class, selectorWithoutPlacement, self.bar, CustomTrayItemIdentifier);
+ NSLog(@"Presented fullscreen Touch Bar using %@", NSStringFromSelector(selectorWithoutPlacement));
+ return;
+ }
+
+ NSLog(@"Fullscreen system modal Touch Bar selector is unavailable; using app-scoped Touch Bar.");
+}
+
+- (void)hideSystemModalCloseBoxIfAvailable {
+ void *handle = dlopen("/System/Library/PrivateFrameworks/DFRFoundation.framework/DFRFoundation", RTLD_LAZY);
+ if (!handle) {
+ return;
+ }
+
+ void (*showsCloseBox)(BOOL) = dlsym(handle, "DFRSystemModalShowsCloseBoxWhenFrontMost");
+ if (showsCloseBox) {
+ showsCloseBox(NO);
+ }
+}
+
+@end
+
+int main(int argc, const char *argv[]) {
+ @autoreleasepool {
+ NSApplication *application = NSApplication.sharedApplication;
+ AppDelegate *delegate = [AppDelegate new];
+ application.delegate = delegate;
+ [application run];
+ }
+ return 0;
+}
diff --git a/touchbar-macOS/you-can-just-run-build.sh b/touchbar-macOS/you-can-just-run-build.sh
new file mode 100644
index 0000000..ccec54c
--- /dev/null
+++ b/touchbar-macOS/you-can-just-run-build.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+./build.sh
+exec ./GoogleBallsTouchBar.app/Contents/MacOS/GoogleBallsTouchBar