Merge branch 'processing:main' into main

This commit is contained in:
lassevonpfeil
2025-08-20 18:57:23 +02:00
committed by GitHub
15 changed files with 421 additions and 72 deletions

View File

@@ -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",

View File

@@ -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'

View File

@@ -313,6 +313,7 @@ _Note: due to GitHub's limitations, this repository's [Contributors](https://git
<td align="center" valign="top" width="16.66%"><a href="https://github.com/aj-m"><img src="https://avatars.githubusercontent.com/u/2524348?v=4?s=120" width="120px;" alt="Andrew"/><br /><sub><b>Andrew</b></sub></a><br /><a href="https://github.com/processing/processing4/commits?author=aj-m" title="Code">💻</a></td>
<td align="center" valign="top" width="16.66%"><a href="https://github.com/pnngocdoan"><img src="https://avatars.githubusercontent.com/u/113954980?v=4?s=120" width="120px;" alt="Ngoc Doan"/><br /><sub><b>Ngoc Doan</b></sub></a><br /><a href="https://github.com/processing/processing4/commits?author=pnngocdoan" title="Code">💻</a></td>
<td align="center" valign="top" width="16.66%"><a href="https://github.com/manoellribeiro"><img src="https://avatars.githubusercontent.com/u/59377764?v=4?s=120" width="120px;" alt="Manoel Ribeiro"/><br /><sub><b>Manoel Ribeiro</b></sub></a><br /><a href="https://github.com/processing/processing4/commits?author=manoellribeiro" title="Documentation">📖</a></td>
<td align="center" valign="top" width="16.66%"><a href="https://softmoon.world"><img src="https://avatars.githubusercontent.com/u/15107?v=4?s=120" width="120px;" alt="Moon"/><br /><sub><b>Moon</b></sub></a><br /><a href="https://github.com/processing/processing4/commits?author=catilac" title="Code">💻</a></td>
</tr>
</tbody>
</table>

View File

@@ -124,6 +124,7 @@ dependencies {
testImplementation(libs.junitJupiterParams)
implementation(libs.clikt)
implementation(libs.kotlinxSerializationJson)
}
tasks.test {
@@ -228,61 +229,44 @@ tasks.register<Exec>("packageCustomMsi"){
tasks.register("generateSnapConfiguration"){
val name = findProperty("snapname") ?: rootProject.name
onlyIf { OperatingSystem.current().isLinux }
val distributable = tasks.named<AbstractJPackageTask>("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<AbstractJPackageTask>("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<Exec>("packageSnap"){
@@ -424,7 +408,6 @@ tasks.register<Copy>("renameWindres") {
}
tasks.register("includeProcessingResources"){
dependsOn(
"includeJdk",
"includeCore",
"includeJavaMode",
"includeSharedAssets",
@@ -433,6 +416,7 @@ tasks.register("includeProcessingResources"){
"includeJavaModeResources",
"renameWindres"
)
mustRunAfter("includeJdk")
finalizedBy("signResources")
}
@@ -539,6 +523,7 @@ afterEvaluate {
dependsOn("includeProcessingResources")
}
tasks.named("createDistributable").configure {
dependsOn("includeJdk")
finalizedBy("setExecutablePermissions")
}
}

View File

@@ -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

View File

@@ -166,18 +166,6 @@ public class Base {
static private void createAndShowGUI(String[] args) {
// these times are fairly negligible relative to Base.<init>
// 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

View File

@@ -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() &&

View File

@@ -10,7 +10,12 @@ 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.api.Contributions
import processing.app.api.Sketchbook
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 +34,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())
@@ -40,7 +50,9 @@ suspend fun main(args: Array<String>){
Processing()
.subcommands(
LSP(),
LegacyCLI(args)
LegacyCLI(args),
Contributions(),
Sketchbook()
)
.main(args)
}
@@ -49,6 +61,9 @@ 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<String>::class.java)
@@ -68,10 +83,9 @@ class LegacyCLI(val args: Array<String>): SuspendingCliktCommand("cli") {
override suspend fun run() {
try {
if (arguments.contains("--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<String>::class.java)
.invoke(null, arguments.toTypedArray())
@@ -80,3 +94,49 @@ class LegacyCLI(val args: Array<String>): SuspendingCliktCommand("cli") {
}
}
}
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))
}

View File

@@ -35,6 +35,7 @@ import processing.app.ui.WelcomeToBeta;
import processing.core.PApplet;
/**
* Threaded class to check for updates in the background.
* <p/>
@@ -112,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();
@@ -125,18 +127,19 @@ public class UpdateCheck {
Preferences.set("update.last", String.valueOf(now));
if (base.activeEditor != null) {
// boolean offerToUpdateContributions = true;
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();
}
if(latest < Base.getRevision()){
WelcomeToBeta.showWelcomeToBeta();
int lastBetaWelcomeSeen = Preferences.getInteger("update.beta_welcome");
if(latest < revision && revision != lastBetaWelcomeSeen ) {
WelcomeToBeta.showWelcomeToBeta();
}
/*

View File

@@ -0,0 +1,144 @@
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.encodeToString
import kotlinx.serialization.json.Json
import processing.app.Platform
import processing.app.api.Sketch.Companion.getSketches
import java.io.File
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") {
val serializer = Json {
prettyPrint = true
}
override fun help(context: Context) = "List all examples"
override suspend fun run() {
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 = File("$javaMode/examples")
.listFiles()
?.map { getSketches(it)}
?: emptyList()
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)
}
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() }
}
}
}
}

View File

@@ -0,0 +1,50 @@
package processing.app.api
import kotlinx.serialization.Serializable
import java.io.File
class Sketch {
companion object{
@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<Folder> = emptyList(),
val sketches: List<Sketch> = emptyList()
)
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,
path = file.absolutePath,
sketches = emptyList(),
children = emptyList()
)
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,
children = children,
sketches = sketches
)
}
fun isSketchFolder(file: File): Boolean {
return file.isDirectory && file.listFiles().any { it.isFile && it.name.endsWith(".pde") }
}
}
}

View File

@@ -0,0 +1,50 @@
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.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")
}
init {
subcommands(SketchbookList())
}
class SketchbookList: SuspendingCliktCommand("list") {
val serializer = Json {
prettyPrint = true
}
override fun help(context: Context) = "List all sketches"
override suspend fun run() {
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) {
!listOf(
"android",
"modes",
"tools",
"examples",
"libraries"
).contains(it.name)
}
val json = serializer.encodeToString(listOf(sketches))
println(json)
}
}
}

View File

@@ -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("update.beta_welcome", getRevision().toString())
dispose()
}
rootPane.putClientProperty("apple.awt.transparentTitleBar", mac)
rootPane.putClientProperty("apple.awt.fullWindowContent", mac)
defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE

View File

@@ -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

View File

@@ -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" }