Skip to content

Commit b2e3ebf

Browse files
shai-almogclaude
andauthored
Windows: experimental Xbox Developer Mode (UWP) target (#6140)
* Windows: experimental Xbox Developer Mode (UWP) target windows.xbox=true builds the native Windows port as a UWP app for an Xbox in Developer Mode: a CoreWindow host rendering through a D3D11/D2D swap chain, compiled against the app partition and linked to WindowsApp.lib alone, with desktop-only natives stubbed by scripts/gen-windows-uwp-stubs.py. Gamepad keys map to game actions and back; the console's visible bounds are the safe area. The builder packs a signed MSIX, generating a dev certificate when none is given. CI cross-builds it and installs + launches it on a Windows runner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Xbox target: red-team fixes Safe area and title are thread-bound to the view: insets are computed on the view thread and cached, the title goes through CoreDispatcher.RunAsync. Stubs whose 0 means success (desktopWindowCreate, printDocument) answer -1. A supplied certificate needs windows.msix.publisher, or the package would never install. CI signs the package with the builder's own development certificate. Docs mark the target experimental and add a gaming consoles appendix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Xbox target: guide snippet from docs/demos, full header on crash protection Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Xbox target: guide prose passes LanguageTool Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Signed-off-by: Shai Almog <67850168+shai-almog@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 1901fd5 commit b2e3ebf

41 files changed

Lines changed: 2369 additions & 41 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/windows-cross-compile.yml‎

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ on:
1515
- 'vm/**'
1616
- 'Ports/WindowsPort/**'
1717
- 'maven/windows/**'
18+
- 'scripts/gen-windows-uwp-stubs.py'
19+
- 'maven/build-engine/src/main/java/com/codename1/builders/WindowsNativeBuilder.java'
20+
- 'maven/build-engine/src/test/java/com/codename1/builders/WindowsNativeBuilderArchTest.java'
21+
- 'maven/build-engine/src/test/resources/com/codename1/builders/xbox-smoke-AppxManifest.xml'
1822
- '!vm/**/README.md'
1923
- '!vm/**/readme.md'
2024
- '!vm/**/docs/**'
@@ -25,6 +29,10 @@ on:
2529
- 'vm/**'
2630
- 'Ports/WindowsPort/**'
2731
- 'maven/windows/**'
32+
- 'scripts/gen-windows-uwp-stubs.py'
33+
- 'maven/build-engine/src/main/java/com/codename1/builders/WindowsNativeBuilder.java'
34+
- 'maven/build-engine/src/test/java/com/codename1/builders/WindowsNativeBuilderArchTest.java'
35+
- 'maven/build-engine/src/test/resources/com/codename1/builders/xbox-smoke-AppxManifest.xml'
2836

2937
concurrency:
3038
# Only the latest revision needs validation. PR numbers keep forks isolated;
@@ -155,13 +163,156 @@ jobs:
155163
test -f core/target/classes/com/codename1/ui/Form.class
156164
test -f windows/target/classes/com/codename1/impl/windows/WindowsImplementation.class
157165
166+
# A UWP (Xbox Developer Mode) build compiles the desktop-only natives
167+
# out and links generated stubs in their place; a native added to one of
168+
# those files without regenerating would fail only the UWP link below.
169+
- name: UWP stubs are current
170+
run: python3 scripts/gen-windows-uwp-stubs.py --check
171+
172+
# Builds the desktop exe and the UWP one (app partition, WindowsApp.lib
173+
# only, /APPCONTAINER): the UWP link is the gate that every Win32 call the
174+
# port makes in that build exists inside the app container.
158175
- name: Cross-compile the native Windows .exe (clang-cl + xwin)
159176
env:
160177
CN1_XWIN_SYSROOT: ${{ runner.temp }}/xwin-sdk
161178
CN1_CLANG_CL: clang-cl
162179
CN1_LLVM_RC: llvm-rc
180+
CN1_UWP_EXE_OUT: ${{ github.workspace }}/uwp-smoke/app.exe
163181
run: |
164182
cd vm
165183
JAVA_HOME="$JDK_8_HOME" mvn -B -pl tests -am test \
166184
'-Dtest=CleanTargetIntegrationTest#crossCompilesWindowsExeWithXwin' \
167185
'-Dsurefire.failIfNoSpecifiedTests=false'
186+
test -f "$CN1_UWP_EXE_OUT"
187+
188+
# The development certificate windows.xbox builds create, made by the
189+
# builder's own openssl commands; the Windows job signs with it, so a
190+
# certificate Windows refuses fails there.
191+
- name: Create the Xbox development certificate (WindowsNativeBuilder)
192+
env:
193+
CN1_XBOX_DEVCERT_OUT: ${{ github.workspace }}/uwp-smoke/devcert
194+
RETRY_ONLY_MATCHING: transient
195+
RETRY_ATTEMPTS: '3'
196+
RETRY_DELAY_SECONDS: '60'
197+
run: |
198+
set -euo pipefail
199+
mkdir -p ~/.codenameone
200+
cp maven/UpdateCodenameOne.jar ~/.codenameone/
201+
cd maven
202+
JAVA_HOME="$JDK_8_HOME" bash ../scripts/ci/retry.sh mvn -B -pl build-engine -am install \
203+
-DskipTests -Dmaven.javadoc.skip=true -Plocal-dev-javase
204+
JAVA_HOME="$JDK_8_HOME" mvn -B -pl build-engine test -Plocal-dev-javase \
205+
'-Dtest=WindowsNativeBuilderArchTest' -Dsurefire.failIfNoSpecifiedTests=false
206+
test -s "$CN1_XBOX_DEVCERT_OUT/dev.pfx"
207+
test -s "$CN1_XBOX_DEVCERT_OUT/dev.cer"
208+
209+
# The manifest is pinned to WindowsNativeBuilder.buildXboxAppxManifest by
210+
# WindowsNativeBuilderArchTest, so the run below installs what windows.xbox
211+
# builds ship.
212+
- name: Upload the UWP smoke app
213+
uses: actions/upload-artifact@v4
214+
with:
215+
name: uwp-smoke
216+
path: |
217+
uwp-smoke/app.exe
218+
uwp-smoke/devcert/dev.pfx
219+
uwp-smoke/devcert/dev.cer
220+
maven/build-engine/src/test/resources/com/codename1/builders/xbox-smoke-AppxManifest.xml
221+
if-no-files-found: error
222+
retention-days: 3
223+
224+
# Phase 1's proof without a console: the cross-built UWP app is packed, signed,
225+
# installed and launched as a real UWP activation (app container, CoreWindow,
226+
# swap chain) on a Windows runner. The app writes a marker into its package
227+
# LocalState once its first form has painted; no marker fails the job.
228+
uwp-run:
229+
name: install + launch the UWP package (Windows x64)
230+
needs: cross-compile
231+
runs-on: windows-latest
232+
timeout-minutes: 20
233+
steps:
234+
- name: Download the UWP smoke app
235+
uses: actions/download-artifact@v4
236+
with:
237+
name: uwp-smoke
238+
path: uwp-smoke
239+
240+
- name: Pack, sign and install
241+
shell: pwsh
242+
run: |
243+
$ErrorActionPreference = 'Stop'
244+
$root = Resolve-Path uwp-smoke
245+
$layout = Join-Path $env:RUNNER_TEMP 'uwp-layout'
246+
New-Item -ItemType Directory -Force (Join-Path $layout 'Assets') | Out-Null
247+
Copy-Item (Join-Path $root 'uwp-smoke/app.exe') (Join-Path $layout 'app.exe')
248+
$manifest = Get-ChildItem -Recurse $root -Filter 'xbox-smoke-AppxManifest.xml' | Select-Object -First 1
249+
Copy-Item $manifest.FullName (Join-Path $layout 'AppxManifest.xml')
250+
Add-Type -AssemblyName System.Drawing
251+
foreach ($logo in 'StoreLogo','Square150x150Logo','Square44x44Logo','SplashScreen') {
252+
$bmp = New-Object System.Drawing.Bitmap 150,150
253+
$g = [System.Drawing.Graphics]::FromImage($bmp); $g.Clear([System.Drawing.Color]::SteelBlue); $g.Dispose()
254+
$bmp.Save((Join-Path $layout "Assets/$logo.png"), [System.Drawing.Imaging.ImageFormat]::Png); $bmp.Dispose()
255+
}
256+
$kit = Get-ChildItem 'C:/Program Files (x86)/Windows Kits/10/bin/*/x64/makeappx.exe' |
257+
Sort-Object FullName -Descending | Select-Object -First 1
258+
$bin = $kit.DirectoryName
259+
$msix = Join-Path $env:RUNNER_TEMP 'UwpSmoke.msix'
260+
& "$bin/makeappx.exe" pack /o /d $layout /p $msix
261+
if ($LASTEXITCODE -ne 0) { throw "makeappx failed" }
262+
# Signed with the certificate WindowsNativeBuilder created (subject
263+
# CN=UwpSmoke, the manifest Publisher; empty PKCS#12 password).
264+
$pfx = Join-Path $root 'uwp-smoke/devcert/dev.pfx'
265+
$cer = Join-Path $root 'uwp-smoke/devcert/dev.cer'
266+
& "$bin/signtool.exe" sign /fd SHA256 /f $pfx $msix
267+
if ($LASTEXITCODE -ne 0) { throw "signtool rejected the builder's development certificate" }
268+
Import-Certificate -FilePath $cer -CertStoreLocation 'Cert:/LocalMachine/TrustedPeople' | Out-Null
269+
$unlock = 'HKLM:/SOFTWARE/Microsoft/Windows/CurrentVersion/AppModelUnlock'
270+
New-Item -Force $unlock | Out-Null
271+
Set-ItemProperty $unlock AllowAllTrustedApps 1 -Type DWord
272+
Set-ItemProperty $unlock AllowDevelopmentWithoutDevLicense 1 -Type DWord
273+
Add-AppxPackage -Path $msix
274+
Get-AppxPackage com.codename1.uwpsmoke | Format-List Name,PackageFamilyName,InstallLocation,SignatureKind
275+
276+
- name: Launch and wait for the first frame
277+
shell: pwsh
278+
run: |
279+
$ErrorActionPreference = 'Stop'
280+
$pkg = Get-AppxPackage com.codename1.uwpsmoke
281+
$pfn = $pkg.PackageFamilyName
282+
$state = Join-Path $env:LOCALAPPDATA "Packages/$pfn/LocalState"
283+
Start-Process "shell:AppsFolder\$pfn!App"
284+
$marker = $null
285+
for ($i = 0; $i -lt 90 -and -not $marker; $i++) {
286+
Start-Sleep -Seconds 1
287+
if (Test-Path $state) {
288+
$marker = Get-ChildItem -Recurse $state -Filter 'uwp-smoke.txt' -ErrorAction SilentlyContinue | Select-Object -First 1
289+
}
290+
}
291+
Add-Type -AssemblyName System.Drawing, System.Windows.Forms
292+
$b = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
293+
$bmp = New-Object System.Drawing.Bitmap $b.Width, $b.Height
294+
$g = [System.Drawing.Graphics]::FromImage($bmp); $g.CopyFromScreen($b.Location, [System.Drawing.Point]::Empty, $b.Size); $g.Dispose()
295+
New-Item -ItemType Directory -Force uwp-run | Out-Null
296+
$bmp.Save('uwp-run/desktop.png', [System.Drawing.Imaging.ImageFormat]::Png)
297+
Get-Process app -ErrorAction SilentlyContinue | Format-Table Id,ProcessName,StartTime
298+
if (-not $marker) {
299+
Get-WinEvent -LogName 'Microsoft-Windows-AppModel-Runtime/Admin' -MaxEvents 20 -ErrorAction SilentlyContinue |
300+
Format-List TimeCreated,Id,Message
301+
Get-WinEvent -FilterHashtable @{LogName='Application'; Id=1000,1001} -MaxEvents 5 -ErrorAction SilentlyContinue |
302+
Format-List TimeCreated,Message
303+
if (Test-Path $state) { Get-ChildItem -Recurse $state | Format-Table FullName,Length }
304+
throw "the UWP app wrote no marker in 90s: it did not reach its first frame"
305+
}
306+
$text = Get-Content $marker.FullName -Raw
307+
Write-Host "marker: $text"
308+
if ($text -notmatch '^CN1-UWP-STARTED [1-9][0-9]*x[1-9][0-9]*') { throw "unexpected marker: $text" }
309+
Get-Process app -ErrorAction SilentlyContinue | Stop-Process -Force
310+
311+
- name: Upload the desktop capture
312+
if: always()
313+
uses: actions/upload-artifact@v4
314+
with:
315+
name: uwp-run-capture
316+
path: uwp-run/
317+
if-no-files-found: ignore
318+
retention-days: 7

‎Ports/WindowsPort/nativeSources/cn1_windows.h‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,60 @@
5959
#include <wincodec.h>
6060
#include <stdint.h>
6161

62+
#if defined(CN1_WINDOWS_UWP)
63+
/*
64+
* UWP (Xbox Developer Mode) build: the app container's API partition drops a
65+
* few Win32 calls that have a direct app-partition equivalent. They are mapped
66+
* here once, so the shared file code reads the same in both builds.
67+
* CreateFileW -> CreateFile2 (same handle, same flags, split differently);
68+
* CharUpperW -> an invariant-locale LCMapStringEx in place. A template file is
69+
* never used by this port, so CreateFile2 ignores that argument.
70+
*/
71+
static __inline HANDLE cn1UwpCreateFileW(LPCWSTR name, DWORD access, DWORD share,
72+
LPSECURITY_ATTRIBUTES sa, DWORD disposition, DWORD flagsAndAttributes, HANDLE templateFile) {
73+
CREATEFILE2_EXTENDED_PARAMETERS ext;
74+
(void) templateFile;
75+
ZeroMemory(&ext, sizeof(ext));
76+
ext.dwSize = sizeof(ext);
77+
ext.dwFileAttributes = flagsAndAttributes & 0x0000FFFF;
78+
ext.dwFileFlags = flagsAndAttributes & 0xFFF00000;
79+
ext.dwSecurityQosFlags = flagsAndAttributes & 0x000F0000;
80+
ext.lpSecurityAttributes = sa;
81+
return CreateFile2(name, access, share, disposition, &ext);
82+
}
83+
#undef CreateFileW
84+
#define CreateFileW cn1UwpCreateFileW
85+
86+
static __inline HANDLE cn1UwpCreateFileA(LPCSTR name, DWORD access, DWORD share,
87+
LPSECURITY_ATTRIBUTES sa, DWORD disposition, DWORD flagsAndAttributes, HANDLE templateFile) {
88+
WCHAR wide[MAX_PATH];
89+
if (MultiByteToWideChar(CP_UTF8, 0, name, -1, wide, MAX_PATH) <= 0) {
90+
SetLastError(ERROR_FILENAME_EXCED_RANGE);
91+
return INVALID_HANDLE_VALUE;
92+
}
93+
return cn1UwpCreateFileW(wide, access, share, sa, disposition, flagsAndAttributes, templateFile);
94+
}
95+
#undef CreateFileA
96+
#define CreateFileA cn1UwpCreateFileA
97+
98+
static __inline LPWSTR cn1UwpCharUpperW(LPWSTR s) {
99+
int len = (int) wcslen(s);
100+
LCMapStringEx(LOCALE_NAME_INVARIANT, LCMAP_UPPERCASE, s, len, s, len, NULL, NULL, 0);
101+
return s;
102+
}
103+
#undef CharUpperW
104+
#define CharUpperW cn1UwpCharUpperW
105+
106+
/* Declared by the SDK for the desktop partition only, but exported by kernel32
107+
* on every Windows, the Xbox included. It is how a null dereference becomes a
108+
* NullPointerException (cn1WinFaultToException), so the UWP build keeps it.
109+
* Developer Mode sideloading does not run the Store's API certification. */
110+
#ifdef __cplusplus
111+
extern "C"
112+
#endif
113+
WINBASEAPI PVOID WINAPI AddVectoredExceptionHandler(ULONG First, PVECTORED_EXCEPTION_HANDLER Handler);
114+
#endif /* CN1_WINDOWS_UWP */
115+
62116
#ifdef __cplusplus
63117
/* C++ method-call macros for the COBJMACROS-style call sites. */
64118
#include "cn1_windows_comc.h"
@@ -71,6 +125,16 @@ extern "C" {
71125
/* The ParparVM runtime is C; keep its declarations C-linked under C++. */
72126
#include "cn1_globals.h"
73127

128+
#if defined(CN1_WINDOWS_UWP)
129+
/* The app's local data folder (ApplicationData.Current.LocalFolder) into buf,
130+
* NUL terminated. Returns 0 when it cannot be read. cn1_windows_corewindow.cpp. */
131+
int cn1UwpLocalFolder(WCHAR* buf, int capacity);
132+
#endif
133+
134+
/* COM, the fault handlers and the input ring, shared by every host's
135+
* initDisplay. 0 when already initialised. cn1_windows_window.cpp. */
136+
int cn1WinInitRuntime(void);
137+
74138
/* ------------------------------------------------------------------ events */
75139

76140
/*

‎Ports/WindowsPort/nativeSources/cn1_windows_accessibility.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
*/
2323

2424
/* Native virtual accessibility tree for the lightweight Win32 port. */
25-
#ifdef _WIN32
25+
#if defined(_WIN32) && !defined(CN1_WINDOWS_UWP) /* desktop only: stubbed for UWP in cn1_windows_uwp_stubs.c */
2626

2727
/* xwin may provide a newer MSVC STL than the clang-cl installed by the Linux
2828
* cross-compile runner. This file uses only the long-stable string/vector

‎Ports/WindowsPort/nativeSources/cn1_windows_audiorec.c‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
* and closes.
3636
*/
3737

38-
#ifdef _WIN32
38+
#if defined(_WIN32) && !defined(CN1_WINDOWS_UWP) /* desktop only: stubbed for UWP in cn1_windows_uwp_stubs.c */
3939

4040
#include "cn1_windows.h"
4141
#include <mmsystem.h>

‎Ports/WindowsPort/nativeSources/cn1_windows_camera.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
* (negative stride); both are handled when packing into CN1's 0xAARRGGBB ints.
3737
*/
3838

39-
#ifdef _WIN32
39+
#if defined(_WIN32) && !defined(CN1_WINDOWS_UWP) /* desktop only: stubbed for UWP in cn1_windows_uwp_stubs.c */
4040

4141
#define WIN32_LEAN_AND_MEAN
4242
#include <windows.h>

‎Ports/WindowsPort/nativeSources/cn1_windows_chrome.cpp‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
/* Integrated main-window title bar. The window keeps WS_OVERLAPPEDWINDOW:
2626
* DWM owns the real caption buttons and DefWindowProc owns system commands,
2727
* resize tracking, caption double-click and the window system menu. */
28-
#ifdef _WIN32
28+
#if defined(_WIN32) && !defined(CN1_WINDOWS_UWP) /* desktop only: stubbed for UWP in cn1_windows_uwp_stubs.c */
2929
#include "cn1_windows.h"
3030
#include <dwmapi.h>
3131
#include <windowsx.h>

0 commit comments

Comments
 (0)