Skip to content

Commit 7de6ada

Browse files
authored
Merge pull request #2 from schwwaaa/spout
Spout + new OS operation
2 parents 8297f62 + 9fee888 commit 7de6ada

File tree

279 files changed

+122986
-564
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

279 files changed

+122986
-564
lines changed

.DS_Store

2 KB
Binary file not shown.

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,11 @@ anyhow = "1"
2323

2424
[build-dependencies]
2525
cc = "1"
26+
cmake = "0.1"
27+
anyhow = "1"
28+
29+
[target.'cfg(windows)'.dependencies]
30+
libloading = "0.8"
31+
32+
[lints.rust]
33+
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(has_syphon)'] }

assets/output.json

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
2-
"output_mode": "texture",
3-
"syphon": {
4-
"enabled": true,
5-
"server_name": "shadecore"
6-
},
7-
"spout": {
8-
"enabled": false
9-
},
10-
"hotkeys": {
11-
"texture": ["Digit1", "Numpad1"],
12-
"syphon": ["Digit2", "Numpad2"],
13-
"spout": ["Digit3", "Numpad3"]
14-
}
15-
}
2+
"output_mode": "texture",
3+
"syphon": {
4+
"enabled": false,
5+
"server_name": "shadecore"
6+
},
7+
"spout": {
8+
"enabled": true
9+
},
10+
"hotkeys": {
11+
"texture": ["Digit1", "Numpad1"],
12+
"syphon": ["Digit2", "Numpad2"],
13+
"spout": ["Digit3", "Numpad3"]
14+
}
15+
}

assets/output.linux.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"output_mode": "texture",
3+
"syphon": {
4+
"enabled": false,
5+
"server_name": "shadecore"
6+
},
7+
"spout": {
8+
"enabled": false
9+
},
10+
"hotkeys": {
11+
"texture": ["Digit1", "Numpad1"],
12+
"syphon": ["Digit2", "Numpad2"],
13+
"spout": ["Digit3", "Numpad3"]
14+
}
15+
}

assets/output.macos.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"output_mode": "syphon",
3+
"syphon": {
4+
"enabled": true,
5+
"server_name": "shadecore"
6+
},
7+
"spout": {
8+
"enabled": false
9+
},
10+
"hotkeys": {
11+
"texture": ["Digit1", "Numpad1"],
12+
"syphon": ["Digit2", "Numpad2"],
13+
"spout": ["Digit3", "Numpad3"]
14+
}
15+
}

assets/output.windows.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"output_mode": "spout",
3+
"syphon": {
4+
"enabled": false,
5+
"server_name": "shadecore"
6+
},
7+
"spout": {
8+
"enabled": true
9+
},
10+
"hotkeys": {
11+
"texture": ["Digit1", "Numpad1"],
12+
"syphon": ["Digit2", "Numpad2"],
13+
"spout": ["Digit3", "Numpad3"]
14+
}
15+
}

build.rs

Lines changed: 154 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,42 @@
11
// build.rs
22
//
3-
// This build script does four things (macOS only):
4-
// 1) Compiles our Objective‑C Syphon bridge (native/syphon_bridge.m) into a static lib.
5-
// 2) Links against the Syphon.framework we vendor inside this repo (vendor/Syphon.framework).
6-
// 3) Adds an LC_RPATH so the runtime loader can actually *find* Syphon.framework when you run
7-
// `cargo run` (or any raw executable build).
8-
// 4) Copies vendor/Syphon.framework into target/{debug|release}/Syphon.framework so the rpath
9-
// we add will resolve correctly.
3+
// macOS:
4+
// - optionally compiles the Syphon ObjC bridge
5+
// - links Syphon.framework if vendored
6+
// - emits cfg(has_syphon) when Syphon.framework exists
107
//
11-
// Why this is needed:
12-
// - Our Rust binary references Syphon as: @rpath/Syphon.framework/Versions/A/Syphon
13-
// - If the binary has *no* rpaths, dyld can’t resolve @rpath and you get:
14-
// "Reason: no LC_RPATH's found"
8+
// Windows:
9+
// - builds native/spout_bridge via CMake (which builds Spout + a small C-ABI bridge DLL)
10+
// - links to spout_bridge import library so Rust can resolve spout_* symbols
11+
// - copies spout_bridge.dll next to the built exe for `cargo run`
1512
//
16-
// For app-bundle distribution later, we’ll instead copy Syphon.framework into:
17-
// MyApp.app/Contents/Frameworks/Syphon.framework
18-
// and rely on the rpath @executable_path/../Frameworks (we also add that here).
13+
// Also:
14+
// - declares cfg(has_syphon) to rustc (silences unexpected_cfgs warnings on non-mac targets)
1915

