From c7fde2ed8c058d6572251ceccad54f6b78aef939 Mon Sep 17 00:00:00 2001 From: Timur Aliberdov Date: Thu, 11 Sep 2025 17:05:11 +0200 Subject: [PATCH] IDEA-377018: add an ability to use different splash screens depending on subscription GitOrigin-RevId: 396dacfc24e9ab814f2e9d6645c0a4ae938999b7 --- .../application/impl/ApplicationInfoImpl.java | 21 +++- .../src/idea/ApplicationInfo.xsd | 1 + .../application/impl/ApplicationInfoTest.java | 111 +++++++++++++++++- .../intellij/openapi/application/impl/util.kt | 33 +++++- 4 files changed, 159 insertions(+), 7 deletions(-) diff --git a/platform/core-impl/src/com/intellij/openapi/application/impl/ApplicationInfoImpl.java b/platform/core-impl/src/com/intellij/openapi/application/impl/ApplicationInfoImpl.java index cb3ee401aa7a..2d5f82f9c019 100644 --- a/platform/core-impl/src/com/intellij/openapi/application/impl/ApplicationInfoImpl.java +++ b/platform/core-impl/src/com/intellij/openapi/application/impl/ApplicationInfoImpl.java @@ -7,6 +7,7 @@ import com.intellij.diagnostic.StartUpMeasurer; import com.intellij.ide.plugins.PluginManagerCore; import com.intellij.openapi.application.ApplicationNamesInfo; import com.intellij.openapi.application.IdeUrlTrackingParametersProvider; +import com.intellij.openapi.application.PathManager; import com.intellij.openapi.application.ex.ApplicationInfoEx; import com.intellij.openapi.application.ex.ApplicationManagerEx; import com.intellij.openapi.diagnostic.Logger; @@ -20,6 +21,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.VisibleForTesting; +import java.nio.file.Files; +import java.nio.file.Path; import java.text.MessageFormat; import java.time.ZoneOffset; import java.time.ZonedDateTime; @@ -34,6 +37,7 @@ import java.util.*; public final class ApplicationInfoImpl extends ApplicationInfoEx { public static final String DEFAULT_PLUGINS_HOST = "https://plugins.jetbrains.com"; public static final String IDEA_PLUGINS_HOST_PROPERTY = "idea.plugins.host"; + public static final String FREE_MODE_SPLASH_MARKER_FILE_NAME = "splash-free-mode.txt"; private static final String IDEA_APPLICATION_INFO_DEFAULT_DARK_LAF = "idea.application.info.default.dark.laf"; private static final String IDEA_APPLICATION_INFO_DEFAULT_CLASSIC_DARK_LAF = "idea.application.info.default.classic.dark.laf"; @@ -57,6 +61,7 @@ public final class ApplicationInfoImpl extends ApplicationInfoEx { private String myShortCompanyName; private String myCompanyUrl = "https://www.jetbrains.com/"; private @Nullable String splashImageUrl; + private @Nullable String freeModeSplashImageUrl; private @Nullable String eapSplashImageUrl; private String svgIconUrl; private String mySvgEapIconUrl; @@ -144,6 +149,11 @@ public final class ApplicationInfoImpl extends ApplicationInfoEx { } break; + case "logo-free-mode": { + freeModeSplashImageUrl = getAttributeValue(child, "url"); + } + break; + case "icon": { svgIconUrl = child.getAttributeValue("svg"); mySmallSvgIconUrl = child.getAttributeValue("svg-small"); @@ -432,7 +442,16 @@ public final class ApplicationInfoImpl extends ApplicationInfoEx { @Override public @Nullable String getSplashImageUrl() { - return isEap && eapSplashImageUrl != null ? eapSplashImageUrl : splashImageUrl; + if (isEap && eapSplashImageUrl != null) return eapSplashImageUrl; + + if (freeModeSplashImageUrl != null) { + Path markerFile = PathManager.getConfigDir().resolve(FREE_MODE_SPLASH_MARKER_FILE_NAME); + if (Files.exists(markerFile)) { + return freeModeSplashImageUrl; + } + } + + return splashImageUrl; } @Override diff --git a/platform/platform-resources/src/idea/ApplicationInfo.xsd b/platform/platform-resources/src/idea/ApplicationInfo.xsd index 6874204898a0..a4cbfba02d07 100644 --- a/platform/platform-resources/src/idea/ApplicationInfo.xsd +++ b/platform/platform-resources/src/idea/ApplicationInfo.xsd @@ -9,6 +9,7 @@ + diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/application/impl/ApplicationInfoTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/application/impl/ApplicationInfoTest.java index 520fd20cf5ad..5eecfdbcf4fd 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/application/impl/ApplicationInfoTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/application/impl/ApplicationInfoTest.java @@ -1,15 +1,20 @@ -// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.openapi.application.impl; +import com.intellij.openapi.application.ApplicationInfo; import com.intellij.testFramework.PlatformTestUtil; import com.intellij.util.xml.dom.XmlElement; import org.jetbrains.annotations.NotNull; import org.junit.Test; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Map; +import static com.intellij.openapi.application.impl.UtilKt.withTempConfigDirectory; import static org.assertj.core.api.Assertions.assertThat; public class ApplicationInfoTest { @@ -31,10 +36,114 @@ public class ApplicationInfoTest { }); } + @Test + public void splashImage_eapLogoPreferredInEap() { + ApplicationInfo info = createAppInfo(eap(true), logo, logoEap); + assertThat(info.isEAP()).isTrue(); + String url = info.getSplashImageUrl(); + assertThat(url).isEqualTo(logoEapUrl); + } + + @Test + public void splashImage_regularLogoWhenNotEap() { + ApplicationInfo info = createAppInfo(eap(false), logo, logoEap); + assertThat(info.isEAP()).isFalse(); + String url = info.getSplashImageUrl(); + assertThat(url).isEqualTo(logoUrl); + } + + @Test + public void splashImage_regularLogoInEapWithNoEapLogoProvided() { + ApplicationInfo info = createAppInfo(eap(true), logo); + assertThat(info.isEAP()).isTrue(); + String url = info.getSplashImageUrl(); + assertThat(url).isEqualTo(logoUrl); + } + + @Test + public void splashImage_freeModeLogoPreferredWhenMarkerPresent() { + withTempConfigDirectory(configDir -> { + createFreeModeSplashMarkerFile(configDir); + ApplicationInfo info = createAppInfo(logo, logoFreeMode); + String url = info.getSplashImageUrl(); + assertThat(url).isEqualTo(logoFreeModeUrl); + }); + } + + @Test + public void splashImage_regularLogoWhenNoFreeModeMarker() { + withTempConfigDirectory(configDir -> { + deleteFreeModeSplashMarkerFile(configDir); + ApplicationInfo info = createAppInfo(logo, logoFreeMode); + String url = info.getSplashImageUrl(); + assertThat(url).isEqualTo(logoUrl); + }); + } + + @Test + public void splashImage_regularLogoWhenMarkerPresentButNoFreeModeLogoProvided() { + withTempConfigDirectory(configDir -> { + createFreeModeSplashMarkerFile(configDir); + ApplicationInfo info = createAppInfo(logo); + String url = info.getSplashImageUrl(); + assertThat(url).isEqualTo(logoUrl); + }); + } + + @Test + public void splashImage_eapLogoBeatsFreeModeInEapWhenBothAvailableAndMarkerPresent() { + withTempConfigDirectory(configDir -> { + createFreeModeSplashMarkerFile(configDir); + ApplicationInfo info = createAppInfo(eap(true), logo, logoEap, logoFreeMode); + String url = info.getSplashImageUrl(); + assertThat(url).isEqualTo(logoEapUrl); + }); + } + + @Test + public void splashImage_freeModeLogoWhenNotEapAndMarkerPresent() { + withTempConfigDirectory(configDir -> { + createFreeModeSplashMarkerFile(configDir); + ApplicationInfo info = createAppInfo(eap(false), logo, logoEap, logoFreeMode); + String url = info.getSplashImageUrl(); + assertThat(url).isEqualTo(logoFreeModeUrl); + }); + } + + @Test + public void splashImage_regularLogoWhenNotEapAndNoMarker() { + withTempConfigDirectory(configDir -> { + deleteFreeModeSplashMarkerFile(configDir); + ApplicationInfo info = createAppInfo(eap(false), logo, logoEap, logoFreeMode); + String url = info.getSplashImageUrl(); + assertThat(url).isEqualTo(logoUrl); + }); + } + public static @NotNull ApplicationInfoImpl createAppInfo(@NotNull XmlElement @NotNull ... content) { var children = new ArrayList<>(List.of(content)); children.add(new XmlElement("icon", Map.of("svg", "xxx.svg", "svg-small", "xxx.svg"), List.of(), null)); children.add(new XmlElement("plugins", Map.of(), List.of(), null)); return new ApplicationInfoImpl(new XmlElement("state", Map.of(), children, null)); } + + private static void createFreeModeSplashMarkerFile(@NotNull Path configDir) throws IOException { + Files.writeString(configDir.resolve(ApplicationInfoImpl.FREE_MODE_SPLASH_MARKER_FILE_NAME), ""); + } + + private static void deleteFreeModeSplashMarkerFile(@NotNull Path configDir) throws IOException { + Files.deleteIfExists(configDir.resolve(ApplicationInfoImpl.FREE_MODE_SPLASH_MARKER_FILE_NAME)); + } + + private static XmlElement eap(boolean isEap) { + return new XmlElement("version", Map.of("eap", String.valueOf(isEap)), List.of(), null); + } + + private static final String logoUrl = "/logo.png"; + private static final String logoEapUrl = "/logo_eap.png"; + private static final String logoFreeModeUrl = "/logo_free.png"; + + private final XmlElement logo = new XmlElement("logo", Map.of("url", logoUrl), List.of(), null); + private final XmlElement logoEap = new XmlElement("logo-eap", Map.of("url", logoEapUrl), List.of(), null); + private final XmlElement logoFreeMode = new XmlElement("logo-free-mode", Map.of("url", logoFreeModeUrl), List.of(), null); } diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/application/impl/util.kt b/platform/platform-tests/testSrc/com/intellij/openapi/application/impl/util.kt index c070db3365b9..6f5889aaa417 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/application/impl/util.kt +++ b/platform/platform-tests/testSrc/com/intellij/openapi/application/impl/util.kt @@ -2,19 +2,19 @@ package com.intellij.openapi.application.impl import com.intellij.concurrency.ContextAwareRunnable -import com.intellij.openapi.application.Application -import com.intellij.openapi.application.EDT -import com.intellij.openapi.application.ModalityState -import com.intellij.openapi.application.asContextElement +import com.intellij.openapi.application.* import com.intellij.testFramework.LeakHunter +import com.intellij.testFramework.utils.io.deleteRecursively import com.intellij.util.ui.EDT import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext -import org.junit.jupiter.api.Assertions.assertNotNull +import org.junit.jupiter.api.function.ThrowingConsumer +import java.nio.file.Path import java.util.function.Supplier import javax.swing.SwingUtilities import kotlin.coroutines.resume +import kotlin.io.path.createTempDirectory import kotlin.test.assertFalse fun Application.withModality(action: () -> Unit) { @@ -64,3 +64,26 @@ suspend fun pumpEDT() { internal suspend fun processApplicationQueue() { withContext(Dispatchers.EDT + ModalityState.any().asContextElement()) {} } + +internal fun withTempDirectory(block: ThrowingConsumer) { + val tempDirectory = createTempDirectory() + try { + block.accept(tempDirectory) + } + finally { + tempDirectory.deleteRecursively() + } +} + +internal fun withTempConfigDirectory(block: ThrowingConsumer) { + withTempDirectory { tempDirectory -> + val oldConfigDir = PathManager.getConfigDir() + try { + PathManager.setExplicitConfigPath(tempDirectory) + block.accept(tempDirectory) + } + finally { + PathManager.setExplicitConfigPath(oldConfigDir); + } + } +}