Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/build-natives.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ jobs:
shell: cmd
run: call native-ssl\src\main\native\windows\build.bat

- name: Build native-proxy Windows DLLs
if: steps.natives-cache.outputs.cache-hit != 'true'
shell: cmd
run: call native-proxy\src\main\native\windows\build.bat

- name: Build decorated-window-jni Windows DLLs
if: steps.natives-cache.outputs.cache-hit != 'true'
shell: cmd
Expand Down Expand Up @@ -139,6 +144,7 @@ jobs:
FILES=(
"darkmode-detector/nucleus_windows_theme.dll"
"native-ssl/nucleus_ssl.dll"
"native-proxy/nucleus_proxy.dll"
"decorated-window-jni/nucleus_windows_decoration.dll"
"system-color/nucleus_systemcolor.dll"
"energy-manager/nucleus_energy_manager.dll"
Expand Down Expand Up @@ -209,6 +215,10 @@ jobs:
if: steps.natives-cache.outputs.cache-hit != 'true'
run: bash native-ssl/src/main/native/macos/build.sh

- name: Build native-proxy macOS dylibs
if: steps.natives-cache.outputs.cache-hit != 'true'
run: bash native-proxy/src/main/native/macos/build.sh

- name: Build decorated-window-jbr macOS dylibs
if: steps.natives-cache.outputs.cache-hit != 'true'
run: bash decorated-window-jbr/src/main/native/macos/build.sh
Expand Down Expand Up @@ -289,6 +299,7 @@ jobs:
FILES=(
"darkmode-detector/libnucleus_darkmode.dylib"
"native-ssl/libnucleus_ssl.dylib"
"native-proxy/libnucleus_proxy.dylib"
"decorated-window-jbr/libnucleus_macos.dylib"
"decorated-window-jni/libnucleus_macos_jni.dylib"
"system-color/libnucleus_systemcolor.dylib"
Expand Down Expand Up @@ -377,6 +388,10 @@ jobs:
if: steps.natives-cache.outputs.cache-hit != 'true'
run: bash linux-hidpi/src/main/native/linux/build.sh

- name: Build native-proxy Linux native shared library
if: steps.natives-cache.outputs.cache-hit != 'true'
run: bash native-proxy/src/main/native/linux/build.sh

- name: Build system-color Linux native shared library
if: steps.natives-cache.outputs.cache-hit != 'true'
run: bash system-color/src/main/native/linux/build.sh
Expand Down Expand Up @@ -438,6 +453,7 @@ jobs:
"darkmode-detector/libnucleus_linux_theme.so"
"decorated-window-jni/libnucleus_linux_jni.so"
"linux-hidpi/libnucleus_linux_hidpi_jni.so"
"native-proxy/libnucleus_proxy.so"
"system-color/libnucleus_systemcolor.so"
"energy-manager/libnucleus_energy_manager.so"
"notification-linux/libnucleus_notification_linux.so"
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/pre-merge.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ jobs:
"native-ssl/src/main/resources/nucleus/native/darwin-x64/libnucleus_ssl.dylib"
"native-ssl/src/main/resources/nucleus/native/win32-x64/nucleus_ssl.dll"
"native-ssl/src/main/resources/nucleus/native/win32-aarch64/nucleus_ssl.dll"
"native-proxy/src/main/resources/nucleus/native/win32-x64/nucleus_proxy.dll"
"native-proxy/src/main/resources/nucleus/native/win32-aarch64/nucleus_proxy.dll"
"native-proxy/src/main/resources/nucleus/native/darwin-aarch64/libnucleus_proxy.dylib"
"native-proxy/src/main/resources/nucleus/native/darwin-x64/libnucleus_proxy.dylib"
"decorated-window-jbr/src/main/resources/nucleus/native/darwin-aarch64/libnucleus_macos.dylib"
"decorated-window-jbr/src/main/resources/nucleus/native/darwin-x64/libnucleus_macos.dylib"
"decorated-window-jni/src/main/resources/nucleus/native/darwin-aarch64/libnucleus_macos_jni.dylib"
Expand All @@ -59,6 +63,8 @@ jobs:
"decorated-window-jni/src/main/resources/nucleus/native/win32-aarch64/nucleus_windows_decoration.dll"
"linux-hidpi/src/main/resources/nucleus/native/linux-x64/libnucleus_linux_hidpi_jni.so"
"linux-hidpi/src/main/resources/nucleus/native/linux-aarch64/libnucleus_linux_hidpi_jni.so"
"native-proxy/src/main/resources/nucleus/native/linux-x64/libnucleus_proxy.so"
"native-proxy/src/main/resources/nucleus/native/linux-aarch64/libnucleus_proxy.so"
"system-color/src/main/resources/nucleus/native/linux-x64/libnucleus_systemcolor.so"
"system-color/src/main/resources/nucleus/native/linux-aarch64/libnucleus_systemcolor.so"
"system-color/src/main/resources/nucleus/native/darwin-aarch64/libnucleus_systemcolor.dylib"
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/publish-maven.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ jobs:
"native-ssl/src/main/resources/nucleus/native/darwin-x64/libnucleus_ssl.dylib"
"native-ssl/src/main/resources/nucleus/native/win32-x64/nucleus_ssl.dll"
"native-ssl/src/main/resources/nucleus/native/win32-aarch64/nucleus_ssl.dll"
"native-proxy/src/main/resources/nucleus/native/win32-x64/nucleus_proxy.dll"
"native-proxy/src/main/resources/nucleus/native/win32-aarch64/nucleus_proxy.dll"
"native-proxy/src/main/resources/nucleus/native/darwin-aarch64/libnucleus_proxy.dylib"
"native-proxy/src/main/resources/nucleus/native/darwin-x64/libnucleus_proxy.dylib"
"decorated-window-jbr/src/main/resources/nucleus/native/darwin-aarch64/libnucleus_macos.dylib"
"decorated-window-jbr/src/main/resources/nucleus/native/darwin-x64/libnucleus_macos.dylib"
"decorated-window-jni/src/main/resources/nucleus/native/darwin-aarch64/libnucleus_macos_jni.dylib"
Expand All @@ -58,6 +62,8 @@ jobs:
"decorated-window-jni/src/main/resources/nucleus/native/win32-aarch64/nucleus_windows_decoration.dll"
"linux-hidpi/src/main/resources/nucleus/native/linux-x64/libnucleus_linux_hidpi_jni.so"
"linux-hidpi/src/main/resources/nucleus/native/linux-aarch64/libnucleus_linux_hidpi_jni.so"
"native-proxy/src/main/resources/nucleus/native/linux-x64/libnucleus_proxy.so"
"native-proxy/src/main/resources/nucleus/native/linux-aarch64/libnucleus_proxy.so"
"system-color/src/main/resources/nucleus/native/linux-x64/libnucleus_systemcolor.so"
"system-color/src/main/resources/nucleus/native/linux-aarch64/libnucleus_systemcolor.so"
"system-color/src/main/resources/nucleus/native/darwin-aarch64/libnucleus_systemcolor.dylib"
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ A multi-module Gradle plugin and runtime library toolkit for shipping production
- `system-color` - Reactive system accent color and high contrast detection via JNI
- `energy-manager` - Energy efficiency & screen-awake APIs
- `native-ssl` / `native-http` / `native-http-okhttp` / `native-http-ktor` - OS trust store integration
- `native-proxy` - OS proxy configuration via JNI (Windows: WinHTTP/WPAD/PAC; macOS: SCDynamicStore/CFNetwork PAC; Linux: GSettings/KDE/env)
- `linux-hidpi` - Native HiDPI scale detection on Linux
- `graalvm-runtime` - GraalVM native-image bootstrap
- `decorated-window-core` - Shared types, layout, styling (design-system agnostic)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ Each module is published independently to Maven Central — use them together or
| `nucleus.autolaunch` | Start the app at user login across all platforms |
| `nucleus.native-ssl` | OS trust store integration |
| `nucleus.native-http` | HTTP client with native SSL |
| `nucleus.native-proxy` | OS proxy configuration — WPAD/PAC (Windows), SCDynamicStore/PAC (macOS), GSettings/KDE/env (Linux) |
| `nucleus.linux-hidpi` | Native HiDPI scale detection on Linux |
| `nucleus.graalvm-runtime` | Native-image bootstrap, font fixes, automatic resource inclusion |

Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ tasks.register("preMerge") {
dependsOn(":native-http:check")
dependsOn(":native-http-okhttp:check")
dependsOn(":native-http-ktor:check")
dependsOn(":native-proxy:check")
dependsOn(":decorated-window-core:check")
dependsOn(":decorated-window-tao:check")
dependsOn(":decorated-window-jbr:check")
Expand Down
120 changes: 120 additions & 0 deletions native-proxy/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import org.apache.tools.ant.taskdefs.condition.Os
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
kotlin("jvm")
alias(libs.plugins.vanniktechMavenPublish)
}

val publishVersion =
providers
.environmentVariable("GITHUB_REF")
.orNull
?.removePrefix("refs/tags/v")
?: "1.0.0"

dependencies {
implementation(project(":core-runtime"))
testImplementation(libs.junit)
}

java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}

kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}