2016
use std::env;
2117
use std::fs;
2218
use std::path::{Path, PathBuf};
2319

2420
fn main() {
25-
// Only do Syphon wiring on macOS. Other platforms should compile fine without it.
26-
if env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("macos") {
21+
// Tell rustc that `cfg(has_syphon)` is an allowed cfg key (silences warnings on Windows/Linux).
22+
println!("cargo:rustc-check-cfg=cfg(has_syphon)");
23+
24+
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
25+
26+
if target_os == "macos" {
27+
build_syphon_macos();
28+
return;
29+
}
30+
31+
if target_os == "windows" {
32+
build_spout_windows();
2733
return;
2834
}
2935

36+
// Other platforms: nothing special.
37+
}
38+
39+
fn build_syphon_macos() {
3040
// Rebuild if these change
3141
println!("cargo:rerun-if-changed=native/syphon_bridge.m");
3242
println!("cargo:rerun-if-changed=native/syphon_bridge.h");
@@ -36,83 +46,176 @@ fn main() {
3646
let vendor_dir = manifest_dir.join("vendor");
3747
let syphon_framework = vendor_dir.join("Syphon.framework");
3848

49+
// Syphon is OPTIONAL. If missing, compile Texture-only on macOS.
3950
if !syphon_framework.exists() {
40-
panic!(
41-
"Syphon.framework not found at {}. Put a built Syphon.framework in vendor/.",
51+
println!(
52+
"cargo:warning=Syphon.framework not found at {} — building WITHOUT Syphon support (Texture-only on macOS).",
4253
syphon_framework.display()
4354
);
55+
return;
4456
}
4557

46-
// -------------------------
58+
// Tell Rust code that Syphon is available in this build.
59+
println!("cargo:rustc-cfg=has_syphon");
60+
4761
// 1) Compile the ObjC bridge into libsyphon_bridge.a
48-
// -------------------------
4962
let mut cc_build = cc::Build::new();
5063
cc_build
5164
.file("native/syphon_bridge.m")
5265
.flag("-fobjc-arc")
5366
.flag("-ObjC")
5467
.include(syphon_framework.join("Headers"))
55-
// Some Syphon distributions also have Headers under Versions/A/Headers
5668
.include(syphon_framework.join("Versions/A/Headers"))
57-
// Allow finding frameworks via -F
5869
.flag(&format!("-F{}", vendor_dir.display()))
59-
// Silence noisy deprecation warnings (optional; remove if you want them visible)
6070
.flag("-Wno-deprecated-declarations");
6171

62-
cc_build.compile("syphon_bridge"); // -> libsyphon_bridge.a
72+
cc_build.compile("syphon_bridge");
6373

64-
// -------------------------
65-
// 2) Link against Syphon.framework + required Apple frameworks
66-
// -------------------------
74+
// 2) Link Syphon.framework + required Apple frameworks
6775
println!("cargo:rustc-link-search=framework={}", vendor_dir.display());
6876
println!("cargo:rustc-link-lib=framework=Syphon");
6977
println!("cargo:rustc-link-lib=framework=Cocoa");
7078
println!("cargo:rustc-link-lib=framework=OpenGL");
7179

72-
// -------------------------
7380
// 3) Add runtime rpaths so dyld can resolve @rpath/Syphon.framework/...
74-
// -------------------------
75-
//
76-
// We add TWO rpaths:
77-
// - @executable_path : so `cargo run` works if Syphon.framework sits next to the binary
78-
// - @executable_path/../Frameworks : so future .app bundles can place frameworks in Contents/Frameworks
79-
//
80-
// IMPORTANT: rustc-link-arg is stable and works across profiles.
8181
println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path");
8282
println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path/../Frameworks");
8383

84-
// -------------------------
8584
// 4) Copy Syphon.framework next to the built binary for `cargo run`
86-
// -------------------------
87-
//
88-
// Cargo puts the executable at:
89-
// <workspace>/target/<profile>/glsl_engine
90-
//
91-
// So we copy:
92-
// vendor/Syphon.framework -> target/<profile>/Syphon.framework
93-
//
94-
// Then @executable_path (the directory containing the binary) is also the directory
95-
// containing Syphon.framework, and dyld can resolve @rpath/Syphon.framework...
9685
let profile = env::var("PROFILE").unwrap_or_else(|_| "debug".into());
97-
98-
// Respect CARGO_TARGET_DIR if set, otherwise default to <manifest>/target
9986
let target_dir = env::var("CARGO_TARGET_DIR")
10087
.map(PathBuf::from)
10188
.unwrap_or_else(|_| manifest_dir.join("target"));
102-
10389
let dest_dir = target_dir.join(&profile).join("Syphon.framework");
10490

105-
// Copy only if missing or obviously stale (simple heuristic: missing dest)
10691
if !dest_dir.exists() {
10792
copy_dir_recursive(&syphon_framework, &dest_dir)
10893
.unwrap_or_else(|e| panic!("Failed to copy Syphon.framework -> {}: {e}", dest_dir.display()));
10994
}
11095
}
11196

97+
fn build_spout_windows() {
98+
// If these change, rerun
99+
println!("cargo:rerun-if-changed=native/spout_bridge/CMakeLists.txt");
100+
println!("cargo:rerun-if-changed=native/spout_bridge/spout_bridge.cpp");
101+
println!("cargo:rerun-if-changed=native/spout_bridge/spout_bridge.h");
102+
println!("cargo:rerun-if-changed=native/spout2");
103+
104+
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
105+
let spout2_dir = manifest_dir.join("native").join("spout2");
106+
107+
if !spout2_dir.exists() {
108+
println!(
109+
"cargo:warning=Spout2 directory not found at {} — building WITHOUT Spout support.",
110+
spout2_dir.display()
111+
);
112+
return;
113+
}
114+
115+
// Map Cargo profile -> CMake config
116+
let profile = env::var("PROFILE").unwrap_or_else(|_| "debug".into());
117+
let cmake_build_type = if profile.eq_ignore_ascii_case("release") {
118+
"Release"
119+
} else {
120+
"Debug"
121+
};
122+
123+
// Build the CMake project (spout_bridge DLL + import lib)
124+
let dst = cmake::Config::new("native/spout_bridge")
125+
.define("SPOUT2_DIR", spout2_dir.to_string_lossy().to_string())
126+
.profile(cmake_build_type)
127+
.build_target("spout_bridge") // <-- THIS is the key fix
128+
.build();
129+
130+
// Find spout_bridge import library + dll (cmake crate layouts vary by generator)
131+
let (lib_dir, dll_path) = find_spout_bridge_artifacts(&dst)
132+
.unwrap_or_else(|| panic!("Could not find spout_bridge.lib / spout_bridge.dll under {}", dst.display()));
133+
134+
// Link against the import library so Rust can resolve spout_* externs
135+
println!("cargo:rustc-link-search=native={}", lib_dir.display());
136+
println!("cargo:rustc-link-lib=dylib=spout_bridge");
137+
138+
// Copy DLL next to the exe for `cargo run`
139+
let target_dir = env::var("CARGO_TARGET_DIR")
140+
.map(PathBuf::from)
141+
.unwrap_or_else(|_| manifest_dir.join("target"));
142+
let exe_dir = target_dir.join(&profile);
143+
144+
let dest_dll = exe_dir.join("spout_bridge.dll");
145+
if let Err(e) = fs::create_dir_all(&exe_dir) {
146+
panic!("Failed to create target dir {}: {e}", exe_dir.display());
147+
}
148+
if let Err(e) = fs::copy(&dll_path, &dest_dll) {
149+
panic!(
150+
"Failed to copy {} -> {} : {e}",
151+
dll_path.display(),
152+
dest_dll.display()
153+
);
154+
}
155+
}
156+
157+
fn find_spout_bridge_artifacts(dst: &Path) -> Option<(PathBuf, PathBuf)> {
158+
// Common locations produced by cmake crate across generators:
159+
// - dst/lib + dst/bin
160+
// - dst/build/<cfg>/...
161+
// - dst/<cfg>/...
162+
let candidates = [
163+
dst.join("lib"),
164+
dst.join("bin"),
165+
dst.join("build"),
166+
dst.join("build").join("Debug"),
167+
dst.join("build").join("Release"),
168+
dst.join("Debug"),
169+
dst.join("Release"),
170+
];
171+
172+
// Helper: search recursively (limited depth) for a filename.
173+
fn find_file(root: &Path, filename: &str, depth: usize) -> Option<PathBuf> {
174+
if depth == 0 || !root.exists() {
175+
return None;
176+
}
177+
let rd = fs::read_dir(root).ok()?;
178+
for e in rd.flatten() {
179+
let p = e.path();
180+
if p.is_file() {
181+
if p.file_name().map(|s| s.to_string_lossy().eq_ignore_ascii_case(filename)) == Some(true) {
182+
return Some(p);
183+
}
184+
} else if p.is_dir() {
185+
if let Some(found) = find_file(&p, filename, depth - 1) {
186+
return Some(found);
187+
}
188+
}
189+
}
190+
None
191+
}
192+
193+
// Find lib first (import library)
194+
let mut lib_path: Option<PathBuf> = None;
195+
for c in &candidates {
196+
if let Some(p) = find_file(c, "spout_bridge.lib", 6) {
197+
lib_path = Some(p);
198+
break;
199+
}
200+
}
201+
let lib_path = lib_path.or_else(|| find_file(dst, "spout_bridge.lib", 8))?;
202+
203+
// Find dll
204+
let mut dll_path: Option<PathBuf> = None;
205+
for c in &candidates {
206+
if let Some(p) = find_file(c, "spout_bridge.dll", 6) {
207+
dll_path = Some(p);
208+
break;
209+
}
210+
}
211+
let dll_path = dll_path.or_else(|| find_file(dst, "spout_bridge.dll", 8))?;
212+
213+
Some((lib_path.parent()?.to_path_buf(), dll_path))
214+
}
215+
112216
/// Recursively copy a directory (framework bundles are directories).
113217
fn copy_dir_recursive(src: &Path, dst: &Path) -> std::io::Result<()> {
114218
if dst.exists() {
115-
// If something exists, remove it so we don't end up with mixed versions.
116219
fs::remove_dir_all(dst)?;
117220
}
118221
fs::create_dir_all(dst)?;
@@ -128,7 +231,6 @@ fn copy_dir_recursive(src: &Path, dst: &Path) -> std::io::Result<()> {
128231
} else if file_type.is_file() {
129232
fs::copy(&from, &to)?;
130233
} else if file_type.is_symlink() {
131-
// Preserve symlinks in framework bundles (common in Versions layout)
132234
let target = fs::read_link(&from)?;
133235
#[cfg(unix)]
134236
std::os::unix::fs::symlink(target, &to)?;

0 commit comments

Comments
 (0)