From bd9e475f380b653a42163ec9062044e15157a0b5 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Fri, 16 May 2025 20:49:06 +0200 Subject: [PATCH 01/14] Fixes for LSP running & Improving the install locations --- app/build.gradle.kts | 3 +- app/src/processing/app/Base.java | 12 ------ app/src/processing/app/Processing.kt | 64 ++++++++++++++++++++++++++-- 3 files changed, 62 insertions(+), 17 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c40365758..ef951c879 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -419,7 +419,6 @@ tasks.register("renameWindres") { } tasks.register("includeProcessingResources"){ dependsOn( - "includeJdk", "includeCore", "includeJavaMode", "includeSharedAssets", @@ -428,6 +427,7 @@ tasks.register("includeProcessingResources"){ "includeJavaModeResources", "renameWindres" ) + mustRunAfter("includeJdk") finalizedBy("signResources") } @@ -534,6 +534,7 @@ afterEvaluate { dependsOn("includeProcessingResources") } tasks.named("createDistributable").configure { + dependsOn("includeJdk") finalizedBy("setExecutablePermissions") } } diff --git a/app/src/processing/app/Base.java b/app/src/processing/app/Base.java index b5aa599b9..4d57135c9 100644 --- a/app/src/processing/app/Base.java +++ b/app/src/processing/app/Base.java @@ -166,18 +166,6 @@ public class Base { static private void createAndShowGUI(String[] args) { // these times are fairly negligible relative to Base. // long t1 = System.currentTimeMillis(); - var preferences = java.util.prefs.Preferences.userRoot().node("org/processing/app"); - var installLocations = new ArrayList<>(List.of(preferences.get("installLocations", "").split(","))); - var installLocation = System.getProperty("user.dir") + "^" + Base.getVersionName(); - - // Check if the installLocation is already in the list - if (!installLocations.contains(installLocation)) { - // Add the installLocation to the list - installLocations.add(installLocation); - - // Save the updated list back to preferences - preferences.put("installLocations", String.join(",", installLocations)); - } // TODO: Cleanup old locations if no longer installed // TODO: Cleanup old locations if current version is installed in the same location diff --git a/app/src/processing/app/Processing.kt b/app/src/processing/app/Processing.kt index 11555edf5..751dd361f 100644 --- a/app/src/processing/app/Processing.kt +++ b/app/src/processing/app/Processing.kt @@ -11,6 +11,9 @@ import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.help import com.github.ajalt.clikt.parameters.options.option import processing.app.ui.Start +import java.io.File +import java.util.prefs.Preferences +import kotlin.concurrent.thread class Processing: SuspendingCliktCommand("processing"){ val version by option("-v","--version") @@ -29,6 +32,11 @@ class Processing: SuspendingCliktCommand("processing"){ return } + thread { + // Update the install locations in preferences + updateInstallLocations() + } + val subcommand = currentContext.invokedSubcommand if (subcommand == null) { Start.main(sketches.toTypedArray()) @@ -49,10 +57,13 @@ class LSP: SuspendingCliktCommand("lsp"){ override fun help(context: Context) = "Start the Processing Language Server" override suspend fun run(){ try { + // run in headless mode + System.setProperty("java.awt.headless", "true") + // Indirect invocation since app does not depend on java mode Class.forName("processing.mode.java.lsp.PdeLanguageServer") .getMethod("main", Array::class.java) - .invoke(null, *arrayOf(emptyList())) + .invoke(null, arrayOf()) } catch (e: Exception) { throw InternalError("Failed to invoke main method", e) } @@ -76,9 +87,8 @@ class LegacyCLI(val args: Array): SuspendingCliktCommand( "cli"){ override suspend fun run(){ val cliArgs = args.filter { it != "cli" } try { - if(build){ - System.setProperty("java.awt.headless", "true") - } + System.setProperty("java.awt.headless", "true") + // Indirect invocation since app does not depend on java mode Class.forName("processing.mode.java.Commander") .getMethod("main", Array::class.java) @@ -87,4 +97,50 @@ class LegacyCLI(val args: Array): SuspendingCliktCommand( "cli"){ throw InternalError("Failed to invoke main method", e) } } +} + +fun updateInstallLocations(){ + val preferences = Preferences.userRoot().node("org/processing/app") + val installLocations = preferences.get("installLocations", "") + .split(",") + .dropLastWhile { it.isEmpty() } + .filter { install -> + try{ + val (path, version) = install.split("^") + val file = File(path) + if(!file.exists() || file.isDirectory){ + return@filter false + } + // call the path to check if it is a valid install location + val process = ProcessBuilder(path, "--version") + .redirectErrorStream(true) + .start() + val exitCode = process.waitFor() + if(exitCode != 0){ + return@filter false + } + val output = process.inputStream.bufferedReader().readText() + return@filter output.contains(version) + } catch (e: Exception){ + false + } + } + .toMutableList() + val command = ProcessHandle.current().info().command() + if(command.isEmpty) { + return + } + val installLocation = "${command.get()}^${Base.getVersionName()}" + + + // Check if the installLocation is already in the list + if (installLocations.contains(installLocation)) { + return + } + + // Add the installLocation to the list + installLocations.add(installLocation) + + // Save the updated list back to preferences + preferences.put("installLocations", java.lang.String.join(",", installLocations)) } \ No newline at end of file From 5b27fc650a57ed195593ee6e3c70f6bf1a6ccf53 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Tue, 20 May 2025 20:43:50 +0200 Subject: [PATCH 02/14] Create Examples JSON --- app/build.gradle.kts | 1 + app/src/processing/app/Processing.kt | 4 +- .../processing/app/contrib/Contributions.kt | 95 +++++++++++++++++++ 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 app/src/processing/app/contrib/Contributions.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ef951c879..79cb4ad15 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -124,6 +124,7 @@ dependencies { testImplementation(libs.junitJupiterParams) implementation(libs.clikt) + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") } tasks.test { diff --git a/app/src/processing/app/Processing.kt b/app/src/processing/app/Processing.kt index 751dd361f..798ef4b34 100644 --- a/app/src/processing/app/Processing.kt +++ b/app/src/processing/app/Processing.kt @@ -10,6 +10,7 @@ import com.github.ajalt.clikt.parameters.arguments.multiple import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.help import com.github.ajalt.clikt.parameters.options.option +import processing.app.contrib.Contributions import processing.app.ui.Start import java.io.File import java.util.prefs.Preferences @@ -48,7 +49,8 @@ suspend fun main(args: Array){ Processing() .subcommands( LSP(), - LegacyCLI(args) + LegacyCLI(args), + Contributions() ) .main(args) } diff --git a/app/src/processing/app/contrib/Contributions.kt b/app/src/processing/app/contrib/Contributions.kt new file mode 100644 index 000000000..0c1948d84 --- /dev/null +++ b/app/src/processing/app/contrib/Contributions.kt @@ -0,0 +1,95 @@ +package processing.app.contrib + +import com.github.ajalt.clikt.command.SuspendingCliktCommand +import com.github.ajalt.clikt.core.Context +import com.github.ajalt.clikt.core.subcommands +import kotlinx.serialization.Serializable +import kotlinx.serialization.encodeToString +import processing.app.Base +import java.io.File +import kotlinx.serialization.json.* + + +class Contributions: SuspendingCliktCommand(){ + override fun help(context: Context) = "Manage Processing contributions" + override suspend fun run() { + System.setProperty("java.awt.headless", "true") + } + init { + subcommands(Examples()) + } + + class Examples: SuspendingCliktCommand("examples") { + override fun help(context: Context) = "Manage Processing examples" + override suspend fun run() { + } + init { + subcommands(ExamplesList()) + } + } + + class ExamplesList: SuspendingCliktCommand("list") { + @Serializable + data class Sketch( + val type: String = "sketch", + val name: String, + val path: String, + val mode: String = "java", + ) + + @Serializable + data class Folder( + val type: String = "folder", + val name: String, + val path: String, + val mode: String = "java", + val children: List = emptyList(), + val sketches: List = emptyList() + ) + + val serializer = Json{ + prettyPrint = true + } + + override fun help(context: Context) = "List all examples" + override suspend fun run() { + + val examplesFolder = Base.getSketchbookExamplesFolder() + + // TODO: Decouple modes listing from `Base` class, defaulting to Java mode for now + val resourcesDir = System.getProperty("compose.application.resources.dir") + val javaMode = "$resourcesDir/modes/java" + val javaModeExamples = "$javaMode/examples" + + val javaExamples = getExamples(File(javaModeExamples)) + + val json = serializer.encodeToString(listOf(javaExamples)) + println(json) + + // Build-in examples for each mode + // Get examples for core libraries + + // Examples downloaded in the sketchbook + // Library contributions + // Mode examples + } + + suspend fun getExamples(file: File): Folder { + val name = file.name + val (sketchesFolders, childrenFolders) = file.listFiles().partition { isExampleFolder(it) } + + val children = childrenFolders.map { getExamples(it) } + val sketches = sketchesFolders.map { Sketch(name = it.name, path = it.absolutePath) } + return Folder( + name = name, + path = file.absolutePath, + children = children, + sketches = sketches + ) + } + fun isExampleFolder(file: File): Boolean { + return file.isDirectory && file.listFiles().any { it.isFile && it.name.endsWith(".pde") } + } + } +} + From b3fb5582065cfdab2530b35b4a8d80dea23a325e Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Thu, 22 May 2025 08:36:50 +0200 Subject: [PATCH 03/14] Moved into api folder + listing Sketchbook --- app/src/processing/app/Processing.kt | 2 +- .../app/{contrib => api}/Contributions.kt | 28 ++++--------------- app/src/processing/app/api/Sketch.kt | 24 ++++++++++++++++ app/src/processing/app/api/Sketchbook.kt | 25 +++++++++++++++++ 4 files changed, 56 insertions(+), 23 deletions(-) rename app/src/processing/app/{contrib => api}/Contributions.kt (71%) create mode 100644 app/src/processing/app/api/Sketch.kt create mode 100644 app/src/processing/app/api/Sketchbook.kt diff --git a/app/src/processing/app/Processing.kt b/app/src/processing/app/Processing.kt index 798ef4b34..b72b59af6 100644 --- a/app/src/processing/app/Processing.kt +++ b/app/src/processing/app/Processing.kt @@ -10,7 +10,7 @@ import com.github.ajalt.clikt.parameters.arguments.multiple import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.help import com.github.ajalt.clikt.parameters.options.option -import processing.app.contrib.Contributions +import processing.app.api.Contributions import processing.app.ui.Start import java.io.File import java.util.prefs.Preferences diff --git a/app/src/processing/app/contrib/Contributions.kt b/app/src/processing/app/api/Contributions.kt similarity index 71% rename from app/src/processing/app/contrib/Contributions.kt rename to app/src/processing/app/api/Contributions.kt index 0c1948d84..070874c13 100644 --- a/app/src/processing/app/contrib/Contributions.kt +++ b/app/src/processing/app/api/Contributions.kt @@ -1,14 +1,14 @@ -package processing.app.contrib +package processing.app.api import com.github.ajalt.clikt.command.SuspendingCliktCommand import com.github.ajalt.clikt.core.Context import com.github.ajalt.clikt.core.subcommands import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json import processing.app.Base +import processing.app.api.Sketch.Companion.getSketches import java.io.File -import kotlinx.serialization.json.* - class Contributions: SuspendingCliktCommand(){ override fun help(context: Context) = "Manage Processing contributions" @@ -47,7 +47,7 @@ class Contributions: SuspendingCliktCommand(){ val sketches: List = emptyList() ) - val serializer = Json{ + val serializer = Json { prettyPrint = true } @@ -61,7 +61,7 @@ class Contributions: SuspendingCliktCommand(){ val javaMode = "$resourcesDir/modes/java" val javaModeExamples = "$javaMode/examples" - val javaExamples = getExamples(File(javaModeExamples)) + val javaExamples = getSketches(File(javaModeExamples)) val json = serializer.encodeToString(listOf(javaExamples)) println(json) @@ -74,22 +74,6 @@ class Contributions: SuspendingCliktCommand(){ // Mode examples } - suspend fun getExamples(file: File): Folder { - val name = file.name - val (sketchesFolders, childrenFolders) = file.listFiles().partition { isExampleFolder(it) } - val children = childrenFolders.map { getExamples(it) } - val sketches = sketchesFolders.map { Sketch(name = it.name, path = it.absolutePath) } - return Folder( - name = name, - path = file.absolutePath, - children = children, - sketches = sketches - ) - } - fun isExampleFolder(file: File): Boolean { - return file.isDirectory && file.listFiles().any { it.isFile && it.name.endsWith(".pde") } - } } -} - +} \ No newline at end of file diff --git a/app/src/processing/app/api/Sketch.kt b/app/src/processing/app/api/Sketch.kt new file mode 100644 index 000000000..6943a7f35 --- /dev/null +++ b/app/src/processing/app/api/Sketch.kt @@ -0,0 +1,24 @@ +package processing.app.api + +import java.io.File + +class Sketch { + companion object{ + fun getSketches(file: File): Contributions.ExamplesList.Folder { + val name = file.name + val (sketchesFolders, childrenFolders) = file.listFiles().partition { isSketchFolder(it) } + + val children = childrenFolders.map { getSketches(it) } + val sketches = sketchesFolders.map { Contributions.ExamplesList.Sketch(name = it.name, path = it.absolutePath) } + return Contributions.ExamplesList.Folder( + name = name, + path = file.absolutePath, + children = children, + sketches = sketches + ) + } + fun isSketchFolder(file: File): Boolean { + return file.isDirectory && file.listFiles().any { it.isFile && it.name.endsWith(".pde") } + } + } +} diff --git a/app/src/processing/app/api/Sketchbook.kt b/app/src/processing/app/api/Sketchbook.kt new file mode 100644 index 000000000..c7bdd7623 --- /dev/null +++ b/app/src/processing/app/api/Sketchbook.kt @@ -0,0 +1,25 @@ +package processing.app.api + +import com.github.ajalt.clikt.command.SuspendingCliktCommand +import com.github.ajalt.clikt.core.Context +import com.github.ajalt.clikt.core.subcommands +import processing.app.Base +import processing.app.api.Sketch.Companion.getSketches + +class Sketchbook: SuspendingCliktCommand() { + override fun help(context: Context) = "Manage the sketchbook" + override suspend fun run() { + System.setProperty("java.awt.headless", "true") + } + init { + subcommands(SketchbookList()) + } + class SketchbookList: SuspendingCliktCommand("list") { + override fun help(context: Context) = "List all sketches" + override suspend fun run() { + val sketchbookFolder = Base.getSketchbookFolder() + + val sketches = getSketches(sketchbookFolder) + } + } +} \ No newline at end of file From a863cff3ca8650088fc09d9ffe3c5096b96ca163 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Thu, 22 May 2025 09:14:42 +0200 Subject: [PATCH 04/14] support for listing Sketchbook --- app/src/processing/app/Processing.kt | 4 ++- app/src/processing/app/api/Contributions.kt | 16 --------- app/src/processing/app/api/Sketch.kt | 36 +++++++++++++++++---- app/src/processing/app/api/Sketchbook.kt | 23 +++++++++++-- 4 files changed, 53 insertions(+), 26 deletions(-) diff --git a/app/src/processing/app/Processing.kt b/app/src/processing/app/Processing.kt index b72b59af6..bb05a72f3 100644 --- a/app/src/processing/app/Processing.kt +++ b/app/src/processing/app/Processing.kt @@ -11,6 +11,7 @@ import com.github.ajalt.clikt.parameters.options.flag import com.github.ajalt.clikt.parameters.options.help import com.github.ajalt.clikt.parameters.options.option import processing.app.api.Contributions +import processing.app.api.Sketchbook import processing.app.ui.Start import java.io.File import java.util.prefs.Preferences @@ -50,7 +51,8 @@ suspend fun main(args: Array){ .subcommands( LSP(), LegacyCLI(args), - Contributions() + Contributions(), + Sketchbook() ) .main(args) } diff --git a/app/src/processing/app/api/Contributions.kt b/app/src/processing/app/api/Contributions.kt index 070874c13..80bfa502f 100644 --- a/app/src/processing/app/api/Contributions.kt +++ b/app/src/processing/app/api/Contributions.kt @@ -29,23 +29,7 @@ class Contributions: SuspendingCliktCommand(){ } class ExamplesList: SuspendingCliktCommand("list") { - @Serializable - data class Sketch( - val type: String = "sketch", - val name: String, - val path: String, - val mode: String = "java", - ) - @Serializable - data class Folder( - val type: String = "folder", - val name: String, - val path: String, - val mode: String = "java", - val children: List = emptyList(), - val sketches: List = emptyList() - ) val serializer = Json { prettyPrint = true diff --git a/app/src/processing/app/api/Sketch.kt b/app/src/processing/app/api/Sketch.kt index 6943a7f35..a0e740014 100644 --- a/app/src/processing/app/api/Sketch.kt +++ b/app/src/processing/app/api/Sketch.kt @@ -1,16 +1,40 @@ package processing.app.api +import kotlinx.serialization.Serializable import java.io.File class Sketch { companion object{ - fun getSketches(file: File): Contributions.ExamplesList.Folder { - val name = file.name - val (sketchesFolders, childrenFolders) = file.listFiles().partition { isSketchFolder(it) } + @Serializable + data class Sketch( + val type: String = "sketch", + val name: String, + val path: String, + val mode: String = "java", + ) - val children = childrenFolders.map { getSketches(it) } - val sketches = sketchesFolders.map { Contributions.ExamplesList.Sketch(name = it.name, path = it.absolutePath) } - return Contributions.ExamplesList.Folder( + @Serializable + data class Folder( + val type: String = "folder", + val name: String, + val path: String, + val mode: String = "java", + val children: List = emptyList(), + val sketches: List = emptyList() + ) + + fun getSketches(file: File, filter: (File) -> Boolean = { true }): Folder { + val name = file.name + val (sketchesFolders, childrenFolders) = file.listFiles()?.partition { isSketchFolder(it) } ?: return Folder( + name = name, + path = file.absolutePath, + sketches = emptyList(), + children = emptyList() + ) + + val children = childrenFolders.filter(filter) .map { getSketches(it) } + val sketches = sketchesFolders.map { Sketch(name = it.name, path = it.absolutePath) } + return Folder( name = name, path = file.absolutePath, children = children, diff --git a/app/src/processing/app/api/Sketchbook.kt b/app/src/processing/app/api/Sketchbook.kt index c7bdd7623..43d1cc6ce 100644 --- a/app/src/processing/app/api/Sketchbook.kt +++ b/app/src/processing/app/api/Sketchbook.kt @@ -3,10 +3,16 @@ package processing.app.api import com.github.ajalt.clikt.command.SuspendingCliktCommand import com.github.ajalt.clikt.core.Context import com.github.ajalt.clikt.core.subcommands -import processing.app.Base +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json +import processing.app.Platform +import processing.app.Preferences import processing.app.api.Sketch.Companion.getSketches +import java.io.File class Sketchbook: SuspendingCliktCommand() { + + override fun help(context: Context) = "Manage the sketchbook" override suspend fun run() { System.setProperty("java.awt.headless", "true") @@ -14,12 +20,23 @@ class Sketchbook: SuspendingCliktCommand() { init { subcommands(SketchbookList()) } + + class SketchbookList: SuspendingCliktCommand("list") { + val serializer = Json { + prettyPrint = true + } + override fun help(context: Context) = "List all sketches" override suspend fun run() { - val sketchbookFolder = Base.getSketchbookFolder() + Platform.init() + // TODO: Allow the user to change the sketchbook location + // TODO: Currently blocked since `Base.getSketchbookFolder()` is not available in headless mode + val sketchbookFolder = Platform.getDefaultSketchbookFolder() - val sketches = getSketches(sketchbookFolder) + val sketches = getSketches(sketchbookFolder.resolve("sketchbook")) + val json = serializer.encodeToString(listOf(sketches)) + println(json) } } } \ No newline at end of file From adda0419522ab02e61289590896fba1f70566ca5 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Fri, 20 Jun 2025 13:48:58 +0200 Subject: [PATCH 05/14] Import cleanup --- app/build.gradle.kts | 2 +- gradle/libs.versions.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ec48ba6c9..6adcaa4ea 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -124,7 +124,7 @@ dependencies { testImplementation(libs.junitJupiterParams) implementation(libs.clikt) - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") + implementation(libs.kotlinxSerializationJson) } tasks.test { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 70f93aaff..dfacae1ea 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,6 +28,7 @@ jsoup = { module = "org.jsoup:jsoup", version = "1.17.2" } markdown = { module = "com.mikepenz:multiplatform-markdown-renderer-m2", version = "0.31.0" } markdownJVM = { module = "com.mikepenz:multiplatform-markdown-renderer-jvm", version = "0.31.0" } clikt = { module = "com.github.ajalt.clikt:clikt", version = "5.0.2" } +kotlinxSerializationJson = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.6.3" } [plugins] jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } From 866c7f819845f2eb86319356fe3d90c1dc3196c8 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Fri, 20 Jun 2025 17:29:12 +0200 Subject: [PATCH 06/14] Sketchbook fixes --- app/src/processing/app/api/Sketch.kt | 2 +- app/src/processing/app/api/Sketchbook.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/processing/app/api/Sketch.kt b/app/src/processing/app/api/Sketch.kt index a0e740014..87f2cc56c 100644 --- a/app/src/processing/app/api/Sketch.kt +++ b/app/src/processing/app/api/Sketch.kt @@ -25,7 +25,7 @@ class Sketch { fun getSketches(file: File, filter: (File) -> Boolean = { true }): Folder { val name = file.name - val (sketchesFolders, childrenFolders) = file.listFiles()?.partition { isSketchFolder(it) } ?: return Folder( + val (sketchesFolders, childrenFolders) = file.listFiles()?.filter (File::isDirectory)?.partition { isSketchFolder(it) } ?: return Folder( name = name, path = file.absolutePath, sketches = emptyList(), diff --git a/app/src/processing/app/api/Sketchbook.kt b/app/src/processing/app/api/Sketchbook.kt index 43d1cc6ce..72eec63ed 100644 --- a/app/src/processing/app/api/Sketchbook.kt +++ b/app/src/processing/app/api/Sketchbook.kt @@ -34,7 +34,7 @@ class Sketchbook: SuspendingCliktCommand() { // TODO: Currently blocked since `Base.getSketchbookFolder()` is not available in headless mode val sketchbookFolder = Platform.getDefaultSketchbookFolder() - val sketches = getSketches(sketchbookFolder.resolve("sketchbook")) + val sketches = getSketches(sketchbookFolder) val json = serializer.encodeToString(listOf(sketches)) println(json) } From 39a0432852e7a57192b29a43c138a8ec26e2c512 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Tue, 12 Aug 2025 13:38:47 +0200 Subject: [PATCH 07/14] Set `java.home` and added some comments --- app/src/processing/app/Platform.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/processing/app/Platform.java b/app/src/processing/app/Platform.java index b911d7e0a..2c2ade5e1 100644 --- a/app/src/processing/app/Platform.java +++ b/app/src/processing/app/Platform.java @@ -105,6 +105,9 @@ public class Platform { "An unknown error occurred while trying to load\n" + "platform-specific code for your machine.", e); } + + // Fix the issue where `java.home` points to the JRE instead of the JDK. processing/processing4#1163 + System.setProperty("java.home", getJavaHome().getAbsolutePath()); } @@ -389,6 +392,7 @@ public class Platform { } static public File getJavaHome() { + // Get the build in JDK location from the Jetpack Compose resources var resourcesDir = System.getProperty("compose.application.resources.dir"); if(resourcesDir != null) { var jdkFolder = new File(resourcesDir,"jdk"); @@ -397,10 +401,13 @@ public class Platform { } } + // If the JDK is set in the environment, use that. var home = System.getProperty("java.home"); if(home != null){ return new File(home); } + + // Otherwise try to use the Ant embedded JDK. if (Platform.isMacOS()) { //return "Contents/PlugIns/jdk1.7.0_40.jdk/Contents/Home/jre/bin/java"; File[] plugins = getContentFile("../PlugIns").listFiles((dir, name) -> dir.isDirectory() && From eece777e285563d51d68d9afd999bbb320594f46 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Tue, 12 Aug 2025 14:11:35 +0200 Subject: [PATCH 08/14] Adding classic confinment support + refactor for later flathub --- .github/workflows/release-gradle.yml | 1 + app/build.gradle.kts | 81 +++++++++++----------------- app/linux/snapcraft.base.yml | 42 +++++++++++++++ 3 files changed, 75 insertions(+), 49 deletions(-) create mode 100644 app/linux/snapcraft.base.yml diff --git a/.github/workflows/release-gradle.yml b/.github/workflows/release-gradle.yml index 16e8984e3..8ec45cad0 100644 --- a/.github/workflows/release-gradle.yml +++ b/.github/workflows/release-gradle.yml @@ -153,6 +153,7 @@ jobs: ORG_GRADLE_PROJECT_compose.desktop.mac.notarization.password: ${{ secrets.PROCESSING_APP_PASSWORD }} ORG_GRADLE_PROJECT_compose.desktop.mac.notarization.teamID: ${{ secrets.PROCESSING_TEAM_ID }} ORG_GRADLE_PROJECT_snapname: ${{ vars.SNAP_NAME }} + ORG_GRADLE_PROJECT_snapconfinement: ${{ vars.SNAP_CONFINEMENT }} - name: Sign files with Trusted Signing if: runner.os == 'Windows' diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5323a1a82..b53620858 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -228,61 +228,44 @@ tasks.register("packageCustomMsi"){ tasks.register("generateSnapConfiguration"){ - val name = findProperty("snapname") ?: rootProject.name + onlyIf { OperatingSystem.current().isLinux } + + val distributable = tasks.named("createDistributable").get() + dependsOn(distributable) + + val name = findProperty("snapname") as String? ?: rootProject.name val arch = when (System.getProperty("os.arch")) { "amd64", "x86_64" -> "amd64" "aarch64" -> "arm64" else -> System.getProperty("os.arch") } - - onlyIf { OperatingSystem.current().isLinux } - val distributable = tasks.named("createDistributable").get() - dependsOn(distributable) - + val confinement = findProperty("snapconfinement") as String? ?: "strict" val dir = distributable.destinationDir.get() - val content = """ - name: $name - version: $version - base: core22 - summary: A creative coding editor - description: | - Processing is a flexible software sketchbook and a programming language designed for learning how to code. - confinement: strict - - apps: - processing: - command: opt/processing/bin/Processing - desktop: opt/processing/lib/processing-Processing.desktop - environment: - LD_LIBRARY_PATH: ${'$'}SNAP/opt/processing/lib/runtime/lib:${'$'}LD_LIBRARY_PATH - LIBGL_DRIVERS_PATH: ${'$'}SNAP/usr/lib/${'$'}SNAPCRAFT_ARCH_TRIPLET/dri - plugs: - - desktop - - desktop-legacy - - wayland - - x11 - - network - - opengl - - home - - removable-media - - audio-playback - - audio-record - - pulseaudio - - gpio - - parts: - processing: - plugin: dump - source: deb/processing_$version-1_$arch.deb - source-type: deb - stage-packages: - - openjdk-17-jre - override-prime: | - snapcraftctl prime - rm -vf usr/lib/jvm/java-17-openjdk-*/lib/security/cacerts - chmod -R +x opt/processing/lib/app/resources/jdk - """.trimIndent() - dir.file("../snapcraft.yaml").asFile.writeText(content) + val base = layout.projectDirectory.file("linux/snapcraft.base.yml") + + doFirst { + + var content = base + .asFile + .readText() + .replace("\$name", name) + .replace("\$arch", arch) + .replace("\$version", version as String) + .replace("\$confinement", confinement) + .let { + if (confinement != "classic") return@let it + // If confinement is not strict, remove the PLUGS section + val start = it.indexOf("# PLUGS START") + val end = it.indexOf("# PLUGS END") + if (start != -1 && end != -1) { + val before = it.substring(0, start) + val after = it.substring(end + "# PLUGS END".length) + return@let before + after + } + return@let it + } + dir.file("../snapcraft.yaml").asFile.writeText(content) + } } tasks.register("packageSnap"){ diff --git a/app/linux/snapcraft.base.yml b/app/linux/snapcraft.base.yml new file mode 100644 index 000000000..4847f0a7c --- /dev/null +++ b/app/linux/snapcraft.base.yml @@ -0,0 +1,42 @@ +name: $name +version: $version +base: core22 +summary: A creative coding editor +description: | + Processing is a flexible software sketchbook and a programming language designed for learning how to code. +confinement: $confinement + +apps: + processing: + command: opt/processing/bin/Processing + desktop: opt/processing/lib/processing-Processing.desktop + environment: + LD_LIBRARY_PATH: $SNAP/opt/processing/lib/runtime/lib:$LD_LIBRARY_PATH + LIBGL_DRIVERS_PATH: $SNAP/usr/lib/$SNAPCRAFT_ARCH_TRIPLET/dri + # PLUGS START + plugs: + - desktop + - desktop-legacy + - wayland + - x11 + - network + - opengl + - home + - removable-media + - audio-playback + - audio-record + - pulseaudio + - gpio + # PLUGS END + +parts: + processing: + plugin: dump + source: deb/processing_$version-1_$arch.deb + source-type: deb + stage-packages: + - openjdk-17-jre + override-prime: | + snapcraftctl prime + rm -vf usr/lib/jvm/java-17-openjdk-*/lib/security/cacerts + chmod -R +x opt/processing/lib/app/resources/jdk \ No newline at end of file From 8b0411095ca7c831ad989cf8e2e8b9036c78c632 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 13 Aug 2025 09:51:44 +0200 Subject: [PATCH 09/14] Completed the examples & sketchbook scanning --- app/src/processing/app/api/Contributions.kt | 111 +++++++++++++++++--- app/src/processing/app/api/Sketch.kt | 8 +- app/src/processing/app/api/Sketchbook.kt | 10 +- 3 files changed, 110 insertions(+), 19 deletions(-) diff --git a/app/src/processing/app/api/Contributions.kt b/app/src/processing/app/api/Contributions.kt index 80bfa502f..25e693404 100644 --- a/app/src/processing/app/api/Contributions.kt +++ b/app/src/processing/app/api/Contributions.kt @@ -3,10 +3,9 @@ package processing.app.api import com.github.ajalt.clikt.command.SuspendingCliktCommand import com.github.ajalt.clikt.core.Context import com.github.ajalt.clikt.core.subcommands -import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json -import processing.app.Base +import processing.app.Platform import processing.app.api.Sketch.Companion.getSketches import java.io.File @@ -37,27 +36,109 @@ class Contributions: SuspendingCliktCommand(){ override fun help(context: Context) = "List all examples" override suspend fun run() { - - val examplesFolder = Base.getSketchbookExamplesFolder() - + Platform.init() // TODO: Decouple modes listing from `Base` class, defaulting to Java mode for now + // TODO: Allow the user to change the sketchbook location + // TODO: Currently blocked since `Base.getSketchbookFolder()` is not available in headless mode + val sketchbookFolder = Platform.getDefaultSketchbookFolder() val resourcesDir = System.getProperty("compose.application.resources.dir") + val javaMode = "$resourcesDir/modes/java" - val javaModeExamples = "$javaMode/examples" - val javaExamples = getSketches(File(javaModeExamples)) + val javaModeExamples = File("$javaMode/examples") + .listFiles() + ?.map { getSketches(it)} + ?: emptyList() - val json = serializer.encodeToString(listOf(javaExamples)) + val javaModeLibrariesExamples = File("$javaMode/libraries") + .listFiles{ it.isDirectory } + ?.map { library -> + val properties = library.resolve("library.properties") + val name = findNameInProperties(properties) ?: library.name + + val libraryExamples = getSketches(library.resolve("examples")) + Sketch.Companion.Folder( + type = "folder", + name = name, + path = library.absolutePath, + mode = "java", + children = libraryExamples?.children ?: emptyList(), + sketches = libraryExamples?.sketches ?: emptyList() + ) + } ?: emptyList() + val javaModeLibraries = Sketch.Companion.Folder( + type = "folder", + name = "Libraries", + path = "$javaMode/libraries", + mode = "java", + children = javaModeLibrariesExamples, + sketches = emptyList() + ) + + val contributedLibraries = sketchbookFolder.resolve("libraries") + .listFiles{ it.isDirectory } + ?.map { library -> + val properties = library.resolve("library.properties") + val name = findNameInProperties(properties) ?: library.name + // Get library name from library.properties if it exists + val libraryExamples = getSketches(library.resolve("examples")) + Sketch.Companion.Folder( + type = "folder", + name = name, + path = library.absolutePath, + mode = "java", + children = libraryExamples?.children ?: emptyList(), + sketches = libraryExamples?.sketches ?: emptyList() + ) + } ?: emptyList() + + val contributedLibrariesFolder = Sketch.Companion.Folder( + type = "folder", + name = "Contributed Libraries", + path = sketchbookFolder.resolve("libraries").absolutePath, + mode = "java", + children = contributedLibraries, + sketches = emptyList() + ) + + val contributedExamples = sketchbookFolder.resolve("examples") + .listFiles{ it.isDirectory } + ?.map { + val properties = it.resolve("examples.properties") + val name = findNameInProperties(properties) ?: it.name + + val sketches = getSketches(it.resolve("examples")) + Sketch.Companion.Folder( + type = "folder", + name, + path = it.absolutePath, + mode = "java", + children = sketches?.children ?: emptyList(), + sketches = sketches?.sketches ?: emptyList(), + ) + } + ?: emptyList() + val contributedExamplesFolder = Sketch.Companion.Folder( + type = "folder", + name = "Contributed Examples", + path = sketchbookFolder.resolve("examples").absolutePath, + mode = "java", + children = contributedExamples, + sketches = emptyList() + ) + + val json = serializer.encodeToString(javaModeExamples + javaModeLibraries + contributedLibrariesFolder + contributedExamplesFolder) println(json) - - // Build-in examples for each mode - // Get examples for core libraries - - // Examples downloaded in the sketchbook - // Library contributions - // Mode examples } + private fun findNameInProperties(properties: File): String? { + if (!properties.exists()) return null + return properties.readLines().firstNotNullOfOrNull { line -> + line.split("=", limit = 2) + .takeIf { it.size == 2 && it[0].trim() == "name" } + ?.let { it[1].trim() } + } + } } } \ No newline at end of file diff --git a/app/src/processing/app/api/Sketch.kt b/app/src/processing/app/api/Sketch.kt index 87f2cc56c..0b57f369d 100644 --- a/app/src/processing/app/api/Sketch.kt +++ b/app/src/processing/app/api/Sketch.kt @@ -23,7 +23,7 @@ class Sketch { val sketches: List = emptyList() ) - fun getSketches(file: File, filter: (File) -> Boolean = { true }): Folder { + fun getSketches(file: File, filter: (File) -> Boolean = { true }): Folder? { val name = file.name val (sketchesFolders, childrenFolders) = file.listFiles()?.filter (File::isDirectory)?.partition { isSketchFolder(it) } ?: return Folder( name = name, @@ -31,9 +31,11 @@ class Sketch { sketches = emptyList(), children = emptyList() ) - - val children = childrenFolders.filter(filter) .map { getSketches(it) } + val children = childrenFolders.filter(filter).mapNotNull { getSketches(it) } val sketches = sketchesFolders.map { Sketch(name = it.name, path = it.absolutePath) } + if(sketches.isEmpty() && children.isEmpty()) { + return null + } return Folder( name = name, path = file.absolutePath, diff --git a/app/src/processing/app/api/Sketchbook.kt b/app/src/processing/app/api/Sketchbook.kt index 72eec63ed..d3fdb411b 100644 --- a/app/src/processing/app/api/Sketchbook.kt +++ b/app/src/processing/app/api/Sketchbook.kt @@ -34,7 +34,15 @@ class Sketchbook: SuspendingCliktCommand() { // TODO: Currently blocked since `Base.getSketchbookFolder()` is not available in headless mode val sketchbookFolder = Platform.getDefaultSketchbookFolder() - val sketches = getSketches(sketchbookFolder) + val sketches = getSketches(sketchbookFolder) { + !listOf( + "android", + "modes", + "tools", + "examples", + "libraries" + ).contains(it.name) + } val json = serializer.encodeToString(listOf(sketches)) println(json) } From 511dd3a6364cbf7a4bf3ba444c86a52fb67fbce4 Mon Sep 17 00:00:00 2001 From: "Moon D." Date: Wed, 13 Aug 2025 15:29:04 -0400 Subject: [PATCH 10/14] Display once per revision number --- app/build.gradle.kts | 3 ++- app/src/processing/app/UpdateCheck.java | 18 +++++++++++++++--- app/src/processing/app/ui/WelcomeToBeta.kt | 6 +++++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5323a1a82..5491d8548 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -424,7 +424,6 @@ tasks.register("renameWindres") { } tasks.register("includeProcessingResources"){ dependsOn( - "includeJdk", "includeCore", "includeJavaMode", "includeSharedAssets", @@ -433,6 +432,7 @@ tasks.register("includeProcessingResources"){ "includeJavaModeResources", "renameWindres" ) + mustRunAfter("includeJdk") finalizedBy("signResources") } @@ -539,6 +539,7 @@ afterEvaluate { dependsOn("includeProcessingResources") } tasks.named("createDistributable").configure { + dependsOn("includeJdk") finalizedBy("setExecutablePermissions") } } diff --git a/app/src/processing/app/UpdateCheck.java b/app/src/processing/app/UpdateCheck.java index 1bfa29688..142e43758 100644 --- a/app/src/processing/app/UpdateCheck.java +++ b/app/src/processing/app/UpdateCheck.java @@ -35,6 +35,7 @@ import processing.app.ui.WelcomeToBeta; import processing.core.PApplet; + /** * Threaded class to check for updates in the background. *

@@ -125,7 +126,6 @@ public class UpdateCheck { Preferences.set("update.last", String.valueOf(now)); if (base.activeEditor != null) { -// boolean offerToUpdateContributions = true; if (latest > Base.getRevision()) { System.out.println("You are running Processing revision 0" + @@ -135,8 +135,20 @@ public class UpdateCheck { // offerToUpdateContributions = !promptToVisitDownloadPage(); promptToVisitDownloadPage(); } - if(latest < Base.getRevision()){ - WelcomeToBeta.showWelcomeToBeta(); + + String lastBetaSeenStr = Preferences.get("beta.last_beta_welcome_seen"); + int lastBetaSeen = 0; + if (lastBetaSeenStr != null) { + lastBetaSeen = Integer.parseInt(lastBetaSeenStr); + } + int revision = Base.getRevision(); + System.err.println("MOON DEBUG" + + Base.getRevision() + ", and lastBetaSeen is " + + lastBetaSeen + "."); + + + if(latest < revision && revision != lastBetaSeen ) { + WelcomeToBeta.showWelcomeToBeta(); } /* diff --git a/app/src/processing/app/ui/WelcomeToBeta.kt b/app/src/processing/app/ui/WelcomeToBeta.kt index d7492fa6a..e0ac05073 100644 --- a/app/src/processing/app/ui/WelcomeToBeta.kt +++ b/app/src/processing/app/ui/WelcomeToBeta.kt @@ -35,6 +35,7 @@ import com.mikepenz.markdown.m2.markdownColor import com.mikepenz.markdown.m2.markdownTypography import com.mikepenz.markdown.model.MarkdownColors import com.mikepenz.markdown.model.MarkdownTypography +import processing.app.Preferences import processing.app.Base.getRevision import processing.app.Base.getVersionName import processing.app.ui.theme.LocalLocale @@ -61,7 +62,10 @@ class WelcomeToBeta { val mac = SystemInfo.isMacFullWindowContentSupported SwingUtilities.invokeLater { JFrame(windowTitle).apply { - val close = { dispose() } + val close = { + Preferences.set("beta.last_beta_welcome_seen", getRevision().toString()) + dispose() + } rootPane.putClientProperty("apple.awt.transparentTitleBar", mac) rootPane.putClientProperty("apple.awt.fullWindowContent", mac) defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE From f09b2c51001ec54925fb4c4674388a4058b152f5 Mon Sep 17 00:00:00 2001 From: "Moon D." Date: Wed, 13 Aug 2025 16:13:19 -0400 Subject: [PATCH 11/14] Add update.beta_welcome to defaults --- build/shared/lib/defaults.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build/shared/lib/defaults.txt b/build/shared/lib/defaults.txt index 6e3e00f0d..1cfc190ca 100644 --- a/build/shared/lib/defaults.txt +++ b/build/shared/lib/defaults.txt @@ -76,6 +76,10 @@ theme.gradient.method = rgb # on how many people are using Processing) update.check = true +# default value for beta_welcome +# -1 means no beta has been run +update.beta_welcome = -1 + # on windows, automatically associate .pde files with processing.exe platform.auto_file_type_associations = true From 673c2224ba988672098da47df5a95539d58acdcc Mon Sep 17 00:00:00 2001 From: "Moon D." Date: Wed, 13 Aug 2025 16:13:42 -0400 Subject: [PATCH 12/14] Clean up and updating key name --- app/src/processing/app/UpdateCheck.java | 19 +++++-------------- app/src/processing/app/ui/WelcomeToBeta.kt | 2 +- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/app/src/processing/app/UpdateCheck.java b/app/src/processing/app/UpdateCheck.java index 142e43758..e18daee3e 100644 --- a/app/src/processing/app/UpdateCheck.java +++ b/app/src/processing/app/UpdateCheck.java @@ -113,6 +113,7 @@ public class UpdateCheck { System.getProperty("os.arch")); int latest = readInt(LATEST_URL + "?" + info); + int revision = Base.getRevision(); String lastString = Preferences.get("update.last"); long now = System.currentTimeMillis(); @@ -127,27 +128,17 @@ public class UpdateCheck { if (base.activeEditor != null) { - if (latest > Base.getRevision()) { + if (latest > revision) { System.out.println("You are running Processing revision 0" + - Base.getRevision() + ", the latest build is 0" + + revision + ", the latest build is 0" + latest + "."); // Assume the person is busy downloading the latest version // offerToUpdateContributions = !promptToVisitDownloadPage(); promptToVisitDownloadPage(); } - String lastBetaSeenStr = Preferences.get("beta.last_beta_welcome_seen"); - int lastBetaSeen = 0; - if (lastBetaSeenStr != null) { - lastBetaSeen = Integer.parseInt(lastBetaSeenStr); - } - int revision = Base.getRevision(); - System.err.println("MOON DEBUG" + - Base.getRevision() + ", and lastBetaSeen is " + - lastBetaSeen + "."); - - - if(latest < revision && revision != lastBetaSeen ) { + int lastBetaWelcomeSeen = Preferences.getInteger("update.beta_welcome"); + if(latest < revision && revision != lastBetaWelcomeSeen ) { WelcomeToBeta.showWelcomeToBeta(); } diff --git a/app/src/processing/app/ui/WelcomeToBeta.kt b/app/src/processing/app/ui/WelcomeToBeta.kt index e0ac05073..7757e820f 100644 --- a/app/src/processing/app/ui/WelcomeToBeta.kt +++ b/app/src/processing/app/ui/WelcomeToBeta.kt @@ -63,7 +63,7 @@ class WelcomeToBeta { SwingUtilities.invokeLater { JFrame(windowTitle).apply { val close = { - Preferences.set("beta.last_beta_welcome_seen", getRevision().toString()) + Preferences.set("update.beta_welcome", getRevision().toString()) dispose() } rootPane.putClientProperty("apple.awt.transparentTitleBar", mac) From 960e7582ece4b20db3a338db54ba6f10dd91bf8f Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 14:42:50 +0000 Subject: [PATCH 13/14] docs: update README.md [skip ci] --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c27391e6f..18920086f 100644 --- a/README.md +++ b/README.md @@ -313,6 +313,7 @@ _Note: due to GitHub's limitations, this repository's [Contributors](https://git Andrew
Andrew

💻 Ngoc Doan
Ngoc Doan

💻 Manoel Ribeiro
Manoel Ribeiro

📖 + Moon
Moon

💻 From c8e272ed1909488322c4078a73839f030b61eae2 Mon Sep 17 00:00:00 2001 From: "allcontributors[bot]" <46447321+allcontributors[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 14:42:51 +0000 Subject: [PATCH 14/14] docs: update .all-contributorsrc [skip ci] --- .all-contributorsrc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index d1d18fb98..5ba97fcde 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -1642,6 +1642,15 @@ "contributions": [ "doc" ] + }, + { + "login": "catilac", + "name": "Moon", + "avatar_url": "https://avatars.githubusercontent.com/u/15107?v=4", + "profile": "https://softmoon.world", + "contributions": [ + "code" + ] } ], "repoType": "github",