Skip to content

Commit a05343b

Browse files
committed
Merge branch 'main' into svg-fuzzy
2 parents 1eb5793 + 8463eea commit a05343b

File tree

24 files changed

+722
-58
lines changed

24 files changed

+722
-58
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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
## [Unreleased]
44

5+
## [5.2.0] - 2025-08-28
6+
### Added
7+
- `MacDeepLink` class and dylibs for supporting deep links on modern versions of macOS.
8+
9+
## [5.1.0] - 2025-08-21
10+
### Added
11+
- Add `META-INF/services` metadata to support Kotlin coroutines `Dispatchers.Main`.
12+
513
## [5.0.1] - 2025-01-26
614
### Changed
715
- Downgrade from Kotlin `2.1.0` to `2.0.21`

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/`.

README.md

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ output = [
1414
[![Maven central](https://img.shields.io/badge/mavencentral-com.diffplug.durian%3Adurian--swt-blue.svg)](https://search.maven.org/artifact/com.diffplug.durian/durian-swt)
1515
[![Apache 2.0](https://img.shields.io/badge/license-apache--2.0-blue.svg)](https://tldrlegal.com/license/apache-license-2.0-(apache-2.0))
1616

17-
[![Changelog](https://img.shields.io/badge/changelog-5.0.1-brightgreen.svg)](CHANGES.md)
18-
[![Javadoc](https://img.shields.io/badge/javadoc-yes-brightgreen.svg)](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/overview-summary.html)
17+
[![Changelog](https://img.shields.io/badge/changelog-5.2.0-brightgreen.svg)](CHANGES.md)
18+
[![Javadoc](https://img.shields.io/badge/javadoc-yes-brightgreen.svg)](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/overview-summary.html)
1919
[![Live chat](https://img.shields.io/badge/gitter-chat-brightgreen.svg)](https://gitter.im/diffplug/durian)
2020
[![Travis CI](https://travis-ci.org/diffplug/durian-swt.svg?branch=master)](https://travis-ci.org/diffplug/durian-swt)
2121
<!---freshmark /shields -->
@@ -25,11 +25,11 @@ output = prefixDelimiterReplace(input, 'https://javadoc.io/static/com.diffplug.d
2525
-->
2626
### Infrastructure
2727

28-
* [`ControlWrapper`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/ControlWrapper.html) - create custom widgets which properly encapsulate their base control.
29-
* [`Coat`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/Coat.html) - a functional interface for populating an empty Composite.
30-
* [`CoatMux`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/CoatMux.html) - a mechanism for layering and swapping Coats.
31-
* [`SwtExec`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/SwtExec.html) - an `ExecutorService` which executes on the SWT thread.
32-
* [`SwtExec.Guarded`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/SwtExec.Guarded.html) - an `ExecutorService` which is tied to the lifetime of an SWT widget. Say goodbye to `SWTException: Widget is disposed` forever! It can also subscribe to any kind of observable (Guava's ListenableFuture or RxJava's Observable), see [DurianRx](https://github.com/diffplug/durian-rx) for more info.
28+
* [`ControlWrapper`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/ControlWrapper.html) - create custom widgets which properly encapsulate their base control.
29+
* [`Coat`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/Coat.html) - a functional interface for populating an empty Composite.
30+
* [`CoatMux`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/CoatMux.html) - a mechanism for layering and swapping Coats.
31+
* [`SwtExec`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/SwtExec.html) - an `ExecutorService` which executes on the SWT thread.
32+
* [`SwtExec.Guarded`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/SwtExec.Guarded.html) - an `ExecutorService` which is tied to the lifetime of an SWT widget. Say goodbye to `SWTException: Widget is disposed` forever! It can also subscribe to any kind of observable (Guava's ListenableFuture or RxJava's Observable), see [DurianRx](https://github.com/diffplug/durian-rx) for more info.
3333

3434
```java
3535
SwtExec.async().guardOn(textBox).subscribe(serverResponse, txt -> {
@@ -39,7 +39,7 @@ SwtExec.async().guardOn(textBox).subscribe(serverResponse, txt -> {
3939

4040
### Fluent builders
4141

42-
* [`Layouts`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/Layouts.html) - all the layouts you'll need in SWT
42+
* [`Layouts`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/Layouts.html) - all the layouts you'll need in SWT
4343

4444
```java
4545
void textOkCanel(Composite cmp) {
@@ -58,7 +58,7 @@ void textOkCanel(Composite cmp) {
5858
}
5959
```
6060

61-
* [`Shells`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/Shells.html) - dialogs without boilerplate
61+
* [`Shells`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/Shells.html) - dialogs without boilerplate
6262

6363
```java
6464
Shells.builder(SWT.DIALOG_TRIM, this::textOkCanel)
@@ -67,13 +67,13 @@ Shells.builder(SWT.DIALOG_TRIM, this::textOkCanel)
6767
.openOnDisplayBlocking();
6868
```
6969

70-
* [`Actions`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/jface/Actions.html) - builder and one-liner:
70+
* [`Actions`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/jface/Actions.html) - builder and one-liner:
7171
`Actions.create("Redo", this::redo);`
7272

73-
* [`LabelProviders`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/jface/LabelProviders.html) - builder and one-liner:
73+
* [`LabelProviders`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/jface/LabelProviders.html) - builder and one-liner:
7474
`LabelProviders.createWithText(Person::getName)`
7575

76-
* [`ColumnFormat`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/ColumnFormat.html) and [`ColumnViewerFormat`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/jface/ColumnViewerFormat.html) - tables and trees without boilerplate
76+
* [`ColumnFormat`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/ColumnFormat.html) and [`ColumnViewerFormat`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/jface/ColumnViewerFormat.html) - tables and trees without boilerplate
7777

7878
```java
7979
ColumnViewerFormat<Person> format = ColumnViewerFormat.builder();
@@ -87,15 +87,15 @@ TreeViewer tree = format.buildTree(parent);
8787

8888
### Resource management
8989

90-
* [`OnePerWidget`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/OnePerWidget.html) - a cache tied to the lifetime of an SWT Widget.
91-
* [`ColorPool`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/ColorPool.html) - a pool of colors tied to the lifetime of a widget. `ColorPool.forWidget(widget).getColor(rgbValue)`
92-
* [`ImageDescriptors`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/jface/ImageDescriptors.html) - use ImageDescriptors with proper resource sharing. `ImageDescriptors.set(btn, imageDescriptor)`
90+
* [`OnePerWidget`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/OnePerWidget.html) - a cache tied to the lifetime of an SWT Widget.
91+
* [`ColorPool`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/ColorPool.html) - a pool of colors tied to the lifetime of a widget. `ColorPool.forWidget(widget).getColor(rgbValue)`
92+
* [`ImageDescriptors`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/jface/ImageDescriptors.html) - use ImageDescriptors with proper resource sharing. `ImageDescriptors.set(btn, imageDescriptor)`
9393

9494
### Interactive testing
9595

9696
Ideally, all UI code would have fully automated UI testing, but
9797
such tests are time-consuming to write, so they often just don't
98-
get written at all. [`InteractiveTest`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/InteractiveTest.html)
98+
get written at all. [`InteractiveTest`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/InteractiveTest.html)
9999
bridges the gap by making it easy to write user-in-the-loop guided tests. Furthermore,
100100
these tests can even be run in a [headless enviroment on a CI server](https://github.com/diffplug/durian-swt/blob/master/build.gradle#L66-L93), where the test UI
101101
will be opened, then automatically closed after a timeout. This ensures that the tests
@@ -127,23 +127,23 @@ InteractiveTest.testCoat(message, cmp -> {
127127

128128
### Miscellaneous stuff
129129

130-
* [`SwtMisc`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/SwtMisc.html) - useful static methods.
130+
* [`SwtMisc`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/SwtMisc.html) - useful static methods.
131131
+ `blockForError`, `blockForSuccess`, `blockForQuestion`, etc. - opens a dialog and blocks for the user's response, can be called from any thread.
132132
+ `loopUntil`, `loopUntilDisposed`, `loopUntilGet` - spins the SWT display loop until some condition is satisfied.
133133
+ `systemFontHeight/Width`, `scaleByFont`, `scaleByFontHeight` - resolution-independent sizes.
134134
+ `treeDefControl`, `treeDefComposite` - a [`TreeDef`](http://diffplug.github.io/durian/javadoc/snapshot/com/diffplug/common/base/TreeDef.html) for traversing UI elements.
135135
+ `setEnabledDeep` - sets the enabled status of every child, grandchild, etc. of the given composite.
136-
* [`SwtRx`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/SwtRx.html) - methods for converting SWT events and models to RxJava Observables.
137-
* [`SwtDebug`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/SwtDebug.html) - utilities for debugging SWT events.
138-
* [`OS`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/os/OS.html), [`Arch`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/os/Arch.html), and [`SwtPlatform`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/os/SwtPlatform.html) - detect things about the running system, and manipulate the SWT jars for build tools.
136+
* [`SwtRx`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/SwtRx.html) - methods for converting SWT events and models to RxJava Observables.
137+
* [`SwtDebug`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/SwtDebug.html) - utilities for debugging SWT events.
138+
* [`OS`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/os/OS.html), [`Arch`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/os/Arch.html), and [`SwtPlatform`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/os/SwtPlatform.html) - detect things about the running system, and manipulate the SWT jars for build tools.
139139
+ These do not require SWT or JFace, so you can add DurianSwt to your gradle or maven dependencies without needing to also figure out the SWT messiness.
140140
+ You can also just copy-paste these straight into your own code - they have no external dependencies.
141141
```java
142142
String installerExtension = OS.getNative().winMacLinux("exe","dmg","sh");
143143
String helperBinary = "driver_" + Arch.getNative().x86x64("", "_64") + ".dll";
144144
String swtJarName = "org.eclipse.swt." + SwtPlatform.getRunning();
145145
```
146-
* [`ViewerMisc`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.0.1/com/diffplug/common/swt/jface/ViewerMisc.html) - useful static methods for JFace viewers.
146+
* [`ViewerMisc`](https://javadoc.io/static/com.diffplug.durian/durian-swt/5.2.0/com/diffplug/common/swt/jface/ViewerMisc.html) - useful static methods for JFace viewers.
147147
+ `singleSelection`, `multiSelection` - returns an RxBox for listening to and setting the selection of a viewer.
148148
+ `setTreeContentProvider`, `setLazyTreeContentProvider` - uses a TreeDef to provide the content of a TreeViewer.
149149

build.gradle

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,8 @@ subprojects { subProject ->
2020

2121
ext.maven_name = subProject.name
2222
ext.javadoc_links = [
23-
"https://javadoc.io/doc/com.diffplug.durian/durian-core/${VER_DURIAN}",
2423
"https://javadoc.io/doc/com.diffplug.durian/durian-collect/${VER_DURIAN}",
2524
"https://javadoc.io/doc/com.diffplug.durian/durian-concurrent/${VER_DURIAN}",
26-
"https://javadoc.io/doc/com.diffplug.durian/durian-debug/${VER_DURIAN_DEBUG}",
2725
"https://javadoc.io/doc/com.diffplug.durian/durian-rx/${VER_DURIAN_RX}",
2826
'https://docs.oracle.com/javase/8/docs/api/'
2927
].join(' ')
@@ -37,6 +35,15 @@ subprojects { subProject ->
3735
if (subProject.name == 'durian-swt') {
3836
// configured there
3937
} else if (subProject.name == 'durian-swt.os') {
38+
// not sure why this spotless black for durian-swt.os is required
39+
spotless {
40+
groovyGradle {
41+
clearSteps()
42+
}
43+
java {
44+
clearSteps()
45+
}
46+
}
4047
dependencies {
4148
compileOnly 'com.google.code.findbugs:jsr305:3.0.2'
4249
testImplementation "junit:junit:$VER_JUNIT"
@@ -47,7 +54,7 @@ subprojects { subProject ->
4754
}
4855
} else {
4956
String platformCode = project.name.substring('durian-swt.'.length())
50-
String SWT_TO_USE = platformCode.endsWith("x86") ? SWT_VERSION_X86 : SWT_VERSION
57+
String SWT_TO_USE = platformCode.endsWith("x86") ? VER_ECLIPSE_PLATFORM_X86 : VER_ECLIPSE_PLATFORM
5158
apply plugin: 'dev.equo.p2deps'
5259
p2deps {
5360
into 'api', {
@@ -60,6 +67,7 @@ subprojects { subProject ->
6067
}
6168
dependencies {
6269
api project(':durian-swt')
70+
implementation "com.diffplug.durian:durian-core:$VER_DURIAN"
6371
}
6472
configurations.all {
6573
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+
}
Binary file not shown.

0 commit comments

Comments
 (0)