val buildNativeWindows by tasks.registering(Exec::class) {
description = "Compiles the C JNI bridge into Windows DLLs (x64 + ARM64)"
group = "build"
val nativeDir = file("src/main/native/windows")
val outputDir = file("src/main/resources/nucleus/native")
val checkFile = File(outputDir, "win32-x64/nucleus_proxy.dll")
onlyIf { Os.isFamily(Os.FAMILY_WINDOWS) && !checkFile.exists() }
inputs.dir(nativeDir)
outputs.dir(outputDir)
workingDir(nativeDir)
commandLine("cmd", "/c", File(nativeDir, "build.bat").absolutePath)
}

val buildNativeMacOs by tasks.registering(Exec::class) {
description = "Compiles the ObjC JNI bridge into macOS dylibs (arm64 + x86_64)"
group = "build"
val nativeDir = file("src/main/native/macos")
val outputDir = file("src/main/resources/nucleus/native")
val checkFile = File(outputDir, "darwin-aarch64/libnucleus_proxy.dylib")
onlyIf { Os.isFamily(Os.FAMILY_MAC) && !checkFile.exists() }
inputs.dir(nativeDir)
outputs.dir(outputDir)
workingDir(nativeDir)
commandLine("bash", File(nativeDir, "build.sh").absolutePath)
}

