From 57f6afdf4aea3762d358e57abc0b7e0699cfd4c0 Mon Sep 17 00:00:00 2001 From: eighteight Date: Mon, 13 Jul 2026 00:43:08 +0300 Subject: [PATCH 1/4] Build universal binaries (arm64 + x86_64) with modern Xcode - Remove VALID_ARCHS = "i386 x86_64" from both app projects: it filtered arm64 out of ARCHS, so builds were Intel-only - Use $(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET) instead of 10.9, matching Syphon-Framework; targeting 10.9 no longer builds on current Xcode (libarclite was removed in Xcode 14.3) - Add LD_RUNPATH_SEARCH_PATHS = @loader_path/../Frameworks to the app targets: current Syphon-Framework uses an @rpath install name, and without a runpath the apps crash at launch (no LC_RPATH's found) - Bump the Syphon-Framework submodule to current master Fixes #7 Co-Authored-By: Claude Fable 5 --- Simple Client/Simple Client.xcodeproj/project.pbxproj | 8 ++++---- Simple Server/Simple Server.xcodeproj/project.pbxproj | 8 ++++---- Syphon-Framework | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Simple Client/Simple Client.xcodeproj/project.pbxproj b/Simple Client/Simple Client.xcodeproj/project.pbxproj index d7f3056..710a928 100644 --- a/Simple Client/Simple Client.xcodeproj/project.pbxproj +++ b/Simple Client/Simple Client.xcodeproj/project.pbxproj @@ -333,6 +333,7 @@ GCC_PREFIX_HEADER = Simple_Client_Prefix.pch; INFOPLIST_FILE = "Simple_Client-Info.plist"; INSTALL_PATH = "$(HOME)/Applications"; + LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "info.v002.syphon.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "Simple Client"; }; @@ -352,6 +353,7 @@ GCC_PREFIX_HEADER = Simple_Client_Prefix.pch; INFOPLIST_FILE = "Simple_Client-Info.plist"; INSTALL_PATH = "$(HOME)/Applications"; + LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "info.v002.syphon.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "Simple Client"; }; @@ -391,10 +393,9 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.9; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; - VALID_ARCHS = "i386 x86_64"; }; name = Debug; }; @@ -430,9 +431,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.9; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; SDKROOT = macosx; - VALID_ARCHS = "i386 x86_64"; }; name = Release; }; diff --git a/Simple Server/Simple Server.xcodeproj/project.pbxproj b/Simple Server/Simple Server.xcodeproj/project.pbxproj index 66d692c..bcc1984 100644 --- a/Simple Server/Simple Server.xcodeproj/project.pbxproj +++ b/Simple Server/Simple Server.xcodeproj/project.pbxproj @@ -327,6 +327,7 @@ GCC_PREFIX_HEADER = Simple_Server_Prefix.pch; INFOPLIST_FILE = "Simple_Server-Info.plist"; INSTALL_PATH = "$(HOME)/Applications"; + LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "info.v002.syphon.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "Simple Server"; }; @@ -346,6 +347,7 @@ GCC_PREFIX_HEADER = Simple_Server_Prefix.pch; INFOPLIST_FILE = "Simple_Server-Info.plist"; INSTALL_PATH = "$(HOME)/Applications"; + LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "info.v002.syphon.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = "Simple Server"; }; @@ -384,10 +386,9 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.9; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; - VALID_ARCHS = "i386 x86_64"; }; name = Debug; }; @@ -422,9 +423,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.9; + MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; SDKROOT = macosx; - VALID_ARCHS = "i386 x86_64"; }; name = Release; }; diff --git a/Syphon-Framework b/Syphon-Framework index ef72eff..71351d4 160000 --- a/Syphon-Framework +++ b/Syphon-Framework @@ -1 +1 @@ -Subproject commit ef72eff3df5ab1f80e7156391e4443f5163e5158 +Subproject commit 71351d4b484cd2d1917867f7846a5cdca724552d From 2ae23441d4e7ff8e7245f1d61e091de3051e4a14 Mon Sep 17 00:00:00 2001 From: eighteight Date: Mon, 13 Jul 2026 00:57:28 +0300 Subject: [PATCH 2/4] Simple Client: keep toolbar items visible when built with modern SDK Two regressions appear when the app is rebuilt with a macOS 11+ SDK: - NSToolbarItem no longer derives its size from a custom view's frame, so the view-based items (server menu, status box) collapse to zero size and vanish into the toolbar overflow menu. Pin the views to their nib sizes with explicit constraints, as old SDKs did implicitly. - Windows default to the compact unified toolbar style; opt into the classic expanded layout to preserve the app's original appearance. Also give the server menu's overflow representation a title. Co-Authored-By: Claude Fable 5 --- Simple Client/SimpleClientAppDelegate.m | 7 +++++++ Simple Client/ToolbarDelegate.m | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/Simple Client/SimpleClientAppDelegate.m b/Simple Client/SimpleClientAppDelegate.m index 86aab3a..fbf838e 100644 --- a/Simple Client/SimpleClientAppDelegate.m +++ b/Simple Client/SimpleClientAppDelegate.m @@ -80,6 +80,13 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification [[self.view window] setContentMinSize:(NSSize){400.0,300.0}]; [[self.view window] setDelegate:self]; + + // macOS 11+ defaults to the compact unified toolbar style, which + // collapses our view-based toolbar items (server menu, status box) + // into the overflow menu — use the classic expanded layout instead + if (@available(macOS 11.0, *)) { + [[self.view window] setToolbarStyle:NSWindowToolbarStyleExpanded]; + } } - (NSArray *)selectedServerDescriptions diff --git a/Simple Client/ToolbarDelegate.m b/Simple Client/ToolbarDelegate.m index 57cab7c..5bd200c 100644 --- a/Simple Client/ToolbarDelegate.m +++ b/Simple Client/ToolbarDelegate.m @@ -56,7 +56,9 @@ - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString [item setPaletteLabel:@"Source"]; [item setToolTip:@"Select a Syphon Server"]; [item setView:availableServersMenu]; + [self sizeItem:item toView:availableServersMenu]; NSMenuItem *menuForm = [[NSMenuItem alloc] init]; + [menuForm setTitle:@"Source"]; [menuForm setMenu:[availableServersMenu menu]]; [item setMenuFormRepresentation:menuForm]; } @@ -67,6 +69,7 @@ - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString [item setToolTip:@"Status"]; [statusBox setCornerRadius:4.0]; [item setView:statusBox]; + [self sizeItem:item toView:statusBox]; } else { @@ -76,6 +79,18 @@ - (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString return item; } +// When built with a modern SDK, NSToolbarItem no longer derives its size +// from the custom view's frame, so view-based items collapse and vanish +// into the overflow menu. Pin them to the size they have in the nib, as +// old SDKs did implicitly. +- (void)sizeItem:(NSToolbarItem *)item toView:(NSView *)view +{ + NSSize size = view.frame.size; + view.translatesAutoresizingMaskIntoConstraints = NO; + [view.widthAnchor constraintEqualToConstant:size.width].active = YES; + [view.heightAnchor constraintEqualToConstant:size.height].active = YES; +} + - (void)toolbarWillAddItem:(NSNotification *)notification { if (@available(macOS 10.14, *)) { From 525a5b4b009ae6100e3ec5010c70efe30cf59208 Mon Sep 17 00:00:00 2001 From: eighteight Date: Mon, 13 Jul 2026 01:20:13 +0300 Subject: [PATCH 3/4] Simple Server: draw a rotating wheel The original Simple Server rendered an animated dial from a bundled Quartz Composer composition; when the QC dependency was removed the renderer became a pulsing clear color, leaving the TODO for a more interesting animation. Draw a wheel in its spirit with OpenGL 3.2 Core: an outer ring carrying three arrows, a counter-rotating inner ring, a central disc, a slowly turning diameter and an orbiting dot. Animation time is measured from renderer start, as GPU sin/cos degrade on huge absolute timestamps. Fixes #4 Co-Authored-By: Claude Fable 5 --- Simple Server/SimpleRenderer.m | 248 ++++++++++++++++++++++++++++++++- 1 file changed, 243 insertions(+), 5 deletions(-) diff --git a/Simple Server/SimpleRenderer.m b/Simple Server/SimpleRenderer.m index 972d14f..7237dca 100644 --- a/Simple Server/SimpleRenderer.m +++ b/Simple Server/SimpleRenderer.m @@ -1,7 +1,7 @@ /* SimpleRenderer.m Syphon (SDK) - + Copyright 2010-2011 bangnoise (Tom Butterworth) & vade (Anton Marini). All rights reserved. @@ -30,13 +30,251 @@ #import "SimpleRenderer.h" #import -@implementation SimpleRenderer +// A rotating wheel in the spirit of the Quartz Composer composition the +// original Simple Server shipped with: an outer ring carrying three arrows, +// a counter-rotating inner ring, a central disc and an orbiting dot. +// Drawn with OpenGL 3.2 Core Profile. + +static NSString * const kVertexSource = @"#version 150\n" +"in vec2 pos;\n" +"uniform float angle;\n" +"uniform vec2 scale;\n" +"uniform vec2 offset;\n" +"uniform float aspect;\n" // width / height +"void main() {\n" +" float c = cos(angle), s = sin(angle);\n" +" vec2 p = mat2(c, s, -s, c) * (pos * scale) + offset;\n" +" p.x /= aspect;\n" +" gl_Position = vec4(p, 0.0, 1.0);\n" +"}\n"; + +static NSString * const kFragmentSource = @"#version 150\n" +"uniform vec4 color;\n" +"out vec4 fragColor;\n" +"void main() { fragColor = color; }\n"; + +enum { + kSegments = 96 +}; + +@implementation SimpleRenderer { + BOOL setupDone; + NSTimeInterval startTime; + GLuint program; + GLuint vao; + GLuint vbo; + GLint uAngle, uScale, uOffset, uAspect, uColor; + + // offsets (in vertices) and counts of each piece in the buffer + GLint discFirst, discCount; + GLint ringFirst, ringCount; + GLint circleFirst, circleCount; + GLint arrowFirst, arrowCount; + GLint lineFirst, lineCount; +} + +static GLuint compileShader(GLenum type, NSString *source) +{ + GLuint shader = glCreateShader(type); + const char *src = [source UTF8String]; + glShaderSource(shader, 1, &src, NULL); + glCompileShader(shader); + GLint ok = 0; + glGetShaderiv(shader, GL_COMPILE_STATUS, &ok); + if (!ok) + { + char log[512]; + glGetShaderInfoLog(shader, sizeof(log), NULL, log); + NSLog(@"Shader compile failed: %s", log); + } + return shader; +} + +- (void)setupGL +{ + GLuint vs = compileShader(GL_VERTEX_SHADER, kVertexSource); + GLuint fs = compileShader(GL_FRAGMENT_SHADER, kFragmentSource); + program = glCreateProgram(); + glAttachShader(program, vs); + glAttachShader(program, fs); + glBindAttribLocation(program, 0, "pos"); + glLinkProgram(program); + glDeleteShader(vs); + glDeleteShader(fs); + uAngle = glGetUniformLocation(program, "angle"); + uScale = glGetUniformLocation(program, "scale"); + uOffset = glGetUniformLocation(program, "offset"); + uAspect = glGetUniformLocation(program, "aspect"); + uColor = glGetUniformLocation(program, "color"); + + // Build all the geometry, in a unit coordinate space, into one buffer + NSMutableData *data = [NSMutableData data]; + GLint cursor = 0; + + // 1: unit disc (triangle fan) — also reused for the orbiting dot + { + discFirst = cursor; + GLfloat center[2] = {0, 0}; + [data appendBytes:center length:sizeof(center)]; + for (int i = 0; i <= kSegments; i++) + { + float a = 2.0 * M_PI * i / kSegments; + GLfloat v[2] = {cosf(a), sinf(a)}; + [data appendBytes:v length:sizeof(v)]; + } + discCount = kSegments + 2; + cursor += discCount; + } + + // 2: annulus, radii 0.86..1.0 (triangle strip) — scaled for both rings + { + ringFirst = cursor; + for (int i = 0; i <= kSegments; i++) + { + float a = 2.0 * M_PI * i / kSegments; + GLfloat v[4] = {0.86f * cosf(a), 0.86f * sinf(a), cosf(a), sinf(a)}; + [data appendBytes:v length:sizeof(v)]; + } + ringCount = (kSegments + 1) * 2; + cursor += ringCount; + } + + // 3: unit circle outline (line loop) + { + circleFirst = cursor; + for (int i = 0; i < kSegments; i++) + { + float a = 2.0 * M_PI * i / kSegments; + GLfloat v[2] = {cosf(a), sinf(a)}; + [data appendBytes:v length:sizeof(v)]; + } + circleCount = kSegments; + cursor += circleCount; + } + + // 4: one arrow, swept along the outer ring at angle 0 (two triangles): + // a tangential tail across the ring ending in a tip + { + arrowFirst = cursor; + float rIn = 0.83f, rOut = 1.03f, rMid = 0.93f; + float aTail = -0.28f, aNeck = 0.10f, aTip = 0.34f; + GLfloat tail[6] = { + rIn * cosf(aTail), rIn * sinf(aTail), + rOut * cosf(aTail), rOut * sinf(aTail), + rIn * cosf(aNeck), rIn * sinf(aNeck)}; + GLfloat head[6] = { + rOut * cosf(aTail), rOut * sinf(aTail), + rIn * cosf(aNeck), rIn * sinf(aNeck), + rMid * cosf(aTip), rMid * sinf(aTip)}; + [data appendBytes:tail length:sizeof(tail)]; + [data appendBytes:head length:sizeof(head)]; + arrowCount = 6; + cursor += arrowCount; + } + + // 5: a diameter line + { + lineFirst = cursor; + GLfloat v[4] = {-1, 0, 1, 0}; + [data appendBytes:v length:sizeof(v)]; + lineCount = 2; + cursor += lineCount; + } + + glGenVertexArrays(1, &vao); + glBindVertexArray(vao); + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, data.length, data.bytes, GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0); + + startTime = [NSDate timeIntervalSinceReferenceDate]; + setupDone = YES; +} + +- (void)drawMode:(GLenum)mode first:(GLint)first count:(GLint)count + angle:(float)angle scale:(NSSize)scale offset:(NSPoint)offset + red:(float)r green:(float)g blue:(float)b alpha:(float)a +{ + glUniform1f(uAngle, angle); + glUniform2f(uScale, scale.width, scale.height); + glUniform2f(uOffset, offset.x, offset.y); + glUniform4f(uColor, r, g, b, a); + glDrawArrays(mode, first, count); +} - (void)render:(NSSize)dimensions { - // TODO: a more interesting animation - GLfloat grad = 0.4 * (1.2 + sin(2 * M_PI * 1.0/4.0 * [NSDate timeIntervalSinceReferenceDate])); - glClearColor(grad, grad, grad, 1.0); + if (!setupDone) + { + [self setupGL]; + } + + glViewport(0, 0, dimensions.width, dimensions.height); + glClearColor(0.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT); + + glUseProgram(program); + glBindVertexArray(vao); + glEnable(GL_BLEND); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + + float aspect = dimensions.height > 0 ? dimensions.width / dimensions.height : 1.0; + glUniform1f(uAspect, aspect); + + // elapsed time since start: keep the value small, as float precision + // (and GPU sin/cos) fall apart on huge absolute timestamps + float t = (float)([NSDate timeIntervalSinceReferenceDate] - startTime); + float spin = t * 0.6; // outer ring, radians/sec + + #define WHEEL 0.82f // overall radius in clip space + + // outer ring with three arrows, rotating + [self drawMode:GL_TRIANGLE_STRIP first:ringFirst count:ringCount + angle:spin scale:NSMakeSize(WHEEL, WHEEL) offset:NSMakePoint(0, 0) + red:0.75 green:0.78 blue:0.82 alpha:0.55]; + for (int i = 0; i < 3; i++) + { + [self drawMode:GL_TRIANGLES first:arrowFirst count:arrowCount + angle:spin + i * (2.0 * M_PI / 3.0) + scale:NSMakeSize(WHEEL, WHEEL) offset:NSMakePoint(0, 0) + red:0.62 green:0.65 blue:0.70 alpha:0.9]; + } + + // inner ring, counter-rotating (annulus scaled down) + [self drawMode:GL_TRIANGLE_STRIP first:ringFirst count:ringCount + angle:-spin * 0.7 scale:NSMakeSize(WHEEL * 0.72f, WHEEL * 0.72f) + offset:NSMakePoint(0, 0) + red:0.25 green:0.27 blue:0.30 alpha:0.85]; + + // central disc + [self drawMode:GL_TRIANGLE_FAN first:discFirst count:discCount + angle:0 scale:NSMakeSize(WHEEL * 0.42f, WHEEL * 0.42f) + offset:NSMakePoint(0, 0) + red:0.82 green:0.86 blue:0.90 alpha:1.0]; + + // thin circle and a slowly turning diameter over everything + [self drawMode:GL_LINE_LOOP first:circleFirst count:circleCount + angle:0 scale:NSMakeSize(WHEEL * 0.56f, WHEEL * 0.56f) + offset:NSMakePoint(0, 0) + red:1.0 green:1.0 blue:1.0 alpha:0.5]; + [self drawMode:GL_LINES first:lineFirst count:lineCount + angle:spin * 0.35 scale:NSMakeSize(WHEEL * 0.98f, WHEEL * 0.98f) + offset:NSMakePoint(0, 0) + red:1.0 green:1.0 blue:1.0 alpha:0.35]; + + // the blue dot, orbiting inside the disc + float dotAngle = -t * 1.5; + NSPoint dotPos = NSMakePoint(WHEEL * 0.28f * cosf(dotAngle), + WHEEL * 0.28f * sinf(dotAngle)); + [self drawMode:GL_TRIANGLE_FAN first:discFirst count:discCount + angle:0 scale:NSMakeSize(WHEEL * 0.05f, WHEEL * 0.05f) + offset:dotPos + red:0.18 green:0.56 blue:0.94 alpha:1.0]; + + glDisable(GL_BLEND); + glBindVertexArray(0); + glUseProgram(0); } @end From aad40be751c2c2110212b69f3dad9f15c9374310 Mon Sep 17 00:00:00 2001 From: eighteight Date: Mon, 13 Jul 2026 01:30:36 +0300 Subject: [PATCH 4/4] Simple Client: restore original toolbar item order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Server menu at the left, status box beside it — as the app has always looked. Co-Authored-By: Claude Fable 5 --- Simple Client/ToolbarDelegate.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Simple Client/ToolbarDelegate.m b/Simple Client/ToolbarDelegate.m index 5bd200c..82b6e40 100644 --- a/Simple Client/ToolbarDelegate.m +++ b/Simple Client/ToolbarDelegate.m @@ -39,7 +39,9 @@ @implementation ToolbarDelegate { - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar { - return @[kStatusItemIdentifier, NSToolbarFlexibleSpaceItemIdentifier, kServersMenuItemIdentifier]; + // server menu at the left, status box centered (see toolbarWillAddItem:) + // — the layout the app has always had + return @[kServersMenuItemIdentifier, kStatusItemIdentifier]; } - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar