diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 8abf6820..1d25e66b 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -44,7 +44,7 @@ jobs: with: java-version: 11 - name: Build - run: ./gradlew build -PskipAutostyle + run: ./gradlew build -PskipAutostyle --info linux: name: Linux (Java 8) @@ -58,7 +58,7 @@ jobs: with: java-version: 8 - name: Build - run: ./gradlew build -PskipAutostyle + run: ./gradlew build -PskipAutostyle --info macos: name: macOS (Java 11) @@ -72,4 +72,4 @@ jobs: with: java-version: 11 - name: Build - run: ./gradlew build -PskipAutostyle + run: ./gradlew build -PskipAutostyle --info diff --git a/.github/workflows/libs.yml b/.github/workflows/libs.yml index 7668824a..56828214 100644 --- a/.github/workflows/libs.yml +++ b/.github/workflows/libs.yml @@ -55,13 +55,13 @@ jobs: - name: Upload x86 artifact uses: actions/upload-artifact@v1 with: - name: darklaf-windows_x86.dll - path: windows/build/lib/main/release/x86/darklaf-windows.dll + name: windows-x86 + path: windows/build/libs/main/x86/darklaf-windows.dll - name: Upload x86-64 artifact uses: actions/upload-artifact@v1 with: - name: darklaf-windows_x86-64.dll - path: windows/build/lib/main/release/x86-64/darklaf-windows.dll + name: windows-x86-64 + path: windows/build/libs/main/x86-64/darklaf-windows.dll macOS: name: macOS (Java 11) @@ -79,5 +79,7 @@ jobs: - name: Upload artifact uses: actions/upload-artifact@v1 with: - name: libdarklaf-macos.dylib - path: macos/build/lib/main/release/stripped/libdarklaf-macos.dylib + name: macos-x86-64 + path: macos/build/libs/main/libdarklaf-macos.dylib + - name: Print library information + run: otool -l macos/build/libs/main/libdarklaf-macos.dylib diff --git a/build.gradle.kts b/build.gradle.kts index 5e0112a3..ef53a790 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -19,8 +19,9 @@ val enableGradleMetadata by props() val skipAutostyle by props() val String.v: String get() = rootProject.extra["$this.version"] as String +val projectVersion = "darklaf".v -val buildVersion = "darklaf".v + releaseParams.snapshotSuffix +val buildVersion = projectVersion + releaseParams.snapshotSuffix println("Building: Darklaf $buildVersion") println(" JDK: " + System.getProperty("java.home")) @@ -83,6 +84,24 @@ allprojects { mavenCentral() } + val githubAccessToken by props("") + + plugins.withType { + prebuildBinaries { + prebuildLibrariesFolder = "pre-build-libraries" + missingLibraryIsFailure = false + github { + user = "weisj" + repository = "darklaf" + workflow = "libs.yml" + branches = listOf("master", "v$projectVersion", projectVersion) + accessToken = githubAccessToken + manualDownloadUrl = + "https://github.com/weisJ/darklaf/actions?query=workflow%3A%22Build+Native+Libraries%22+is%3Asuccess+branch%3Amaster" + } + } + } + if (!skipAutostyle) { apply(plugin = "com.github.autostyle") autostyle { @@ -266,7 +285,8 @@ allprojects { configure { if (project.path.startsWith(":darklaf-dependencies-bom") || - project.path == ":") { + project.path == ":" + ) { // We don't it to Central for now return@configure } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 32d60eac..5dcba2a8 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -1,16 +1,28 @@ plugins { - `kotlin-dsl` + `java-gradle-plugin` + groovy } dependencies { - implementation(kotlin("gradle-plugin")) + implementation(platform("dev.nokee:nokee-gradle-plugins:0.4.0-60cff2b")) } repositories { mavenCentral() gradlePluginPortal() + maven { url = uri("https://dl.bintray.com/nokeedev/distributions") } + maven { url = uri("https://dl.bintray.com/nokeedev/distributions-snapshots") } } -configure { - experimentalWarning.set(false) +gradlePlugin { + plugins { + create("uber-jni-jar") { + id = "uber-jni-jar" + implementationClass = "UberJniJarPlugin" + } + create("use-prebuilt-binaries") { + id = "use-prebuilt-binaries" + implementationClass = "UsePrebuiltBinariesWhenUnbuildablePlugin" + } + } } diff --git a/buildSrc/src/main/groovy/CallableLogger.groovy b/buildSrc/src/main/groovy/CallableLogger.groovy new file mode 100644 index 00000000..f8e2b505 --- /dev/null +++ b/buildSrc/src/main/groovy/CallableLogger.groovy @@ -0,0 +1,14 @@ +import java.util.concurrent.Callable + +class CallableLogger extends OneTimeLogger implements Callable> { + + CallableLogger(Runnable logger) { + super(logger) + } + + @Override + List call() throws Exception { + super.log() + return Collections.emptyList() + } +} diff --git a/buildSrc/src/main/groovy/DownloadPrebuiltBinaryFromGitHubAction.groovy b/buildSrc/src/main/groovy/DownloadPrebuiltBinaryFromGitHubAction.groovy new file mode 100644 index 00000000..cf6ffe37 --- /dev/null +++ b/buildSrc/src/main/groovy/DownloadPrebuiltBinaryFromGitHubAction.groovy @@ -0,0 +1,351 @@ +import groovy.json.JsonOutput +import groovy.json.JsonSlurper +import groovy.transform.CompileStatic +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.Transformer +import org.gradle.api.tasks.OutputFile + +import java.nio.file.Files +import java.nio.file.Path +import java.nio.file.StandardCopyOption +import java.util.concurrent.locks.ReadWriteLock +import java.util.concurrent.locks.ReentrantReadWriteLock +import java.util.stream.Stream +import java.util.zip.ZipEntry +import java.util.zip.ZipFile + +@CompileStatic +class DownloadPrebuiltBinaryFromGitHubAction extends DefaultTask { + + private static final ReadWriteLock LOCK = new ReentrantReadWriteLock() + + private static final String VERSION_INFO_FILE_NAME = "github_artifact_versions.json" + private static final String TEMP_PATH = "tmp${File.separator}prebuild" + private static final String PRE_BUILD_PATH = "libs${File.separator}prebuild" + + private final OneTimeLogger tokenWarning = new OneTimeLogger.Static({ + error("""No github access token is specified. Latest artifacts will need to be included manually. + |The access token needs to have the 'read-public' property. Specify using: + | -PgithubAccessToken= + |or by setting + | githubAccessToken= + |inside the gradle.properties file. + |""".stripMargin()) + }, "missingTokenWarning") + private final OneTimeLogger useCachedWarning = new OneTimeLogger({ + log("Could not download artifact or artifact information. Using cached version") + }) + + private Map cacheInfo + + private String manualDownloadUrl = "" + private String user + private String repository + private String workflow + private List branches = [] + private boolean missingLibraryIsFailure + + private String githubAccessToken + private String variant + private Optional prebuiltBinary + + @OutputFile + File getPrebuiltBinaryFile() { + if (user == null) throw new GradleException("Github user isn't specified") + if (repository == null) repository = project.name + if (workflow == null) throw new GradleException("Workflow isn't specified") + + if (prebuiltBinary == null) { + if (githubAccessToken == null || githubAccessToken.isEmpty()) { + tokenWarning.log() + } + prebuiltBinary = getExternalBinary(variant) + } + + return prebuiltBinary.orElseGet { + String errorMessage = """Library for $variant could not be downloaded. + |Download it from $manualDownloadUrl + |""".stripMargin() + + if (missingLibraryIsFailure) { + throw new GradleException(format(errorMessage)) + } else { + new OneTimeLogger.Static({ + error(errorMessage) + }, "${variant}-missing").log() + } + return createDirectory(tempFilePath("dummy/")) + } + } + + void setMissingLibraryIsFailure(boolean missingLibraryIsFailure) { + this.missingLibraryIsFailure = missingLibraryIsFailure + } + + void setGithubAccessToken(String githubAccessToken) { + this.githubAccessToken = githubAccessToken + } + + void setVariant(String variant) { + this.variant = variant + } + + void setUser(String user) { + this.user = user + } + + void setRepository(String repository) { + this.repository = repository + } + + void setWorkflow(String workflow) { + this.workflow = workflow + } + + void setManualDownloadUrl(String manualDownloadUrl) { + this.manualDownloadUrl = manualDownloadUrl + } + + void setBranches(List branches) { + this.branches = branches + } + + private Map getCacheInfo() { + if (cacheInfo == null) { + LOCK.readLock().lock() + try { + File cacheInfoFile = getCacheInfoFile() + JsonSlurper jsonParser = new JsonSlurper() + cacheInfo = jsonParser.parseText(cacheInfoFile.text) as Map + } finally { + LOCK.readLock().unlock() + } + } + return cacheInfo + } + + private File getCacheInfoFile() { + String path = preBuildPath(VERSION_INFO_FILE_NAME) + File cacheInfo = new File(path) + if (!cacheInfo.exists()) { + cacheInfo = createFile(path) + cacheInfo << "{}" + } + return cacheInfo + } + + private void writeToCache(String variantName, String timeStamp, File file) { + LOCK.writeLock().lock() + try { + Map cacheInfo = getCacheInfo() + Map entry = [timeStamp: timeStamp, path: file.absolutePath] + cacheInfo.put(variantName, entry) + getCacheInfoFile().write(JsonOutput.prettyPrint(JsonOutput.toJson(cacheInfo))) + } finally { + + } + } + + Optional getExternalBinary(String variant) { + Tuple2, Optional> fetchResult = getBinaryDownloadUrl(variant) + Optional downloadInfo = fetchResult.getFirst() + Optional cachedFile = fetchResult.getSecond() + if (cachedFile.isPresent()) { + log("Reusing previously downloaded binary ${cachedFile.map { it.absolutePath }.orElse(null)}") + return cachedFile + } + Optional downloadedFile = downloadInfo.map { + getBinaryFromUrl(variant, it.url).orElse(null) + } + + if (downloadedFile.isPresent()) { + writeToCache(variant, downloadInfo.get()?.timeStamp, downloadedFile.get()) + } else { + info("No file found for variant $variant") + } + + if (downloadedFile.isPresent()) return downloadedFile + return getCachedFile(variant) + } + + private Optional getBinaryFromUrl(String variant, String url) { + File directory = createDirectory(preBuildPath(variant)) + info("Downloading binary for variant '$variant' from $url") + Optional file = downloadZipFile(url, variant).map { unzip(it, directory).findFirst() }.orElse(Optional.empty()) + info("Finished download for variant '$variant'") + return file + } + + private String preBuildPath(String variant) { + return "${project.buildDir}${File.separator}$PRE_BUILD_PATH${File.separator}$variant" + } + + private Optional downloadZipFile(String url, String variant) { + return fetch(url) { + File file = createFile(zipPath(variant)) + Path response = file.toPath() + Files.copy(it.getInputStream(), response, StandardCopyOption.REPLACE_EXISTING) + return new ZipFile(file) + } + } + + private String zipPath(String name) { + return tempFilePath("${name}.zip") + } + + private String tempFilePath(String name) { + return "$project.buildDir${File.separator}$TEMP_PATH${File.separator}${name}" + } + + private static Stream unzip(ZipFile self, File directory) { + Collection files = self.entries().findAll { !(it as ZipEntry).directory } + return files.stream().map { + ZipEntry e = it as ZipEntry + e.name.with { fileName -> + File outputFile = createFile("${directory.path}$File.separator$fileName") + Files.copy(self.getInputStream(e), outputFile.toPath(), StandardCopyOption.REPLACE_EXISTING) + return outputFile + } + } + } + + private Tuple2, Optional> getBinaryDownloadUrl(String variantName) { + boolean isUptoDate = false + File cachedFile = null + String timeStamp = null + String artifactUrl = getLatestRun(getJson(getWorkflowsUrl())).with { + timeStamp = it.get("created_at") + Optional cachedFilePath = getCachedFilePath(variantName, timeStamp) + isUptoDate = cachedFilePath.isPresent() + if (isUptoDate) { + cachedFile = new File(cachedFilePath.get()) + isUptoDate = cachedFile.exists() + } + return get("artifacts_url") as String + } + info("Latest artifact for variant '$variantName' is from $timeStamp") + if (isUptoDate) { + return new Tuple2<>(Optional.empty(), Optional.of(cachedFile)) + } + DownloadInfo downloadInfo = artifactUrl?.with { url -> + Map[] artifacts = getJson(url).get("artifacts") as Map[] + String artifactDownloadUrl = artifacts?.find { variantName == it.get("name") }?.get("url") as String + return artifactDownloadUrl?.with { + new DownloadInfo(getJson(it)?.get("archive_download_url") as String, timeStamp) + } + } + + return new Tuple2<>(Optional.ofNullable(downloadInfo), Optional.empty()) + } + + private String getWorkflowsUrl() { + return "https://api.github.com/repos/$user/$repository/actions/workflows/$workflow/runs" + } + + private Optional getCachedFilePath(String variantName, String timeStamp) { + Map cacheInfo = getCacheInfo() + boolean isLatest = (cacheInfo[variantName] as Map)?.get("timeStamp") == timeStamp + if (isLatest) { + return Optional.ofNullable((cacheInfo[variantName] as Map)?.get("path") as String) + } else { + return Optional.empty() + } + } + + private Optional getCachedFile(String variant) { + Map cacheInfo = getCacheInfo() + return Optional.ofNullable(cacheInfo[variant] as Map).map { + return new File(String.valueOf(it["path"])).with { + if (it.exists()) useCachedWarning.log() + it.exists() ? it : null + } + } + } + + private Map getLatestRun(Map json) { + Map[] runs = json.get("workflow_runs") as Map[] + return Optional.ofNullable(runs?.find { run -> + boolean completed = "completed" == run.get("status") + boolean success = "success" == run.get("conclusion") + boolean isCorrectBranch = branches.isEmpty() || branches.contains(run.get("head_branch")?.toString()) + return completed && success && isCorrectBranch + }).orElseGet { + log("No suitable workflow run found.") + return Collections.emptyMap() + } + } + + private Map getJson(String url) { + return fetch(url) { + JsonSlurper jsonParser = new JsonSlurper() + Map parsedJson = jsonParser.parseText(it.getInputStream().getText()) as Map + return parsedJson + }.orElse(Collections.emptyMap()) + } + + private Optional fetch(String url, Transformer transformer) { + info("Fetching $url") + if (isOffline()) return Optional.empty() + HttpURLConnection get = new URL(url).openConnection() as HttpURLConnection + get.setRequestMethod("GET") + githubAccessToken?.with { + get.setRequestProperty("Authorization", "token $it") + } + try { + def responseCode = get.getResponseCode() + if (responseCode == HttpURLConnection.HTTP_OK) { + return Optional.ofNullable(transformer.transform(get)) + } else { + log("Could not fetch $url. Response code '$responseCode'.") + } + } catch (IOException ignored) { + } + return Optional.empty() + } + + private static File createFile(String fileName) { + File file = new File(fileName) + if (file.exists()) file.delete() + file.getParentFile().mkdirs() + file.createNewFile() + return file + } + + private static File createDirectory(String fileName) { + File file = new File(fileName) + file.mkdirs() + return file + } + + private boolean isOffline() { + return project.getGradle().startParameter.isOffline() + } + + private void info(String message) { + project.logger.info(format(message)) + } + + private void log(String message) { + project.logger.warn(format(message)) + } + + private void error(String message) { + project.logger.error(format(message)) + } + + private String format(String message) { + String pad = " " * (project.name.size() + 2) + return "${project.name}: ${message.replace("\n", "\n$pad")}" + } + + private class DownloadInfo { + protected String url + protected String timeStamp + + private DownloadInfo(String url, String timeStamp) { + this.url = url + this.timeStamp = timeStamp + } + } +} diff --git a/buildSrc/src/main/groovy/JniUtils.groovy b/buildSrc/src/main/groovy/JniUtils.groovy new file mode 100644 index 00000000..b934581d --- /dev/null +++ b/buildSrc/src/main/groovy/JniUtils.groovy @@ -0,0 +1,31 @@ +import dev.nokee.platform.nativebase.OperatingSystemFamily +import dev.nokee.platform.nativebase.TargetMachine +import org.gradle.api.GradleException +import org.gradle.api.Project + +class JniUtils { + static String asVariantName(TargetMachine targetMachine) { + String operatingSystemFamily = 'macos' + if (targetMachine.operatingSystemFamily.windows) { + operatingSystemFamily = 'windows' + } + + String architecture = 'x86-64' + if (targetMachine.architecture.'32Bit') { + architecture = 'x86' + } + + return "$operatingSystemFamily-$architecture" + } + + static String getLibraryFileNameFor(Project project, OperatingSystemFamily osFamily) { + if (osFamily.windows) { + return "${project.name}.dll" + } else if (osFamily.linux) { + return "lib${project.name}.so" + } else if (osFamily.macOS) { + return "lib${project.name}.dylib" + } + throw new GradleException("Unknown operating system family '${osFamily}'.") + } +} diff --git a/buildSrc/src/main/groovy/OneTimeLogger.groovy b/buildSrc/src/main/groovy/OneTimeLogger.groovy new file mode 100644 index 00000000..8a906e10 --- /dev/null +++ b/buildSrc/src/main/groovy/OneTimeLogger.groovy @@ -0,0 +1,45 @@ +class OneTimeLogger { + private final Runnable logger + private boolean messageAlreadyLogged = false + + OneTimeLogger(Runnable logger) { + this.logger = logger + } + + protected boolean isLogged() { + return messageAlreadyLogged + } + + protected void setLogged(boolean logged) { + messageAlreadyLogged = logged + } + + protected void log() { + if (!isLogged()) { + logger.run() + setLogged(true) + } + } + + static class Static extends OneTimeLogger { + + private static final Map isLogged = new HashMap<>() + + private final Object identifier + + Static(Runnable logger, Object identifier) { + super(logger) + this.identifier = identifier + } + + @Override + protected void setLogged(boolean logged) { + isLogged.put(identifier, logged) + } + + @Override + protected boolean isLogged() { + return Boolean.TRUE == isLogged.get(identifier) + } + } +} diff --git a/buildSrc/src/main/groovy/UberJniJarPlugin.groovy b/buildSrc/src/main/groovy/UberJniJarPlugin.groovy new file mode 100644 index 00000000..b1d8990d --- /dev/null +++ b/buildSrc/src/main/groovy/UberJniJarPlugin.groovy @@ -0,0 +1,66 @@ +import dev.nokee.platform.jni.JniJarBinary +import dev.nokee.platform.jni.JniLibrary +import dev.nokee.platform.jni.JniLibraryExtension +import dev.nokee.platform.nativebase.TargetMachine +import groovy.transform.CompileStatic +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.Transformer +import org.gradle.api.file.CopySpec +import org.gradle.api.provider.Provider +import org.gradle.jvm.tasks.Jar + +@CompileStatic +class UberJniJarPlugin implements Plugin { + + @Override + void apply(Project project) { + project.tasks.named('jar', Jar) { task -> + configure(task) + } + } + + private static void configure(Jar task) { + def project = task.getProject() + def logger = task.getLogger() + def library = project.extensions.getByType(JniLibraryExtension) + library.binaries.withType(JniJarBinary).configureEach { + if (it.jarTask.isPresent()) it.jarTask.get()?.enabled = false + } + if (library.targetMachines.get().size() >= 1) { + logger.info("${project.name}: Merging binaries into the JVM Jar.") + for (TargetMachine targetMachine : library.targetMachines.get()) { + Provider variant = library.variants + .flatMap(targetMachineOf(targetMachine)) + .map(onlyOne() as Transformer>) as Provider + task.into(variant.map { it.resourcePath }) { CopySpec spec -> + spec.from(variant.map { it.nativeRuntimeFiles }) + } + } + } + } + + // Filter variants that match the specified target machine. + private static Transformer, JniLibrary> targetMachineOf(TargetMachine targetMachine) { + return new Transformer, JniLibrary>() { + @Override + Iterable transform(JniLibrary variant) { + if (variant.targetMachine == targetMachine) { + return [variant] + } + return [] + } + } + } + + // Ensure only a single variant is present in the collection and return the variant. + private static Transformer> onlyOne() { + return new Transformer>() { + @Override + JniLibrary transform(List variants) { + assert variants.size() == 1 + return variants.first() + } + } + } +} diff --git a/buildSrc/src/main/groovy/UsePrebuiltBinariesWhenUnbuildablePlugin.groovy b/buildSrc/src/main/groovy/UsePrebuiltBinariesWhenUnbuildablePlugin.groovy new file mode 100644 index 00000000..d1504dc9 --- /dev/null +++ b/buildSrc/src/main/groovy/UsePrebuiltBinariesWhenUnbuildablePlugin.groovy @@ -0,0 +1,164 @@ +import dev.nokee.platform.jni.JniLibraryExtension +import groovy.transform.CompileStatic +import org.gradle.api.Action +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.plugins.ExtensionAware + +@CompileStatic +class UsePrebuiltBinariesWhenUnbuildablePlugin implements Plugin { + + private PrebuildBinariesExtension prebuildExtension + private GithubArtifactExtension githubArtifactExtension + + void prebuildBinaries(Action action) { + action.execute(prebuildExtension) + } + + PrebuildBinariesExtension getPrebuildBinaries() { + return prebuildExtension + } + + @Override + void apply(Project project) { + JniLibraryExtension library = project.extensions.getByType(JniLibraryExtension) + prebuildExtension = project.extensions.create("prebuildBinaries", PrebuildBinariesExtension, this) + githubArtifactExtension = (prebuildExtension as ExtensionAware).with { + it.extensions.create("github", GithubArtifactExtension) + } + library.variants.configureEach { var -> + if (prebuildExtension.alwaysUsePrebuildArtifact || !var.sharedLibrary.buildable) { + // Try to include the library file... if available + def defaultLibraryName = JniUtils.getLibraryFileNameFor(project, var.targetMachine.operatingSystemFamily) + def variantName = JniUtils.asVariantName(var.targetMachine) + def libraryFile = project.file( + "${prebuildExtension.prebuildLibrariesFolder}/$variantName/$defaultLibraryName" + ) + + if (!libraryFile.exists()) { + // No local binary provided. Try to download it from github actions. + def prebuiltBinariesTask = project.tasks.register("downloadPrebuiltBinary$variantName", DownloadPrebuiltBinaryFromGitHubAction.class) + prebuiltBinariesTask.configure { + it.githubAccessToken = githubArtifactExtension.accessToken + it.variant = variantName + it.user = githubArtifactExtension.user + it.repository = githubArtifactExtension.repository + it.workflow = githubArtifactExtension.workflow + it.manualDownloadUrl = githubArtifactExtension.manualDownloadUrl + it.branches = githubArtifactExtension.branches + it.missingLibraryIsFailure = prebuildExtension.missingLibraryIsFailure + } + var.nativeRuntimeFiles.setFrom(prebuiltBinariesTask.map { it.prebuiltBinaryFile }) + var.nativeRuntimeFiles.from(new CallableLogger({ + project.logger.warn("${project.name}: Using pre-build library from github for targetMachine $variantName.") + })) + } else { + //Use provided library. + var.nativeRuntimeFiles.setFrom(libraryFile) + var.nativeRuntimeFiles.from(new CallableLogger({ + def relativePath = project.rootProject.relativePath(libraryFile) + project.logger.warn("${project.name}: Using pre-build library $relativePath for targetMachine $variantName.") + })) + } + } + } + } + + static class PrebuildBinariesExtension { + + private String prebuildLibrariesFolder = "pre-build-libraries" + private boolean alwaysUsePrebuildArtifact = false + private boolean missingLibraryIsFailure = true + private UsePrebuiltBinariesWhenUnbuildablePlugin plugin + + PrebuildBinariesExtension(UsePrebuiltBinariesWhenUnbuildablePlugin plugin) { + this.plugin = plugin + } + + void github(Action action) { + action.execute(plugin.githubArtifactExtension) + } + + void setAlwaysUsePrebuildArtifact(boolean alwaysUsePrebuildArtifact) { + this.alwaysUsePrebuildArtifact = alwaysUsePrebuildArtifact + } + + boolean getAlwaysUsePrebuildArtifact() { + return alwaysUsePrebuildArtifact + } + + String getPrebuildLibrariesFolder() { + return prebuildLibrariesFolder + } + + boolean getMissingLibraryIsFailure() { + return missingLibraryIsFailure + } + + void setPrebuildLibrariesFolder(String prebuildLibrariesFolder) { + this.prebuildLibrariesFolder = prebuildLibrariesFolder + } + + void setMissingLibraryIsFailure(boolean missingLibraryIsFailure) { + this.missingLibraryIsFailure = missingLibraryIsFailure + } + } + + static class GithubArtifactExtension { + private String user + private String repository + private String workflow + private String manualDownloadUrl + private String accessToken + private List branches = ["master"] + + String getUser() { + return user + } + + String getRepository() { + return repository + } + + String getWorkflow() { + return workflow + } + + String getManualDownloadUrl() { + return manualDownloadUrl + } + + String getAccessToken() { + return accessToken + } + + List getBranches() { + return branches + } + + void setUser(String user) { + this.user = user + } + + void setRepository(String repository) { + this.repository = repository + } + + void setWorkflow(String workflow) { + this.workflow = workflow + } + + void setManualDownloadUrl(String manualDownloadUrl) { + this.manualDownloadUrl = manualDownloadUrl + } + + void setAccessToken(String accessToken) { + this.accessToken = accessToken + } + + void setBranches(List branches) { + this.branches = branches + } + } + +} diff --git a/buildSrc/src/main/kotlin/MacOSSdkPathTask.kt b/buildSrc/src/main/kotlin/MacOSSdkPathTask.kt deleted file mode 100644 index 00571ff5..00000000 --- a/buildSrc/src/main/kotlin/MacOSSdkPathTask.kt +++ /dev/null @@ -1,18 +0,0 @@ -import org.gradle.api.DefaultTask -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.TaskAction -import org.gradle.nativeplatform.toolchain.internal.xcode.MacOSSdkPathLocator -import java.io.File -import javax.inject.Inject - -open class MacOSSdkPathTask @Inject constructor( - private val locator: MacOSSdkPathLocator -) : DefaultTask() { - @Internal - lateinit var sdkPath: File - - @TaskAction - fun run() { - sdkPath = locator.find() - } -} diff --git a/buildSrc/src/main/kotlin/jni-library.gradle.kts b/buildSrc/src/main/kotlin/jni-library.gradle.kts deleted file mode 100644 index ebdb8100..00000000 --- a/buildSrc/src/main/kotlin/jni-library.gradle.kts +++ /dev/null @@ -1,126 +0,0 @@ -import org.gradle.internal.jvm.Jvm -import org.gradle.kotlin.dsl.invoke - -plugins { - `cpp-library` - `java-library` -} - -// This configuration might be used for adding cpp-only dependencies -val jniImplementation by configurations.creating - -val defaultLibraryName: String by project - -configurations.matching { - it.name.startsWith("cppCompile") || - it.name.startsWith("nativeLink") || - it.name.startsWith("nativeRuntime") -}.all { - extendsFrom(jniImplementation) -} - -tasks.compileJava { - options.headerOutputDirectory.convention( - project.layout.buildDirectory.dir("generated/jni-headers") - ) - // The nested output is not marked automatically as an output of the task regarding task dependencies. - // So we mark it manually here. - // See https://github.com/gradle/gradle/issues/6619. - outputs.dir(options.headerOutputDirectory) - // Cannot do incremental header generation, since the pattern for cleaning them up is currently wrong. - // See https://github.com/gradle/gradle/issues/12084. - options.isIncremental = false -} - -tasks.withType().configureEach { - includes(tasks.compileJava.flatMap { it.options.headerOutputDirectory }) -} - -library { - binaries.configureEach { - val targetOs = targetMachine.operatingSystemFamily - compileTask.get().apply { - val javaHome = Jvm.current().javaHome.canonicalPath - includes("$javaHome/include") - includes(when { - targetOs.isMacOs -> listOf("$javaHome/include/darwin") - targetOs.isLinux -> listOf("$javaHome/include/linux") - targetOs.isWindows -> listOf("$javaHome/include/win32") - else -> emptyList() - }) - } - } -} - -/** - * Gradle does not support [Provider] for [JavaForkOptions.systemProperty], so - * we pass an object that overrides [Any.toString]. - */ -fun Provider.overrideToString() = object { - override fun toString() = orNull ?: "" -} - -val TargetMachine.variantName: String get() = "$operatingSystemFamily-$architecture" - -// Gradle populates library.binaries in afterEvaluate, so we can't access it earlier -afterEvaluate { - // C++ library is built for Windows/macOS only, so we skip it otherwise - library.developmentBinary.orNull?.let { it as CppSharedLibrary }?.let { developmentBinary -> - tasks.test { - dependsOn(developmentBinary.linkTask) - val libraryDir = developmentBinary.runtimeFile - .map { it.asFile.parentFile.absolutePath } - systemProperty("java.library.path", libraryDir.overrideToString()) - } - } - tasks.jar { - //Disable all task. Tasks are reenabled if needed. - library.binaries.get().forEach { - it.compileTask.get().enabled = false - } - library.binaries.get() - .filter { it.isOptimized } - .filterIsInstance().let { - val taskMap = it.map { binary -> binary.targetPlatform.targetMachine to binary }.toMap() - library.targetMachines.get().forEach { targetMachine -> - val libraryPath = "com/github/weisj/darklaf/platform/${project.name}" - val variantName = targetMachine.variantName - val libraryFile = file("libraries/$variantName/$defaultLibraryName") - val relativePath = rootProject.relativePath(libraryFile) - when { - libraryFile.exists() -> { - //Use provided library. - logger.warn( - "${project.name}: Using pre-build library $relativePath for targetMachine $variantName." - ) - into("$libraryPath/$variantName") { - from(libraryFile) - } - } - targetMachine in taskMap -> { - taskMap[targetMachine]?.let { binary -> - binary.compileTask.get().enabled = true - // Publish optimized binary to reduce size. - binary.linkTask.get().debuggable.set(false) - //Build and copy library - dependsOn(binary.linkTask) - into("$libraryPath/$variantName") { - from(binary.runtimeFile) - } - } - } - else -> { - val downloadUrl = - "https://github.com/weisJ/darklaf/actions?query=workflow%3A%22Build+Native+Libraries%22+is%3Asuccess+branch%3Amaster" - logger.warn( - """ - ${project.name}: Library $relativePath for targetMachine $variantName does not exist. - ${" ".repeat(project.name.length + 1)} Download it from $downloadUrl - """.trimIndent() - ) - } - } - } - } - } -} diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 39d630c2..9bf805b8 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -1,6 +1,5 @@ plugins { `java-library` - id("com.github.johnrengelman.shadow") id("com.github.vlsi.crlf") } @@ -42,27 +41,9 @@ val fontTest by tasks.registering(JavaExec::class) { classpath(sourceSets.main.get().runtimeClasspath, sourceSets.test.get().runtimeClasspath) } -tasks.shadowJar { - exclude("help/") - exclude("icons/") - exclude("org/jdesktop/jxlayer/plaf/ext/images/") - exclude("com/sun/jna/darwin/") - exclude("com/sun/jna/freebsd-x86/") - exclude("com/sun/jna/freebsd-x86-64/") - exclude("com/sun/jna/linux-arm/") - exclude("com/sun/jna/linux-x86/") - exclude("com/sun/jna/linux-x86-64/") - exclude("com/sun/jna/openbsd-x86/") - exclude("com/sun/jna/openbsd-x86-64/") - exclude("com/sun/jna/sunos-sparc/") - exclude("com/sun/jna/sunos-sparcv9/") - exclude("com/sun/jna/sunos-x86/") - exclude("com/sun/jna/sunos-x86-64/") -} - abstract class DemoTask : JavaExec() { init { - main = "UIDemo" + main = "DemoLauncher" } @Option( diff --git a/core/src/main/java/com/github/weisj/darklaf/settings/ThemeSettingsPanel.java b/core/src/main/java/com/github/weisj/darklaf/settings/ThemeSettingsPanel.java index 6ac50b1f..b2132dbe 100644 --- a/core/src/main/java/com/github/weisj/darklaf/settings/ThemeSettingsPanel.java +++ b/core/src/main/java/com/github/weisj/darklaf/settings/ThemeSettingsPanel.java @@ -556,7 +556,7 @@ public class ThemeSettingsPanel extends JPanel { themeFollowsSystem = new JCheckBox(resourceBundle.getString("check_system_theme")) { @Override public void setEnabled(final boolean b) { - boolean enabled = b && ThemePreferencesHandler.getSharedInstance().supportsNativeFontSize(); + boolean enabled = b && ThemePreferencesHandler.getSharedInstance().supportsNativeTheme(); super.setEnabled(enabled); } }; diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/scrollpane/DarkMacScrollBarUI.java b/core/src/main/java/com/github/weisj/darklaf/ui/scrollpane/DarkMacScrollBarUI.java index 0edfad73..677d730c 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/scrollpane/DarkMacScrollBarUI.java +++ b/core/src/main/java/com/github/weisj/darklaf/ui/scrollpane/DarkMacScrollBarUI.java @@ -25,6 +25,7 @@ package com.github.weisj.darklaf.ui.scrollpane; import java.awt.*; +import java.awt.geom.RoundRectangle2D; import javax.swing.*; import javax.swing.plaf.ComponentUI; @@ -38,14 +39,23 @@ public class DarkMacScrollBarUI extends DarkScrollBarUI { return new DarkMacScrollBarUI(); } + @Override + protected void paintTrack(final Graphics g, final JComponent c, final Rectangle bounds) {} + @Override protected void paintMaxiThumb(final Graphics2D g, final Rectangle rect) { GraphicsContext context = GraphicsUtil.setupStrokePainting(g); - g.setComposite(COMPOSITE.derive(THUMB_ALPHA)); - g.setColor(getThumbColor()); + g.setComposite(COMPOSITE.derive(thumbAlpha)); boolean horizontal = scrollbar.getOrientation() == JScrollBar.HORIZONTAL; - int arc = horizontal ? (rect.height - 2) : (rect.width - 2); - g.fillRoundRect(rect.x + 1, rect.y + 1, rect.width - 2, rect.height - 2, arc, arc); + int ins = 2; + int arc = horizontal ? (rect.height - 2 * ins) : (rect.width - 2 * ins); + RoundRectangle2D roundRect = new RoundRectangle2D.Float(); + roundRect.setRoundRect(rect.x + ins, rect.y + ins, + rect.width - 2 * ins, rect.height - 2 * ins, arc, arc); + g.setColor(getThumbColor()); + g.fill(roundRect); + g.setColor(getThumbBorderColor()); + g.draw(roundRect); context.restore(); } } diff --git a/core/src/main/java/com/github/weisj/darklaf/ui/scrollpane/DarkScrollBarUI.java b/core/src/main/java/com/github/weisj/darklaf/ui/scrollpane/DarkScrollBarUI.java index 08e5b9ed..6de6af91 100644 --- a/core/src/main/java/com/github/weisj/darklaf/ui/scrollpane/DarkScrollBarUI.java +++ b/core/src/main/java/com/github/weisj/darklaf/ui/scrollpane/DarkScrollBarUI.java @@ -39,7 +39,6 @@ import com.github.weisj.darklaf.util.ColorUtil; */ public class DarkScrollBarUI extends BasicScrollBarUI implements ScrollBarConstants { - protected static final float THUMB_ALPHA = 0.6f; protected static final AlphaComposite COMPOSITE = AlphaComposite.getInstance(AlphaComposite.SRC_OVER); protected DarkScrollBarListener scrollBarListener; protected Color thumbBorderColor; @@ -48,6 +47,7 @@ public class DarkScrollBarUI extends BasicScrollBarUI implements ScrollBarConsta protected Color trackBackground; protected int smallSize; protected int size; + protected float thumbAlpha; public static ComponentUI createUI(final JComponent c) { return new DarkScrollBarUI(); @@ -77,6 +77,7 @@ public class DarkScrollBarUI extends BasicScrollBarUI implements ScrollBarConsta trackBackground = UIManager.getColor("ScrollBar.trackColor"); smallSize = UIManager.getInt("ScrollBar.smallWidth"); size = UIManager.getInt("ScrollBar.width"); + thumbAlpha = UIManager.getInt("ScrollBar.thumbAlpha") / 100.0f; } @Override @@ -148,14 +149,16 @@ public class DarkScrollBarUI extends BasicScrollBarUI implements ScrollBarConsta } protected void paintMaxiThumb(final Graphics2D g, final Rectangle rect) { - g.setComposite(COMPOSITE.derive(THUMB_ALPHA)); - Color thumbColor = getThumbColor(); - float thumbAlpha = scrollBarListener.getThumbAlpha(); - double percent = Math.min(1.0, Math.max(0.0, 1 - (thumbAlpha - THUMB_ALPHA))); - g.setColor(ColorUtil.blendColors(thumbBorderColor, thumbColor, percent)); + g.setComposite(COMPOSITE.derive(thumbAlpha)); + g.setColor(getThumbBorderColor()); PaintUtil.drawRect(g, rect.x, rect.y, rect.width, rect.height, 1); - g.setColor(thumbColor); - g.fillRect(rect.x + 1, rect.y + 1, rect.width - 2, rect.height - 2); + g.setColor(getThumbColor()); + PaintUtil.fillRect(g, rect.x + 1, rect.y + 1, rect.width - 2, rect.height - 2); + } + + protected Color getThumbBorderColor() { + double percent = Math.min(1.0, Math.max(0.0, 1 - (scrollBarListener.getThumbAlpha() - thumbAlpha))); + return ColorUtil.blendColors(thumbBorderColor, thumbColor, percent); } protected Color getThumbColor() { diff --git a/core/src/main/resources/com/github/weisj/darklaf/properties/platform/mac.properties b/core/src/main/resources/com/github/weisj/darklaf/properties/platform/mac.properties index 1dd23d7a..b071f72e 100644 --- a/core/src/main/resources/com/github/weisj/darklaf/properties/platform/mac.properties +++ b/core/src/main/resources/com/github/weisj/darklaf/properties/platform/mac.properties @@ -25,6 +25,9 @@ # suppress inspection "UnusedProperty" for whole file # ScrollBarUI = com.github.weisj.darklaf.ui.scrollpane.DarkMacScrollBarUI +ScrollBar.smallWidth = 10 +ScrollBar.width = 12 + Table.alternateRowColor = true Tree.alternateRowColor = true List.alternateRowColor = true diff --git a/core/src/main/resources/com/github/weisj/darklaf/properties/ui/scrollBar.properties b/core/src/main/resources/com/github/weisj/darklaf/properties/ui/scrollBar.properties index 1c645446..c34042a3 100644 --- a/core/src/main/resources/com/github/weisj/darklaf/properties/ui/scrollBar.properties +++ b/core/src/main/resources/com/github/weisj/darklaf/properties/ui/scrollBar.properties @@ -25,12 +25,13 @@ # suppress inspection "UnusedProperty" for whole file # ScrollBarUI = com.github.weisj.darklaf.ui.scrollpane.DarkScrollBarUI -ScrollBar.fadeStartColor = %controlFadeStart -ScrollBar.fadeEndColor = %controlFadeEnd +ScrollBar.fadeStartColor = %controlFadeStartSecondary +ScrollBar.fadeEndColor = %controlFadeEndSecondary ScrollBar.trackColor = %controlTrack ScrollBar.thumbBorderColor = %controlBorderSecondary ScrollBar.thumb = %controlFillSecondary ScrollBar.thumbShadow = null ScrollBar.thumbHighlight = null -ScrollBar.smallWidth = 10 -ScrollBar.width = 12 +ScrollBar.smallWidth = 8 +ScrollBar.width = 10 +ScrollBar.thumbAlpha = 60 diff --git a/core/src/test/java/ui/ComponentDemo.java b/core/src/test/java/ui/ComponentDemo.java index 6fd53718..c31b62ed 100644 --- a/core/src/test/java/ui/ComponentDemo.java +++ b/core/src/test/java/ui/ComponentDemo.java @@ -64,6 +64,8 @@ public interface ComponentDemo { } static void showDemo(final ComponentDemo demo, final Dimension dimension, final boolean asDialog) { + LafManager.enabledPreferenceChangeReporting(false); + System.setProperty("apple.laf.useScreenMenuBar", "true"); SwingUtilities.invokeLater(() -> { if (!LafManager.isInstalled()) { LafManager.install(demo.createTheme()); diff --git a/core/src/test/java/ui/PreferenceChangeDemo.java b/core/src/test/java/ui/PreferenceChangeDemo.java index 8d0005b9..256ba4fd 100644 --- a/core/src/test/java/ui/PreferenceChangeDemo.java +++ b/core/src/test/java/ui/PreferenceChangeDemo.java @@ -38,6 +38,7 @@ import com.github.weisj.darklaf.theme.Theme; public class PreferenceChangeDemo implements ComponentDemo { public static void main(final String[] args) { + LafManager.enabledPreferenceChangeReporting(false); ComponentDemo.showDemo(new PreferenceChangeDemo()); } diff --git a/gradle.properties b/gradle.properties index 56fcb4af..c133d9c7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,7 +11,6 @@ darklaf.version = 2.2.0 # Plugins shadow.version = 5.1.0 com.github.vlsi.vlsi-release-plugins.version = 1.70 -com.github.johnrengelman.shadow.version = 5.1.0 com.github.autostyle.version = 3.1 # Dependencies diff --git a/macos/build.gradle.kts b/macos/build.gradle.kts index 8f70e024..8c221e94 100644 --- a/macos/build.gradle.kts +++ b/macos/build.gradle.kts @@ -1,52 +1,51 @@ -plugins { - `jni-library` -} - -fun DependencyHandlerScope.javaImplementation(dep: Any) { - compileOnly(dep) - runtimeOnly(dep) -} - -dependencies { - javaImplementation(project(":darklaf-theme")) - javaImplementation(project(":darklaf-native-utils")) - javaImplementation(project(":darklaf-utils")) - javaImplementation(project(":darklaf-platform-base")) - javaImplementation(project(":darklaf-property-loader")) -} - -val macPath by tasks.registering(MacOSSdkPathTask::class) +import JniUtils.asVariantName -val sdkRoot: Provider get() = macPath.map { it.sdkPath.absolutePath } - -fun ListProperty.addJavaFrameworks() { - addAll("-framework", "JavaNativeFoundation") - add("-F") - add(sdkRoot.map { "$it/System/Library/Frameworks/JavaVM.framework/Frameworks" }) - add("-F") - add("/System/Library/Frameworks/JavaVM.framework/Frameworks") +plugins { + java + id("dev.nokee.jni-library") + id("dev.nokee.objective-cpp-language") + `uber-jni-jar` + `use-prebuilt-binaries` } library { - targetMachines.addAll(machines.macOS.x86_64) - binaries.configureEach { - compileTask.get().apply { - dependsOn(macPath) - compilerArgs.addAll("-x", "objective-c++") - compilerArgs.addAll("-mmacosx-version-min=10.10") - compilerArgs.addAll("-Wunguarded-availability") - compilerArgs.addJavaFrameworks() - source.from( - file("src/main/objectiveCpp/Decorations.mm"), - file("src/main/objectiveCpp/ThemeInfo.mm") - ) + val minOs = "10.10" + val frameworkVersion = "10.15" + + dependencies { + jvmImplementation(project(":darklaf-theme")) + jvmImplementation(project(":darklaf-native-utils")) + jvmImplementation(project(":darklaf-utils")) + jvmImplementation(project(":darklaf-platform-base")) + jvmImplementation(project(":darklaf-property-loader")) + nativeImplementation("dev.nokee.framework:JavaVM:[$frameworkVersion,)") + nativeImplementation("dev.nokee.framework:JavaVM:[$frameworkVersion,)") { + capabilities { + requireCapability("JavaVM:JavaNativeFoundation:[$frameworkVersion,)") + } } + nativeImplementation("dev.nokee.framework:AppKit:[$frameworkVersion,)") + nativeImplementation("dev.nokee.framework:Cocoa:[$frameworkVersion,)") } - binaries.whenElementFinalized(CppSharedLibrary::class) { - linkTask.get().apply { - dependsOn(macPath) - linkerArgs.addAll("-lobjc", "-framework", "AppKit") - linkerArgs.addJavaFrameworks() + + targetMachines.addAll(machines.macOS.x86_64) + variants.configureEach { + resourcePath.set("com/github/weisj/darklaf/platform/${project.name}/${asVariantName(targetMachine)}") + sharedLibrary { + compileTasks.configureEach { + compilerArgs.addAll("-mmacosx-version-min=$minOs") + // Build type not modeled yet, assuming release + compilerArgs.addAll(toolChain.map { + when (it) { + is Gcc, is Clang -> listOf("-O2") + is VisualCpp -> listOf("/O2") + else -> emptyList() + } + }) + } + linkTask.configure { + linkerArgs.addAll("-lobjc", "-mmacosx-version-min=$minOs") + } } } } diff --git a/macos/gradle.properties b/macos/gradle.properties deleted file mode 100644 index 31539f5c..00000000 --- a/macos/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -defaultLibraryName = libdarklaf-macos.dylib diff --git a/macos/libraries/macos-x86-64/library.md b/macos/pre-build-libraries/macos-x86-64/library.md similarity index 100% rename from macos/libraries/macos-x86-64/library.md rename to macos/pre-build-libraries/macos-x86-64/library.md diff --git a/macos/src/main/java/com/github/weisj/darklaf/platform/macos/JNIThemeInfoMacOS.java b/macos/src/main/java/com/github/weisj/darklaf/platform/macos/JNIThemeInfoMacOS.java index c54f16a9..271f3402 100644 --- a/macos/src/main/java/com/github/weisj/darklaf/platform/macos/JNIThemeInfoMacOS.java +++ b/macos/src/main/java/com/github/weisj/darklaf/platform/macos/JNIThemeInfoMacOS.java @@ -109,4 +109,8 @@ public class JNIThemeInfoMacOS { * @param listenerPtr pointer to the listener. */ public static native void deletePreferenceChangeListener(final long listenerPtr); + + public static native void patchAppBundle(final boolean isJava11OrOlder); + + public static native void unpatchAppBundle(); } diff --git a/macos/src/main/java/com/github/weisj/darklaf/platform/macos/MacOSLibrary.java b/macos/src/main/java/com/github/weisj/darklaf/platform/macos/MacOSLibrary.java index f587c690..62b17919 100644 --- a/macos/src/main/java/com/github/weisj/darklaf/platform/macos/MacOSLibrary.java +++ b/macos/src/main/java/com/github/weisj/darklaf/platform/macos/MacOSLibrary.java @@ -42,19 +42,8 @@ public class MacOSLibrary extends AbstractLibrary { super(PATH, DLL_NAME, LogUtil.getLogger(MacOSLibrary.class)); } - @Override - protected String getLibraryPath() { - if (!SystemInfo.isX64) { - logger.warning("JRE model '" - + SystemInfo.jreArchitecture - + "' not supported. Native features will be disabled"); - return null; - } - return super.getLibraryPath(); - } - @Override protected boolean canLoad() { - return SystemInfo.isMacOSYosemite; + return SystemInfo.isX64 && SystemInfo.isMacOSYosemite; } } diff --git a/macos/src/main/java/com/github/weisj/darklaf/platform/macos/MacOSThemePreferenceProvider.java b/macos/src/main/java/com/github/weisj/darklaf/platform/macos/MacOSThemePreferenceProvider.java index e41082b1..2af25538 100644 --- a/macos/src/main/java/com/github/weisj/darklaf/platform/macos/MacOSThemePreferenceProvider.java +++ b/macos/src/main/java/com/github/weisj/darklaf/platform/macos/MacOSThemePreferenceProvider.java @@ -27,6 +27,8 @@ package com.github.weisj.darklaf.platform.macos; import java.awt.*; import java.util.function.Consumer; +import javax.swing.*; + import com.github.weisj.darklaf.theme.info.*; import com.github.weisj.darklaf.util.SystemInfo; @@ -79,6 +81,17 @@ public class MacOSThemePreferenceProvider implements ThemePreferenceProvider { @Override public void initialize() { MacOSLibrary.get().updateLibrary(); + if (MacOSLibrary.get().isLoaded()) { + /* + * Patching the app bundle doesn't work anymore in JDK 14. I am not sure what the last version is, + * where it still works, so for now everything below or equal to JDK 11 will benefit from the newer + * catalina algorithm. For everything else we fall back to the Mojave style. This should still give a + * correct answer most of the time. + */ + JNIThemeInfoMacOS.patchAppBundle(!SystemInfo.isJavaVersionAtLeast("12")); + SwingUtilities.invokeLater(() -> { /* Do nothing. This simply forces native resources to be loaded */ }); + JNIThemeInfoMacOS.unpatchAppBundle(); + } } @Override diff --git a/macos/src/main/objectiveCpp/Decorations.mm b/macos/src/main/objcpp/Decorations.mm similarity index 100% rename from macos/src/main/objectiveCpp/Decorations.mm rename to macos/src/main/objcpp/Decorations.mm diff --git a/macos/src/main/objectiveCpp/ThemeInfo.mm b/macos/src/main/objcpp/ThemeInfo.mm similarity index 74% rename from macos/src/main/objectiveCpp/ThemeInfo.mm rename to macos/src/main/objcpp/ThemeInfo.mm index 1b485954..368d0e8e 100644 --- a/macos/src/main/objectiveCpp/ThemeInfo.mm +++ b/macos/src/main/objcpp/ThemeInfo.mm @@ -27,8 +27,9 @@ #define OBJC(jl) ((id)jlong_to_ptr(jl)) +#define NSRequiresAquaSystemAppearance CFSTR("NSRequiresAquaSystemAppearance") + #define KEY_APPLE_INTERFACE_STYLE @"AppleInterfaceStyle" -#define KEY_SWITCHES_AUTOMATICALLY @"AppleInterfaceStyleSwitchesAutomatically" #define KEY_ACCENT_COLOR @"AppleAccentColor" #define KEY_SELECTION_COLOR @"selectedTextBackgroundColor" #define KEY_SYSTEM_COLOR_LIST @"System" @@ -44,6 +45,9 @@ #define VALUE_NO_ACCENT_COLOR (-100) #define VALUE_NO_SELECTION_COLOR (-1) +BOOL patched = NO; +BOOL catalinaEnabled = NO; + @interface PreferenceChangeListener:NSObject { @public JavaVM *jvm; @public jobject callback; @@ -101,19 +105,35 @@ } - (void)notificationEvent:(NSNotification *)notification { - [self runCallback]; + [JNFRunLoop performOnMainThreadWaiting:NO withBlock:^{ + [self runCallback]; + }]; } @end +BOOL isDarkModeCatalina() { + NSAppearance *appearance = NSApp.effectiveAppearance; + NSAppearanceName appearanceName = [appearance bestMatchFromAppearancesWithNames:@[NSAppearanceNameAqua, + NSAppearanceNameDarkAqua]]; + return [appearanceName isEqualToString:NSAppearanceNameDarkAqua]; +} + +BOOL isDarkModeMojave() { + NSString *interfaceStyle = [[NSUserDefaults standardUserDefaults] stringForKey:KEY_APPLE_INTERFACE_STYLE]; + return [VALUE_DARK caseInsensitiveCompare:interfaceStyle] == NSOrderedSame; +} + JNIEXPORT jboolean JNICALL Java_com_github_weisj_darklaf_platform_macos_JNIThemeInfoMacOS_isDarkThemeEnabled(JNIEnv *env, jclass obj) { JNF_COCOA_ENTER(env); - if(@available(macOS 10.14, *)) { - NSString *interfaceStyle = [[NSUserDefaults standardUserDefaults] stringForKey:KEY_APPLE_INTERFACE_STYLE]; - // interfaceStyle can be nil (light mode) or "Dark" (dark mode). - BOOL isDark = [VALUE_DARK caseInsensitiveCompare:interfaceStyle] == NSOrderedSame; - return (jboolean) isDark; + if(@available(macOS 10.15, *)) { + if (catalinaEnabled) { + return (jboolean) isDarkModeCatalina(); + } + } + if (@available(macOS 10.14, *)) { + return (jboolean) isDarkModeMojave(); } else { return (jboolean) NO; } @@ -182,3 +202,39 @@ JNF_COCOA_ENTER(env); } JNF_COCOA_EXIT(env); } + +JNIEXPORT void JNICALL +Java_com_github_weisj_darklaf_platform_macos_JNIThemeInfoMacOS_patchAppBundle(JNIEnv *env, jclass obj, jboolean preJava11) { +JNF_COCOA_ENTER(env); + if (@available(macOS 10.15, *)) { + NSString *name = [[NSBundle mainBundle] bundleIdentifier]; + + CFStringRef bundleName = (__bridge CFStringRef)name; + + Boolean exists = false; + CFPreferencesGetAppBooleanValue(NSRequiresAquaSystemAppearance, bundleName, &exists); + + catalinaEnabled = preJava11 || exists; + + if (!exists) { + // Only patch if value hasn't been explicitly set + CFPreferencesSetAppValue(NSRequiresAquaSystemAppearance, kCFBooleanFalse, bundleName); + CFPreferencesAppSynchronize(bundleName); + patched = YES; + } + } +JNF_COCOA_EXIT(env); +} + +JNIEXPORT void JNICALL +Java_com_github_weisj_darklaf_platform_macos_JNIThemeInfoMacOS_unpatchAppBundle(JNIEnv *env, jclass obj) { +JNF_COCOA_ENTER(env); + if (!patched) return; + if (@available(macOS 10.15, *)) { + NSString *name = [[NSBundle mainBundle] bundleIdentifier]; + CFStringRef bundleName = (__bridge CFStringRef)name; + CFPreferencesSetAppValue(NSRequiresAquaSystemAppearance, nil, bundleName); + CFPreferencesAppSynchronize(bundleName); + } +JNF_COCOA_EXIT(env); +} diff --git a/native-utils/src/main/java/com/github/weisj/darklaf/platform/AbstractLibrary.java b/native-utils/src/main/java/com/github/weisj/darklaf/platform/AbstractLibrary.java index 68368c01..9482f55b 100644 --- a/native-utils/src/main/java/com/github/weisj/darklaf/platform/AbstractLibrary.java +++ b/native-utils/src/main/java/com/github/weisj/darklaf/platform/AbstractLibrary.java @@ -63,14 +63,12 @@ public abstract class AbstractLibrary { String path = getLibraryPath(); if (path != null && !path.isEmpty()) { NativeUtil.loadLibraryFromJar(getLibraryPath()); + loaded = true; + info("Loaded " + getLibraryName() + "."); } - loaded = true; - logger.info("Loaded " + getLibraryName() + ". Native features are enabled."); } catch (Throwable e) { // Library not found, SecurityManager prevents library loading etc. - logger.log(Level.SEVERE, - "Could not load library " + getLibraryName() + ". Native features will be disabled", - e); + error("Could not load library " + getLibraryName() + ".", e); } } @@ -91,4 +89,16 @@ public abstract class AbstractLibrary { public boolean isLoaded() { return loaded; } + + protected void info(final String message) { + if (logger != null) logger.info(message); + } + + protected void warning(final String message) { + if (logger != null) logger.warning(message); + } + + protected void error(final String message, final Throwable e) { + if (logger != null) logger.log(Level.SEVERE, message, e); + } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 62f8882a..9a763870 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,7 +4,6 @@ pluginManagement { fun PluginDependenciesSpec.idv(id: String, key: String = id) = id(id) version key.v() idv("com.github.autostyle") - idv("com.github.johnrengelman.shadow") idv("com.github.vlsi.crlf", "com.github.vlsi.vlsi-release-plugins") idv("com.github.vlsi.gradle-extensions", "com.github.vlsi.vlsi-release-plugins") idv("com.github.vlsi.license-gather", "com.github.vlsi.vlsi-release-plugins") diff --git a/theme/src/main/resources/com/github/weisj/darklaf/theme/darcula/darcula_defaults.properties b/theme/src/main/resources/com/github/weisj/darklaf/theme/darcula/darcula_defaults.properties index 5cd36dde..04e4c10f 100644 --- a/theme/src/main/resources/com/github/weisj/darklaf/theme/darcula/darcula_defaults.properties +++ b/theme/src/main/resources/com/github/weisj/darklaf/theme/darcula/darcula_defaults.properties @@ -102,6 +102,8 @@ Theme.highContrast = false %controlBackground = 555555 %controlFadeStart = 696969 %controlFadeEnd = 838383 +%controlFadeStartSecondary = 696969 +%controlFadeEndSecondary = 8f8f8f %controlErrorFadeStart = e74848 %controlErrorFadeEnd = f4a2a0 %controlPassedFadeStart = 008f50 diff --git a/theme/src/main/resources/com/github/weisj/darklaf/theme/high_contrast_dark/high_contrast_dark_defaults.properties b/theme/src/main/resources/com/github/weisj/darklaf/theme/high_contrast_dark/high_contrast_dark_defaults.properties index 67aba665..dc2fea1d 100644 --- a/theme/src/main/resources/com/github/weisj/darklaf/theme/high_contrast_dark/high_contrast_dark_defaults.properties +++ b/theme/src/main/resources/com/github/weisj/darklaf/theme/high_contrast_dark/high_contrast_dark_defaults.properties @@ -102,6 +102,8 @@ Theme.highContrast = true %controlBackground = 404040 %controlFadeStart = FFFFFF %controlFadeEnd = 1A1A1A +%controlFadeStartSecondary = B2B2B2 +%controlFadeEndSecondary = FFFFFF %controlErrorFadeStart = E6194B %controlErrorFadeEnd = 431C27 %controlPassedFadeStart = 00E61F diff --git a/theme/src/main/resources/com/github/weisj/darklaf/theme/high_contrast_dark/high_contrast_dark_ui.properties b/theme/src/main/resources/com/github/weisj/darklaf/theme/high_contrast_dark/high_contrast_dark_ui.properties index fdcb1b31..21d7cd5f 100644 --- a/theme/src/main/resources/com/github/weisj/darklaf/theme/high_contrast_dark/high_contrast_dark_ui.properties +++ b/theme/src/main/resources/com/github/weisj/darklaf/theme/high_contrast_dark/high_contrast_dark_ui.properties @@ -28,5 +28,5 @@ ColorChooser.swatchesDefaultRecentColor = 000000 Button.borderless.drawOutline = true TabbedPane.selectedHoverBackground = %backgroundHover ToolTip.paintShadow = false -ScrollBar.fadeEndColor = %ScrollBar.fadeStartColor RootPane.borderInsets = 1,1,1,1 +ScrollBar.thumbAlpha = 100 diff --git a/theme/src/main/resources/com/github/weisj/darklaf/theme/high_contrast_light/high_contrast_light_defaults.properties b/theme/src/main/resources/com/github/weisj/darklaf/theme/high_contrast_light/high_contrast_light_defaults.properties index 6ad2da86..27bcdba1 100644 --- a/theme/src/main/resources/com/github/weisj/darklaf/theme/high_contrast_light/high_contrast_light_defaults.properties +++ b/theme/src/main/resources/com/github/weisj/darklaf/theme/high_contrast_light/high_contrast_light_defaults.properties @@ -102,6 +102,8 @@ Theme.highContrast = true %controlBackground = BFBFBF %controlFadeStart = 000000 %controlFadeEnd = E5E5E5 +%controlFadeStartSecondary = 6B6B6B +%controlFadeEndSecondary = 0B0B0B %controlErrorFadeStart = E6194B %controlErrorFadeEnd = E3BCC7 %controlPassedFadeStart = 19FF38 diff --git a/theme/src/main/resources/com/github/weisj/darklaf/theme/high_contrast_light/high_contrast_light_ui.properties b/theme/src/main/resources/com/github/weisj/darklaf/theme/high_contrast_light/high_contrast_light_ui.properties index 422ea4b1..52070217 100644 --- a/theme/src/main/resources/com/github/weisj/darklaf/theme/high_contrast_light/high_contrast_light_ui.properties +++ b/theme/src/main/resources/com/github/weisj/darklaf/theme/high_contrast_light/high_contrast_light_ui.properties @@ -31,5 +31,5 @@ TableHeader.foreground = FFFFFF TabbedPane.selectedHoverBackground = %backgroundHover TabFrameTab.selectedForeground = %textSelectionForeground TabFrameTab.hoverForeground = %textSelectionForeground -ScrollBar.fadeEndColor = %ScrollBar.fadeStartColor RootPane.borderInsets = 1,1,1,1 +ScrollBar.thumbAlpha = 100 diff --git a/theme/src/main/resources/com/github/weisj/darklaf/theme/intellij/intellij_defaults.properties b/theme/src/main/resources/com/github/weisj/darklaf/theme/intellij/intellij_defaults.properties index deb99694..89db6577 100644 --- a/theme/src/main/resources/com/github/weisj/darklaf/theme/intellij/intellij_defaults.properties +++ b/theme/src/main/resources/com/github/weisj/darklaf/theme/intellij/intellij_defaults.properties @@ -122,6 +122,8 @@ Theme.highContrast = false %controlBackground = C4C4C4 %controlFadeStart = C4C4C4 %controlFadeEnd = 808080 +%controlFadeStartSecondary = 808080 +%controlFadeEndSecondary = 4c4c4c %controlErrorFadeStart = d64f4f %controlErrorFadeEnd = fb8f89 %controlPassedFadeStart = 34b171 diff --git a/theme/src/main/resources/com/github/weisj/darklaf/theme/one_dark/one_dark_defaults.properties b/theme/src/main/resources/com/github/weisj/darklaf/theme/one_dark/one_dark_defaults.properties index d8e26d59..b6dd4b48 100644 --- a/theme/src/main/resources/com/github/weisj/darklaf/theme/one_dark/one_dark_defaults.properties +++ b/theme/src/main/resources/com/github/weisj/darklaf/theme/one_dark/one_dark_defaults.properties @@ -102,6 +102,8 @@ Theme.highContrast = false %controlBackground = 1D1D26 %controlFadeStart = 568AF2 %controlFadeEnd = 313469 +%controlFadeStartSecondary = 676A71 +%controlFadeEndSecondary = 868A91 %controlErrorFadeStart = bd3c5f %controlErrorFadeEnd = 472c33 %controlPassedFadeStart = 239E62 diff --git a/theme/src/main/resources/com/github/weisj/darklaf/theme/one_dark/one_dark_ui.properties b/theme/src/main/resources/com/github/weisj/darklaf/theme/one_dark/one_dark_ui.properties index 7808f6ae..cac3cb70 100644 --- a/theme/src/main/resources/com/github/weisj/darklaf/theme/one_dark/one_dark_ui.properties +++ b/theme/src/main/resources/com/github/weisj/darklaf/theme/one_dark/one_dark_ui.properties @@ -28,8 +28,6 @@ CheckBox.activeFillColor = %textBackground CheckBox.selectedFillColor = %textBackground Slider.activeThumbFill = %textBackground RadioButton.activeFillColor = %textBackground -ScrollBar.fadeStartColor = 676A71 -ScrollBar.fadeEndColor = 797E85 Menu.selectionBackground = %backgroundColorfulInactive MenuItem.selectionBackground = %backgroundColorfulInactive RadioButtonMenuItem.selectionBackground = %backgroundColorfulInactive diff --git a/theme/src/main/resources/com/github/weisj/darklaf/theme/solarized_dark/solarized_dark_defaults.properties b/theme/src/main/resources/com/github/weisj/darklaf/theme/solarized_dark/solarized_dark_defaults.properties index aea3dfac..fa291983 100644 --- a/theme/src/main/resources/com/github/weisj/darklaf/theme/solarized_dark/solarized_dark_defaults.properties +++ b/theme/src/main/resources/com/github/weisj/darklaf/theme/solarized_dark/solarized_dark_defaults.properties @@ -102,6 +102,8 @@ Theme.highContrast = false %controlBackground = 2F535F %controlFadeStart = 476971 %controlFadeEnd = 67848C +%controlFadeStartSecondary = 476971 +%controlFadeEndSecondary = 769097 %controlErrorFadeStart = DE4647 %controlErrorFadeEnd = EFA0A0 %controlPassedFadeStart = 008A4B diff --git a/theme/src/main/resources/com/github/weisj/darklaf/theme/solarized_light/solarized_light_defaults.properties b/theme/src/main/resources/com/github/weisj/darklaf/theme/solarized_light/solarized_light_defaults.properties index 46e5ebad..07bff09a 100644 --- a/theme/src/main/resources/com/github/weisj/darklaf/theme/solarized_light/solarized_light_defaults.properties +++ b/theme/src/main/resources/com/github/weisj/darklaf/theme/solarized_light/solarized_light_defaults.properties @@ -102,6 +102,8 @@ Theme.highContrast = false %controlBackground = C3BEAF %controlFadeStart = C3BEAF %controlFadeEnd = 6B8086 +%controlFadeStartSecondary = AFAB9D +%controlFadeEndSecondary = 6B8086 %controlErrorFadeStart = D14943 %controlErrorFadeEnd = F6887C %controlPassedFadeStart = 2DAA66 diff --git a/windows/build.gradle.kts b/windows/build.gradle.kts index 4c3a2a39..c25e5073 100644 --- a/windows/build.gradle.kts +++ b/windows/build.gradle.kts @@ -1,41 +1,54 @@ - +import JniUtils.asVariantName plugins { - `jni-library` -} - -fun DependencyHandlerScope.javaImplementation(dep: Any) { - compileOnly(dep) - runtimeOnly(dep) -} - -dependencies { - javaImplementation(project(":darklaf-native-utils")) - javaImplementation(project(":darklaf-utils")) - javaImplementation(project(":darklaf-platform-base")) - javaImplementation(project(":darklaf-theme")) - javaImplementation(project(":darklaf-property-loader")) - javaImplementation("net.java.dev.jna:jna") + java + id("dev.nokee.jni-library") + id("dev.nokee.cpp-language") + `uber-jni-jar` + `use-prebuilt-binaries` } library { + dependencies { + jvmImplementation(project(":darklaf-native-utils")) + jvmImplementation(project(":darklaf-utils")) + jvmImplementation(project(":darklaf-platform-base")) + jvmImplementation(project(":darklaf-theme")) + jvmImplementation(project(":darklaf-property-loader")) + jvmImplementation("net.java.dev.jna:jna") + } + targetMachines.addAll(machines.windows.x86, machines.windows.x86_64) - binaries.configureEach { - compileTask.get().compilerArgs.addAll( - when (toolChain) { - is Gcc, is Clang -> listOf("--std=c++11") - is VisualCpp -> listOf("/EHsc") - else -> emptyList() + variants.configureEach { + resourcePath.set("com/github/weisj/darklaf/platform/${project.name}/${asVariantName(targetMachine)}") + sharedLibrary { + compileTasks.configureEach { + compilerArgs.addAll(toolChain.map { + when (it) { + is Gcc, is Clang -> listOf("--std=c++11") + is VisualCpp -> listOf("/EHsc") + else -> emptyList() + } + }) + + // Build type not modeled yet, assuming release + compilerArgs.addAll(toolChain.map { + when (it) { + is Gcc, is Clang -> listOf("-O2") + is VisualCpp -> listOf("/O2") + else -> emptyList() + } + }) } - ) - } - binaries.whenElementFinalized(CppSharedLibrary::class) { - linkTask.get().linkerArgs.addAll( - when (toolChain) { - is Gcc, is Clang -> listOf("-ldwmapi", "-lGdi32", "-luser32", "-ladvapi32", "-Shell32") - is VisualCpp -> listOf("dwmapi.lib", "user32.lib", "Gdi32.lib", "Advapi32.lib", "Shell32.lib") - else -> emptyList() + linkTask.configure { + linkerArgs.addAll(toolChain.map { + when (it) { + is Gcc, is Clang -> listOf("-ldwmapi", "-lGdi32", "-luser32", "-ladvapi32", "-Shell32") + is VisualCpp -> listOf("dwmapi.lib", "user32.lib", "Gdi32.lib", "Advapi32.lib", "Shell32.lib") + else -> emptyList() + } + }) } - ) + } } } diff --git a/windows/libraries/windows-x86-64/library.md b/windows/pre-build-libraries/windows-x86-64/library.md similarity index 100% rename from windows/libraries/windows-x86-64/library.md rename to windows/pre-build-libraries/windows-x86-64/library.md diff --git a/windows/libraries/windows-x86/library.md b/windows/pre-build-libraries/windows-x86/library.md similarity index 100% rename from windows/libraries/windows-x86/library.md rename to windows/pre-build-libraries/windows-x86/library.md diff --git a/windows/src/main/java/com/github/weisj/darklaf/platform/windows/WindowsLibrary.java b/windows/src/main/java/com/github/weisj/darklaf/platform/windows/WindowsLibrary.java index 08e30c17..3dfd2bc1 100644 --- a/windows/src/main/java/com/github/weisj/darklaf/platform/windows/WindowsLibrary.java +++ b/windows/src/main/java/com/github/weisj/darklaf/platform/windows/WindowsLibrary.java @@ -44,17 +44,6 @@ public class WindowsLibrary extends AbstractLibrary { super(PATH, DLL_NAME, LogUtil.getLogger(WindowsLibrary.class)); } - @Override - protected String getLibraryPath() { - if (SystemInfo.undefinedArchitecture) { - logger.warning("Could not determine jre model '" - + SystemInfo.jreArchitecture - + "'. Native features will be disabled"); - return null; - } - return super.getLibraryPath(); - } - @Override protected String getPath() { if (SystemInfo.isX86) { @@ -68,6 +57,6 @@ public class WindowsLibrary extends AbstractLibrary { @Override protected boolean canLoad() { - return SystemInfo.isWindows10; + return !SystemInfo.undefinedArchitecture && SystemInfo.isWindows10; } }