Skip to content

Commit 6f6f891

Browse files
authored
Add deeplink support on mac (#34)
2 parents bf86b37 + 88da585 commit 6f6f891

File tree

15 files changed

+561
-0
lines changed

15 files changed

+561
-0
lines changed

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
* text=auto eol=lf
22
*.MF text eol=crlf
33
*.jar binary
4+
*.dylib binary

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# DurianSwt releases
22

33
## [Unreleased]
4+
### Added
5+
- `MacDeepLink` class and dylibs for supporting deep links on modern versions of macOS.
46

57
## [5.1.0] - 2025-08-21
68
### Added

CLAUDE.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Build Commands
6+
7+
This is a Gradle-based Java/Kotlin project focused on SWT (Standard Widget Toolkit) utilities. Standard gradle tasks.
8+
9+
### macOS-specific Requirements
10+
- On macOS, SWT tests require the `-XstartOnFirstThread` JVM argument (automatically configured in build.gradle).
11+
12+
## Project Architecture
13+
### Multi-Module Structure
14+
DurianSwt is organized as a multi-module Gradle project with platform-specific implementations:
15+
16+
- **durian-swt.os**: Platform detection utilities (OS, Arch, SwtPlatform) - no SWT dependencies
17+
- **durian-swt**: Main module with core SWT utilities and builders
18+
- **platform-specific modules**: (durian-swt.cocoa.macosx.aarch64, durian-swt.cocoa.macosx.x86_64, durian-swt.gtk.linux.x86_64, durian-swt.win32.win32.x86_64)
19+
20+
## Native Components
21+
22+
### macOS Deep Link Support (`natives/mac-deep-link/`)
23+
24+
Contains Objective-C code for handling deep links via a custom `diffplug://` protocol on macOS without breaking SWT:
25+
26+
- **DeepLinkBridge.m**: JNI bridge that intercepts URL open events from macOS
27+
- **compile-one.sh**: Compiles the native library for a specific architecture (x86_64 or arm64)
28+
- **clean-and-build.sh**: Builds for both architectures and deploys to appropriate module resources
29+
30+
To rebuild native libraries:
31+
```bash
32+
cd natives/mac-deep-link
33+
./clean-and-build.sh # Builds for both architectures
34+
# Or compile for specific architecture:
35+
./compile-one.sh arm64 # Apple Silicon
36+
./compile-one.sh x86_64 # Intel
37+
```
38+
39+
The resulting `DeepLinkBridge.dylib` files are placed in the platform-specific modules under `src/main/resources/durian-swt-natives/`.

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ subprojects { subProject ->
6969
}
7070
dependencies {
7171
api project(':durian-swt')
72+
implementation "com.diffplug.durian:durian-core:$VER_DURIAN"
7273
}
7374
configurations.all {
7475
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright (C) 2025 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.common.swt;
17+
18+
import com.diffplug.common.base.Preconditions;
19+
import java.util.ArrayList;
20+
import java.util.concurrent.atomic.AtomicReference;
21+
import java.util.function.Consumer;
22+
import org.jetbrains.annotations.Nullable;
23+
24+
/**
25+
* - immediately on app startup, call `MacDeepLink.startCapturingBeforeSwt()`
26+
* - once SWT has initialized, call `MacDeepLink.swtHasInitializedBeginReceiving(Consumer<String>)`
27+
* - all urls which were captured before SWT initialized will be passed immediately (on the SWT thread)
28+
*
29+
* That's all! Don't do anything else.
30+
*/
31+
public class MacDeepLink {
32+
/**
33+
* state transitions are:
34+
* - `null` on startup
35+
* - `startCapturingBeforeSwt()` transitions to an `ArrayList<String>`, backlog urls get added to it
36+
* - `swtHasInitializedBeginReceiving()` transitions to a `Consumer<String>`, all new urls go there
37+
*/
38+
private static final AtomicReference<@Nullable Object> state = new AtomicReference<>();
39+
40+
public static void startCapturingBeforeSwt() {
41+
String libPath = System.getProperty("durian-swt.library.path");
42+
if (libPath != null) {
43+
System.load(libPath + "/durian-swt-natives/DeepLinkBridge.dylib");
44+
} else {
45+
throw new IllegalArgumentException("You need to set 'durian-swt.library.path'");
46+
}
47+
48+
var was = state.getAndSet(new ArrayList<>());
49+
Preconditions.checkArgument(was == null, "`startCapturingBeforeSwt() should be called first`");
50+
nativeBeforeSwt();
51+
}
52+
53+
public static void swtHasInitializedBeginReceiving(Consumer<String> handler) {
54+
SwtMisc.assertUI();
55+
var was = state.getAndSet(handler);
56+
Preconditions.checkArgument(was instanceof ArrayList<?>, "Call `applicationStartBeforeSwt()` first.");
57+
58+
var backlog = (ArrayList<String>) was;
59+
backlog.forEach(handler);
60+
61+
nativeAfterSwt();
62+
}
63+
64+
// Native method declarations - implemented in DeepLinkBridge.m
65+
private static native void nativeBeforeSwt();
66+
67+
private static native void nativeAfterSwt();
68+
69+
/**
70+
* Called from native code when a URL is received.
71+
* This method is invoked on various threads by the native code.
72+
*
73+
* @param url The URL string received from the operating system
74+
*/
75+
public static void __internal_deliverUrl(String url) {
76+
var was = state.get();
77+
if (was instanceof Consumer) {
78+
((Consumer<String>) was).accept(url);
79+
} else if (was instanceof ArrayList<?>) {
80+
((ArrayList<String>) was).add(url);
81+
} else {
82+
throw new IllegalStateException("Expected Consumer or ArrayList, was " + was);
83+
}
84+
}
85+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright (C) 2025 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.common.swt;
17+
18+
import com.diffplug.common.base.Preconditions;
19+
import java.util.ArrayList;
20+
import java.util.concurrent.atomic.AtomicReference;
21+
import java.util.function.Consumer;
22+
import org.jetbrains.annotations.Nullable;
23+
24+
/**
25+
* - immediately on app startup, call `MacDeepLink.startCapturingBeforeSwt()`
26+
* - once SWT has initialized, call `MacDeepLink.swtHasInitializedBeginReceiving(Consumer<String>)`
27+
* - all urls which were captured before SWT initialized will be passed immediately (on the SWT thread)
28+
*
29+
* That's all! Don't do anything else.
30+
*/
31+
public class MacDeepLink {
32+
/**
33+
* state transitions are:
34+
* - `null` on startup
35+
* - `startCapturingBeforeSwt()` transitions to an `ArrayList<String>`, backlog urls get added to it
36+
* - `swtHasInitializedBeginReceiving()` transitions to a `Consumer<String>`, all new urls go there
37+
*/
38+
private static final AtomicReference<@Nullable Object> state = new AtomicReference<>();
39+
40+
public static void startCapturingBeforeSwt() {
41+
String libPath = System.getProperty("durian-swt.library.path");
42+
if (libPath != null) {
43+
System.load(libPath + "/durian-swt-natives/DeepLinkBridge.dylib");
44+
} else {
45+
throw new IllegalArgumentException("You need to set 'durian-swt.library.path'");
46+
}
47+
48+
var was = state.getAndSet(new ArrayList<>());
49+
Preconditions.checkArgument(was == null, "`startCapturingBeforeSwt() should be called first`");
50+
nativeBeforeSwt();
51+
}
52+
53+
public static void swtHasInitializedBeginReceiving(Consumer<String> handler) {
54+
SwtMisc.assertUI();
55+
var was = state.getAndSet(handler);
56+
Preconditions.checkArgument(was instanceof ArrayList<?>, "Call `applicationStartBeforeSwt()` first.");
57+
58+
var backlog = (ArrayList<String>) was;
59+
backlog.forEach(handler);
60+
61+
nativeAfterSwt();
62+
}
63+
64+
// Native method declarations - implemented in DeepLinkBridge.m
65+
private static native void nativeBeforeSwt();
66+
67+
private static native void nativeAfterSwt();
68+
69+
/**
70+
* Called from native code when a URL is received.
71+
* This method is invoked on various threads by the native code.
72+
*
73+
* @param url The URL string received from the operating system
74+
*/
75+
public static void __internal_deliverUrl(String url) {
76+
var was = state.get();
77+
if (was instanceof Consumer) {
78+
((Consumer<String>) was).accept(url);
79+
} else if (was instanceof ArrayList<?>) {
80+
((ArrayList<String>) was).add(url);
81+
} else {
82+
throw new IllegalStateException("Expected Consumer or ArrayList, was " + was);
83+
}
84+
}
85+
}

durian-swt.gtk.linux.x86/src/main/resources/durian-swt-natives/placeholder

Whitespace-only changes.

durian-swt.gtk.linux.x86_64/src/main/resources/durian-swt-natives/placeholder

Whitespace-only changes.

0 commit comments

Comments
 (0)