diff --git a/android/core/build.gradle.kts b/android/core/build.gradle.kts index 0931388..9831dc5 100644 --- a/android/core/build.gradle.kts +++ b/android/core/build.gradle.kts @@ -2,9 +2,23 @@ plugins { `java-library` } +val ts3ClientDir: String = providers.gradleProperty("ts3.client.dir") + .orElse(providers.environmentVariable("TS3_CLIENT_DIR")) + .getOrElse(rootDir.resolve("../TeamSpeak3-Client-linux_amd64").path) + +/** TeamSpeak's own icon packs, proprietary and so taken from an install, as the desktop's build does. */ +val teamspeakIconPacks = tasks.register("teamspeakIconPacks") { + into(layout.buildDirectory.dir("generated/iconPacks")) + from(File(ts3ClientDir, "gfx")) { + include("default.zip", "default_colored_2014.zip", "default_mono_2014.zip") + into("gfx/teamspeak") + } +} + java { sourceSets["main"].java.setSrcDirs(listOf(rootDir.resolve("../ts3-client/core/src/main/java"))) sourceSets["main"].resources.setSrcDirs(listOf(rootDir.resolve("../ts3-client/core/src/main/resources"))) + sourceSets["main"].resources.srcDir(teamspeakIconPacks) } tasks.withType().configureEach { diff --git a/ts3-client/core/pom.xml b/ts3-client/core/pom.xml index 5d2d22a..1988905 100644 --- a/ts3-client/core/pom.xml +++ b/ts3-client/core/pom.xml @@ -17,6 +17,8 @@ 17 + + ${project.basedir}/../../TeamSpeak3-Client-linux_amd64 @@ -31,4 +33,22 @@ test + + + + + src/main/resources + + + + ${ts3.client.dir}/gfx + gfx/teamspeak + + default.zip + default_colored_2014.zip + default_mono_2014.zip + + + + diff --git a/ts3-client/core/src/main/java/com/ts3client/gfx/IconPack.java b/ts3-client/core/src/main/java/com/ts3client/gfx/IconPack.java index 9a7a881..d83bd91 100644 --- a/ts3-client/core/src/main/java/com/ts3client/gfx/IconPack.java +++ b/ts3-client/core/src/main/java/com/ts3client/gfx/IconPack.java @@ -17,6 +17,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; /** * A TeamSpeak icon pack: a zip (or unpacked directory) of images plus an optional @@ -173,7 +174,34 @@ public final class IconPack { Map files = zipped ? listZip(source) : listDirectory(source); if (files == null || files.isEmpty()) return null; Storage storage = zipped ? path -> readFromZip(source, path) : path -> readFromDirectory(source, path); + return create(source.getName(), source, files, storage); + } + /** + * Reads a pack's {@code .zip} shipped on the classpath, held in memory as it cannot be + * opened as a file there. + * + * @param resource the zip, e.g. {@code /gfx/teamspeak/default.zip} + * @return the pack, or {@code null} when it is not there or holds no icons + */ + public static IconPack loadZipResource(String resource) { + Map contents = new LinkedHashMap<>(); + try (InputStream in = IconPack.class.getResourceAsStream(resource)) { + if (in == null) return null; + try (ZipInputStream zip = new ZipInputStream(in)) { + for (ZipEntry e; (e = zip.getNextEntry()) != null; ) { + if (!e.isDirectory()) contents.put(e.getName(), zip.readAllBytes()); + } + } + } catch (IOException e) { + return null; + } + Map files = new LinkedHashMap<>(); + contents.forEach((path, data) -> files.put(path, (long) data.length)); + return create(resource.substring(resource.lastIndexOf('/') + 1), null, files, contents::get); + } + + private static IconPack create(String id, File source, Map files, Storage storage) { String iniPath = files.keySet().stream() .filter(p -> p.equalsIgnoreCase(INI)) .findFirst().orElse(null); @@ -190,7 +218,6 @@ public final class IconPack { : byConvention(files); if (entries.isEmpty()) return null; - String id = source.getName(); String name = info.getOrDefault("name", displayName(id)); return new IconPack(id, source, name, storage, fallback, entries); } diff --git a/ts3-client/core/src/main/java/com/ts3client/gfx/IconPacks.java b/ts3-client/core/src/main/java/com/ts3client/gfx/IconPacks.java index 451a3d4..111b0b5 100644 --- a/ts3-client/core/src/main/java/com/ts3client/gfx/IconPacks.java +++ b/ts3-client/core/src/main/java/com/ts3client/gfx/IconPacks.java @@ -23,14 +23,21 @@ public final class IconPacks { /** The pack shipped with the client, drawn from Google's Material icons. */ public static final String MATERIAL_ID = "material"; + /** TeamSpeak's own packs, which the build ships when it finds a TeamSpeak 3 client to take them from. */ + private static final List TEAMSPEAK_IDS = + List.of(DEFAULT_ID, "default_colored_2014.zip", "default_mono_2014.zip"); + private static final String TEAMSPEAK_RESOURCE = "/gfx/teamspeak/"; + private static volatile IconPack material; + private static volatile List teamspeak; private IconPacks() { } /** * Loads every pack found under {@code extraDirectory} and the well-known - * locations, the first search root winning on a name clash. + * locations, the first search root winning on a name clash, then the shipped + * ones the folders did not have. * * @param userDirectory the client's own pack folder (may not exist yet) * @param extraDirectory an additional folder chosen by the user, or {@code null} @@ -48,6 +55,7 @@ public final class IconPacks { if (pack != null) byId.put(pack.id(), pack); } } + for (IconPack shipped : teamspeak()) byId.putIfAbsent(shipped.id(), shipped); IconPack shipped = material(); if (shipped != null) byId.putIfAbsent(shipped.id(), shipped); List packs = new ArrayList<>(byId.values()); @@ -61,6 +69,19 @@ public final class IconPacks { return material; } + /** @return TeamSpeak's packs the build shipped, which a pack of the same name found on disk replaces */ + public static List teamspeak() { + if (teamspeak == null) { + List packs = new ArrayList<>(); + for (String id : TEAMSPEAK_IDS) { + IconPack pack = IconPack.loadZipResource(TEAMSPEAK_RESOURCE + id); + if (pack != null) packs.add(pack); + } + teamspeak = List.copyOf(packs); + } + return teamspeak; + } + /** @return the pack with this id, or {@code null} when it is not installed */ public static IconPack select(List packs, String id) { for (IconPack p : packs) {