val buildNativeLinux by tasks.registering(Exec::class) {
description = "Compiles the C JNI bridge into a Linux shared library"
group = "build"
val nativeDir = file("src/main/native/linux")
val outputDir = file("src/main/resources/nucleus/native")
val arch = if (System.getProperty("os.arch") == "aarch64") "linux-aarch64" else "linux-x64"
val checkFile = File(outputDir, "$arch/libnucleus_proxy.so")
onlyIf { Os.isFamily(Os.FAMILY_UNIX) && !Os.isFamily(Os.FAMILY_MAC) && !checkFile.exists() }
inputs.dir(nativeDir)
outputs.dir(outputDir)
workingDir(nativeDir)
commandLine("bash", File(nativeDir, "build.sh").absolutePath)
}

tasks.processResources {
dependsOn(buildNativeWindows, buildNativeMacOs, buildNativeLinux)
}

tasks.configureEach {
if (name == "sourcesJar") {
dependsOn(buildNativeWindows, buildNativeMacOs, buildNativeLinux)
}
}

mavenPublishing {
coordinates("dev.nucleusframework", "nucleus.native-proxy", publishVersion)

pom {
name.set("Nucleus Native Proxy")
description.set(
"OS proxy configuration integration (WinHTTP/WPAD/PAC on Windows; " +
"SCDynamicStore/CFNetwork PAC on macOS; GSettings/KDE/env on Linux) " +
"for JVM desktop applications",
)
url.set("https://github.com/NucleusFramework/Nucleus")

licenses {
license {
name.set("MIT License")
url.set("https://opensource.org/licenses/MIT")
}
}

developers {
developer {
id.set("nucleusframework")
name.set("NucleusFramework")
url.set("https://github.com/NucleusFramework")
}
}

scm {
url.set("https://github.com/NucleusFramework/Nucleus")
connection.set("scm:git:git://github.com/NucleusFramework/Nucleus.git")
developerConnection.set("scm:git:ssh://git@github.com/NucleusFramework/Nucleus.git")
}
}

publishToMavenCentral()
if (project.hasProperty("signingInMemoryKey")) {
signAllPublications()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package dev.nucleusframework.nativeproxy

import java.net.InetAddress

/**
* A single entry of a proxy bypass list.
*
* The variants mirror Chromium's `SchemeHostPortMatcherRule` implementations.
*/
sealed interface BypassRule {
fun matches(
scheme: String,
host: String,
port: Int,
): Boolean

/** `*` — every URL bypasses the proxy. */
data object MatchAll : BypassRule {
override fun matches(
scheme: String,
host: String,
port: Int,
): Boolean = true
}

/**
* A hostname wildcard pattern, optionally restricted to a scheme and/or a port.
*
* `*` matches any run of characters and `?` a single one, so `*.corp.com`
* matches `a.corp.com` but not `corp.com` — exactly as `base::MatchPattern`.
*/
data class HostnamePattern(
val pattern: String,
val scheme: String? = null,
val port: Int? = null,
) : BypassRule {
private val regex = globToRegex(pattern)

override fun matches(
scheme: String,
host: String,
port: Int,
): Boolean {
if (this.scheme != null && this.scheme != scheme) return false
if (this.port != null && this.port != port) return false
return regex.matches(host)
}
}

/** An IP block in CIDR notation (`10.0.0.0/8`, `fe80::/10`). */
data class IpBlock(
val prefix: InetAddress,
val prefixBits: Int,
val scheme: String? = null,
) : BypassRule {
override fun matches(
scheme: String,
host: String,
port: Int,
): Boolean {
if (this.scheme != null && this.scheme != scheme) return false
val address = parseIpLiteral(host) ?: return false
return matchesCidr(address, prefix, prefixBits)
}
}
}

private fun globToRegex(pattern: String): Regex {
val builder = StringBuilder()
for (char in pattern) {
when (char) {
'*' -> builder.append(".*")
'?' -> builder.append('.')
else -> builder.append(Regex.escape(char.toString()))
}
}
return Regex(builder.toString())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package dev.nucleusframework.nativeproxy

/**
* Splits a `host`, `host:port` or `[v6]:port` authority, returning the port as
* raw text so callers decide whether a malformed port is fatal.
*
* A bare IPv6 literal without brackets carries no port: the last colon belongs
* to the address.
*/
internal fun splitHostPort(value: String): Pair<String, String?>? {
if (value.startsWith('[')) return splitBracketedHostPort(value)

val colon = value.lastIndexOf(':')
if (colon < 0 || value.indexOf(':') != colon) return value to null
return value.substring(0, colon) to value.substring(colon + 1)
}

private fun splitBracketedHostPort(value: String): Pair<String, String?>? {
val closing = value.indexOf(']')
if (closing < 0) return null

val host = value.substring(1, closing)
val tail = value.substring(closing + 1)
return when {
tail.isEmpty() -> host to null
tail.startsWith(':') -> host to tail.substring(1)
else -> null
}
}
Loading
Loading