Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
6d1eb12
Add Apple TV (tvOS) and Google TV (Android TV) support
shai-almog Jun 19, 2026
d5d6252
tvOS native slice: guard tvOS-absent APIs (compile progress)
shai-almog Jun 20, 2026
4584450
tvOS native: GLViewController fully compiles; correct IOSNative approach
shai-almog Jun 20, 2026
530a99a
Fix iOS/Mac build regression + developer-guide prose gate
shai-almog Jun 20, 2026
09406e2
Merge remote-tracking branch 'origin/master' into tv-support
shai-almog Jun 20, 2026
5e83ce9
tvOS: guard remaining tvOS-absent APIs in IOSNative.m so the appletvo…
shai-almog Jun 20, 2026
6549e03
tvOS: fix CI-only build breaks (WebKit import on tvOS + arm64 sim slice)
shai-almog Jun 20, 2026
c8345b2
tvOS: guard the camera-capture block (INCLUDE_CAMERA_USAGE) for tvOS
shai-almog Jun 21, 2026
344cb72
tvOS: omit the foreground willPresentNotification handler (userInfo a…
shai-almog Jun 21, 2026
c876b45
tvOS: exclude the CN1Camera AVCapture implementation on tvOS
shai-almog Jun 21, 2026
0d7bbdb
tvOS: fix launch crash (NIB excluded) + add run-tv diagnostics
shai-almog Jun 21, 2026
e0664ea
tvOS: render real content into screenshots (programmatic METALView + …
shai-almog Jun 21, 2026
c6f05df
tvOS: seed screenshots-tv goldens from the Apple TV 4K simulator capture
shai-almog Jun 21, 2026
d97cb25
tvOS: reseed screenshots-tv goldens from CI capture + flip build-ios-…
shai-almog Jun 21, 2026
0437ccb
tvOS: reseed graphics-draw-round-rect golden (seed run captured the p…
shai-almog Jun 21, 2026
c798c18
tvOS: document the working render/capture pipeline + local-repro reci…
shai-almog Jun 21, 2026
107d1fe
tvOS: skip DrawRoundRect (SIGBUS crashes the suite) + drop its golden
shai-almog Jun 21, 2026
dbe3edf
Revert "tvOS: skip DrawRoundRect (SIGBUS crashes the suite) + drop it…
shai-almog Jun 21, 2026
8dff7c1
tvOS: instrument the signal handler with a backtrace dump (diagnostic)
shai-almog Jun 21, 2026
a4f5223
Revert "tvOS: instrument the signal handler with a backtrace dump (di…
shai-almog Jun 21, 2026
4507ec9
iOS/tvOS: clamp path alpha-mask extent to the Metal texture limit (fi…
shai-almog Jun 21, 2026
9c50fb1
Revert "iOS/tvOS: clamp path alpha-mask extent to the Metal texture l…
shai-almog Jun 21, 2026
1ac9253
iOS/tvOS: bound path alpha-mask to the render target (fixes tvOS Draw…
shai-almog Jun 21, 2026
39949f6
tvOS: reseed graphics-draw-arc/fill-round-rect goldens from the crash…
shai-almog Jun 21, 2026
1867031
tvOS: widen per-test screenshot timeout to 90s so 4K captures finish …
shai-almog Jun 21, 2026
7841c86
Revert "tvOS: widen per-test screenshot timeout to 90s so 4K captures…
shai-almog Jun 21, 2026
2ef959a
tvOS: drop late screenshot captures so a timed-out 4K test is missing…
shai-almog Jun 21, 2026
366ad6a
Merge remote-tracking branch 'origin/master' into tv-support
shai-almog Jun 21, 2026
5b5811e
tvOS: exclude the 4 dense nested-curve graphics tests from the 4K pix…
shai-almog Jun 21, 2026
8119603
cn1ss: gate the late-capture discard to tvOS only
shai-almog Jun 21, 2026
eb46f48
iOS/tvOS: clamp alpha coverage index (fix intermittent tvOS SIGBUS) +…
shai-almog Jun 21, 2026
7e36602
tvOS: remove the temporary SignalHandler backtrace instrumentation
shai-almog Jun 21, 2026
df78125
iOS: stop tvNative from forcing Metal on the iOS app target
shai-almog Jun 22, 2026
1f3eb20
Merge remote-tracking branch 'origin/master' into tv-support
shai-almog Jun 22, 2026
cf2cec4
TV: document build-hint tables + skip GoogleWebMap on tvOS
shai-almog Jun 22, 2026
ab22ca4
tvOS: guard MKMapView.rotateEnabled in the Apple map provider
shai-almog Jun 22, 2026
23972ff
tvOS: seed map + app-review screenshot goldens from CI capture
shai-almog Jun 22, 2026
37cd6a3
tvOS: run the heavy graphics tests (adapt, don't skip) + document TV …
shai-almog Jun 22, 2026
1e0f246
tvOS: seed goldens for the adapted round-rect/arc graphics tests
shai-almog Jun 22, 2026
826067d
docs: fix vale prose gate on the new TV build-hint rows
shai-almog Jun 22, 2026
d2f791f
tvOS: register bundled TrueType fonts via UIAppFonts (fixes missing M…
shai-almog Jun 23, 2026
e3c4160
iOS/tvOS/watchOS: register bundled .ttf fonts at runtime (fixes Mater…
shai-almog Jun 23, 2026
aff63ef
tvOS/watch: re-seed Material-icon goldens now that the font loads
shai-almog Jun 23, 2026
9ac8ea1
tvOS/watch: re-seed remaining Material-icon goldens missed in the fir…
shai-almog Jun 23, 2026
20d7e21
ci: re-trigger iOS screenshot suite for the Material-icon golden re-seed
shai-almog Jun 23, 2026
a7b232e
tvOS: note Material-font runtime registration in the run-tv-ui-tests …
shai-almog Jun 23, 2026
9125d2d
Merge remote-tracking branch 'origin/master' into tv-support
shai-almog Jun 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions .github/workflows/scripts-ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:
- 'scripts/build-ios-app.sh'
- 'scripts/run-ios-ui-tests.sh'
- 'scripts/run-watch-ui-tests.sh'
- 'scripts/run-tv-ui-tests.sh'
- 'scripts/run-ios-native-tests.sh'
- 'scripts/ios/notification-tests/native-tests/**'
- 'scripts/ios/notification-tests/install-native-notification-tests.sh'
Expand All @@ -19,6 +20,7 @@ on:
- 'scripts/ios/screenshots/**'
- 'scripts/ios/screenshots-metal/**'
- 'scripts/ios/screenshots-watch/**'
- 'scripts/ios/screenshots-tv/**'
- 'scripts/templates/**'
- '!scripts/templates/**/*.md'
- 'CodenameOne/src/**'
Expand All @@ -44,6 +46,7 @@ on:
- 'scripts/build-ios-app.sh'
- 'scripts/run-ios-ui-tests.sh'
- 'scripts/run-watch-ui-tests.sh'
- 'scripts/run-tv-ui-tests.sh'
- 'scripts/run-ios-native-tests.sh'
- 'scripts/ios/notification-tests/native-tests/**'
- 'scripts/ios/notification-tests/install-native-notification-tests.sh'
Expand All @@ -53,6 +56,7 @@ on:
- 'scripts/ios/screenshots/**'
- 'scripts/ios/screenshots-metal/**'
- 'scripts/ios/screenshots-watch/**'
- 'scripts/ios/screenshots-tv/**'
- 'scripts/templates/**'
- '!scripts/templates/**/*.md'
- 'CodenameOne/src/**'
Expand Down Expand Up @@ -665,3 +669,149 @@ jobs:
path: artifacts
if-no-files-found: warn
retention-days: 14

build-ios-tv:
# Native Apple TV (tvOS) screenshot pipeline. The tvOS slice auto-enables
# from codename1.tvMain in the sample, so build-ios-app.sh generates the
# <Main>TV target alongside the iOS app. tvOS reuses the iOS UIApplicationMain
# entry and the Metal renderer (no OpenGL ES on tvOS); this job builds the TV
# target for the appletvsimulator, renders the cn1ss suite and streams frames
# to the same Cn1ssScreenshotServer the iOS/watch jobs use, comparing against
# scripts/ios/screenshots-tv.
#
# BLOCKING (a hard golden gate, like build-ios-watch): the tvOS slice
# compiles end-to-end and the golden set is seeded from a CI capture (see
# Ports/iOSPort/nativeSources/TVOS_PORT.md). A mismatch fails the job.
needs: build-port
permissions:
contents: read
pull-requests: write
issues: write
runs-on: macos-15
timeout-minutes: 60
concurrency:
group: mac-ci-${{ github.workflow }}-tv-${{ github.ref_name }}
cancel-in-progress: true

env:
GITHUB_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
GH_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}

steps:
- uses: actions/checkout@v6

- name: Cache CocoaPods and user gems
uses: actions/cache@v5
with:
path: |
~/.gem
~/Library/Caches/CocoaPods
~/.cocoapods/repos
key: ${{ runner.os }}-pods-v1-${{ hashFiles('scripts/setup-workspace.sh') }}
restore-keys: |
${{ runner.os }}-pods-v1-

- name: Ensure CocoaPods tooling
run: |
mkdir -p ~/.codenameone
cp maven/UpdateCodenameOne.jar ~/.codenameone/
set -euo pipefail
if ! command -v ruby >/dev/null; then
echo "ruby not found"; exit 1
fi
GEM_USER_DIR="$(ruby -e 'print Gem.user_dir')"
export PATH="$GEM_USER_DIR/bin:$PATH"
if ! command -v pod >/dev/null 2>&1; then
gem install cocoapods xcodeproj --no-document --user-install
fi
pod --version

- name: Compute setup-workspace hash
id: setup_hash
run: |
set -euo pipefail
echo "hash=$(shasum -a 256 scripts/setup-workspace.sh | awk '{print $1}')" >> "$GITHUB_OUTPUT"

- name: Set TMPDIR
run: echo "TMPDIR=${{ runner.temp }}" >> $GITHUB_ENV

- name: Cache codenameone-tools
uses: actions/cache@v5
with:
path: ${{ runner.temp }}/codenameone-tools
key: ${{ runner.os }}-cn1-tools-${{ steps.setup_hash.outputs.hash }}
restore-keys: |
${{ runner.os }}-cn1-tools-

- name: Cache Maven repository
uses: actions/cache@v5
with:
path: ~/.m2/repository
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-m2-

- name: Restore cn1-binaries cache
uses: actions/cache@v5
with:
path: ../cn1-binaries
key: cn1-binaries-${{ runner.os }}-${{ steps.setup_hash.outputs.hash }}
restore-keys: |
cn1-binaries-${{ runner.os }}-

- name: Restore built CN1 + iOS port artifacts
uses: actions/cache/restore@v4
with:
path: |
~/.m2/repository/com/codenameone
Themes
Ports/iOSPort/nativeSources
key: ${{ needs.build-port.outputs.cn1_built_cache_key }}
fail-on-cache-miss: true

- name: Build sample iOS app (generates the tvOS target)
id: build-ios-app
run: ./scripts/build-ios-app.sh -q -DskipTests
timeout-minutes: 30

- name: Ensure tvOS simulator runtime
run: |
set -euo pipefail
if ! xcrun simctl list runtimes available 2>/dev/null | grep -qi tvOS; then
echo "No tvOS runtime found; attempting download"
xcodebuild -downloadPlatform tvOS || true
fi
xcrun simctl list runtimes available 2>/dev/null | grep -i tvOS || true
xcrun simctl list devices available 2>/dev/null | grep -i "Apple TV" || true

- name: Run tvOS UI screenshot tests
env:
ARTIFACTS_DIR: ${{ github.workspace }}/artifacts/tv-ui-tests
SCREENSHOT_REF_DIR: ${{ github.workspace }}/scripts/ios/screenshots-tv
CN1SS_COMMENT_MARKER: '<!-- CN1SS_IOS_TV_COMMENT -->'
CN1SS_PREVIEW_SUBDIR: ios-tv
CN1SS_REPORT_TITLE: 'Apple TV (tvOS) screenshot updates'
CN1SS_SUCCESS_MESSAGE: '✅ Native Apple TV (tvOS, Metal) screenshot tests passed.'
CN1SS_COMMENT_LOG_PREFIX: '[run-tv-ui-tests]'
CN1SS_FAIL_ON_MISMATCH: '1'
# Tolerate a missing golden set while the tvOS native slice is being
# brought up; tighten to match build-ios-watch once goldens are seeded.
CN1SS_ALLOWED_MISSING: '4'
run: |
set -euo pipefail
mkdir -p "${ARTIFACTS_DIR}"
echo "workspace='${{ steps.build-ios-app.outputs.workspace }}'"
echo "scheme='${{ steps.build-ios-app.outputs.scheme }}'"
./scripts/run-tv-ui-tests.sh \
"${{ steps.build-ios-app.outputs.workspace }}" \
"${{ steps.build-ios-app.outputs.scheme }}"
timeout-minutes: 45

- name: Upload tv artifacts
if: always()
uses: actions/upload-artifact@v7
with:
name: tv-ui-tests
path: artifacts
if-no-files-found: warn
retention-days: 14
11 changes: 11 additions & 0 deletions CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java
Original file line number Diff line number Diff line change
Expand Up @@ -5612,6 +5612,17 @@ public boolean isWatch() {
return false;
}

/// Indicates whether the application is running on a television form factor
/// (Apple TV / Android TV / Google TV). Notice that this is often a guess
/// derived from the device/skin metadata.
///
/// #### Returns
///
/// true if the device is assumed to be a TV
public boolean isTV() {
return false;
}

/// Returns true if the device has dialing capabilities
///
/// #### Returns
Expand Down
11 changes: 11 additions & 0 deletions CodenameOne/src/com/codename1/ui/CN.java
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,17 @@ public static boolean isWatch() {
return Display.impl.isWatch();
}

/// Indicates whether the application is running on a television form factor
/// (Apple TV / Android TV / Google TV). Notice that this is often a guess
/// derived from the device metadata.
///
/// #### Returns
///
/// true if the device is assumed to be a TV
public static boolean isTV() {
return Display.impl.isTV();
}

/// Returns the size of the desktop hosting the application window when running on a desktop platform.
///
/// #### Returns
Expand Down
11 changes: 11 additions & 0 deletions CodenameOne/src/com/codename1/ui/Display.java
Original file line number Diff line number Diff line change
Expand Up @@ -4391,6 +4391,17 @@ public boolean isWatch() {
return impl.isWatch();
}

/// Indicates whether the application is running on a television form factor
/// (Apple TV / Android TV / Google TV). Notice that this is often a guess
/// derived from the device metadata.
///
/// #### Returns
///
/// true if the device is assumed to be a TV
public boolean isTV() {
return impl.isTV();
}

/// Returns true if the device has dialing capabilities
///
/// #### Returns
Expand Down
3 changes: 2 additions & 1 deletion CodenameOne/src/com/codename1/ui/util/Resources.java
Original file line number Diff line number Diff line change
Expand Up @@ -1523,7 +1523,8 @@ Hashtable loadTheme(String id, boolean newerVersion) throws IOException {
if ("HTML5".equals(platformName)) {
platformName = Display.getInstance().getProperty("HTML5.platformName", "mac");
}
String deviceType = Display.getInstance().isDesktop() ? "desktop" : Display.getInstance().isTablet() ? "tablet" : "phone";
Display d = Display.getInstance();
String deviceType = d.isDesktop() ? "desktop" : d.isTablet() ? "tablet" : d.isTV() ? "tv" : d.isWatch() ? "watch" : "phone";
String platformPrefix = "platform-" + platformName + "-";
String densityPrefix = "density-" + densityStr + "-";
String devicePrefix = "device-" + deviceType + "-";
Expand Down
5 changes: 5 additions & 0 deletions CodenameOneDesigner/build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -165,5 +165,10 @@
<path path="${run.test.classpath}"/>
</classpath>
</java>
<java classname="com.codename1.designer.css.CSSDeviceFormFactorMediaQueryTest" fork="true" failonerror="true">
<classpath>
<path path="${run.test.classpath}"/>
</classpath>
</java>
</target>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package com.codename1.designer.css;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Hashtable;

/**
* Regression tests for device form-factor media queries compiling into
* {@code device-<type>-} prefixed UIIDs. The {@code tv} and {@code watch}
* form factors reuse the same generic {@code device-*} mechanism that already
* backs {@code device-desktop}/{@code device-tablet}/{@code device-phone};
* at runtime Resources.loadTheme selects the matching prefix via
* Display.isTV()/isWatch().
*/
public class CSSDeviceFormFactorMediaQueryTest {

public static void main(String[] args) throws Exception {
installHeadlessImplementation();
testTvMediaCompilesToDeviceTvUiids();
testWatchMediaCompilesToDeviceWatchUiids();
}

/**
* CSSTheme.load touches Display / Util, which need a CodenameOneImplementation.
* Install the same minimal headless stub the no-cef CLI uses (see
* NoCefCSSCLI#installHeadlessImplementation) so this test runs standalone.
*/
private static void installHeadlessImplementation() throws Exception {
HeadlessCssCompilerImplementation stub = new HeadlessCssCompilerImplementation();
Class<?> displayCls = Class.forName("com.codename1.ui.Display");
java.lang.reflect.Field implField = displayCls.getDeclaredField("impl");
implField.setAccessible(true);
if (implField.get(null) == null) {
implField.set(null, stub);
}
com.codename1.io.Util.setImplementation(stub);
}

private static void testTvMediaCompilesToDeviceTvUiids() throws Exception {
Path cssFile = Files.createTempFile("cn1-tv-media", ".css");
Path resFile = Files.createTempFile("cn1-tv-media", ".res");
try {
String css = "Button { color: #111111; }"
+ "@media device-tv {"
+ " Button { color: #00ff00; background-color: #001100; }"
+ "}";
Files.write(cssFile, css.getBytes(StandardCharsets.UTF_8));

CSSTheme theme = CSSTheme.load(cssFile.toUri().toURL());
theme.resourceFile = resFile.toFile();
theme.res = new com.codename1.ui.util.EditableResourcesForCSS(resFile.toFile());
theme.res.setTheme("Theme", new Hashtable());
theme.updateResources();

Hashtable themeProps = theme.res.getTheme("Theme");
assertEquals("111111", themeProps.get("Button.fgColor"), "Base Button fgColor");
assertEquals("00FF00", themeProps.get("device-tv-Button.fgColor"), "TV Button fgColor");
assertEquals("001100", themeProps.get("device-tv-Button.bgColor"), "TV Button bgColor");
} finally {
deleteIfExists(cssFile);
deleteIfExists(resFile);
}
}

private static void testWatchMediaCompilesToDeviceWatchUiids() throws Exception {
Path cssFile = Files.createTempFile("cn1-watch-media", ".css");
Path resFile = Files.createTempFile("cn1-watch-media", ".res");
try {
String css = "Button { color: #111111; }"
+ "@media device-watch {"
+ " Button { color: #2222ff; }"
+ "}";
Files.write(cssFile, css.getBytes(StandardCharsets.UTF_8));

CSSTheme theme = CSSTheme.load(cssFile.toUri().toURL());
theme.resourceFile = resFile.toFile();
theme.res = new com.codename1.ui.util.EditableResourcesForCSS(resFile.toFile());
theme.res.setTheme("Theme", new Hashtable());
theme.updateResources();

Hashtable themeProps = theme.res.getTheme("Theme");
assertEquals("111111", themeProps.get("Button.fgColor"), "Base Button fgColor");
assertEquals("2222FF", themeProps.get("device-watch-Button.fgColor"), "Watch Button fgColor");
} finally {
deleteIfExists(cssFile);
deleteIfExists(resFile);
}
}

private static void deleteIfExists(Path path) {
try {
Files.deleteIfExists(path);
} catch (IOException ignored) {
}
}

private static void assertEquals(Object expected, Object actual, String message) {
if (expected == null ? actual != null : !expected.equals(actual)) {
throw new AssertionError(message + " expected=" + expected + " actual=" + actual);
}
}
}
Loading
Loading