Add live Mac controller tester and refresh bilingual app screenshots

This commit is contained in:
Erick Sanchez committed 2026-10-26 13:46:33 -06:00
1 parent df11bd3e44
commit 8be22c449c
20 files changed
+284 -32

No files matched your search

+8 -4
View File
@@ -28,11 +28,11 @@ Cuando abra la app:
**Mac:** crea una sola `.app` completa, con todos los datos del juego dentro; pulsa **Abrir juego**. No necesitas copiar una carpeta RomFS aparte. No necesitas Sideloadly ni un iPhone. Las partidas quedan en `~/Library/Application Support/nxnative/<title ID>/`.
**iPhone / iPad:** crea una IPA ligera sin firmar. Instálala con Sideloadly y copia los datos del juego como te indica la app.
**iPhone / iPad:** crea una IPA ligera sin firmar. Instálala con Sideloadly y copia los datos del juego como te indica la app. [Ver pantalla de preparación para iPhone/iPad](docs/images/nxnative-ios-es.png).
**Necesitas:** Mac con Apple silicon (M1 o posterior), macOS 26+ y Xcode 26+. El comando instala las otras herramientas. Los juegos y las llaves los aportas tú. La primera compilación puede tardar.
![Nuestra app para preparar juegos](docs/images/nxnative-easy-es.png)
![Nuestra app para preparar juegos](docs/images/nxnative-mac-es-v1.3.png)
## Qué aporta esta versión
@@ -74,7 +74,7 @@ Para actualizar, usa la misma cuenta y el mismo bundle ID efectivo de Sideloadly
El carrusel muestra los perfiles de esta copia; no proporciona ni descarga juegos. Debes añadir tus propios archivos del juego. Pulsa **Ver todos** arriba a la derecha para consultar la lista completa de soporte. Busca por nombre, versión o Title ID; cada resultado enlaza a su ficha. El catálogo vuelve a leer los perfiles locales cada vez que lo abres. El carrusel rota automáticamente y se pausa al colocar el cursor encima.
![Buscador de perfiles de juegos](docs/images/nxnative-catalog-es.png)
![Buscador de perfiles de juegos](docs/images/nxnative-catalog-es-v1.3.png)
**Las pruebas propias cubren compilación y empaquetado en Mac, no una verificación automática de juego en iPhone.** Los perfiles y reportes de juego proceden de upstream; revisa [la ficha de tu juego](dm/games/README.md#games) antes de prepararlo. Un perfil no garantiza que cualquier versión funcione en cualquier iPhone.
@@ -206,7 +206,11 @@ Los juegos preparados usan GameController de Apple en **Mac, iPhone y iPad**:
- **PS5:** DualSense y DualSense Edge.
- **PS4:** DualShock 4.
La app muestra automáticamente el nombre del control conectado y **Listo en este Mac** cuando Apple reconoce un perfil compatible. Se actualiza al conectar o desconectar el control. Si preparas para iPhone/iPad, te recuerda conectarlo **a ese dispositivo**: estar conectado al Mac no lo conecta al teléfono.
La app muestra automáticamente el nombre del control conectado y **Listo en este Mac** cuando Apple reconoce un perfil compatible. Se actualiza al conectar o desconectar el control. El panel de controles del Mac no aparece al elegir iPhone/iPad. Conecta el control directamente al dispositivo donde vas a jugar.
En modo **Mac**, pulsa **Probar control**: se iluminan botones y cruceta, se mueven ambos sticks y las barras muestran la presión de los gatillos. **¡Está respondiendo!** aparece después de recibir una pulsación o movimiento. Si hay varios controles, puedes elegir cuál probar. La prueba sólo lee entradas mientras está abierta y no reemplaza los manejadores de entrada de los juegos.
![Prueba visual del control en Mac](docs/images/nxnative-controller-test-es.png)
Conecta el control desde **Bluetooth del dispositivo** y abre tu juego. No necesitas instalar drivers ni activar opciones en la herramienta. Se mapean sticks, cruceta, botones, gatillos, botones superiores, clics de sticks y menús. En iPhone/iPad los controles táctiles se ocultan al conectar uno físico.
+7 -3
View File
@@ -28,11 +28,11 @@ When the app opens:
**Mac:** creates one complete game `.app`, with its game data included inside; click **Open game**. No separate RomFS folder to copy. No Sideloadly or iPhone needed. Saves stay in `~/Library/Application Support/nxnative/<title ID>/`.
**iPhone / iPad:** creates an unsigned lightweight IPA; install it with Sideloadly, then copy the game data as shown in the app.
**iPhone / iPad:** creates an unsigned lightweight IPA; install it with Sideloadly, then copy the game data as shown in the app. [See the iPhone/iPad preparation screen](docs/images/nxnative-ios.png).
**Required:** an Apple silicon Mac (M1 or newer), macOS 26+ and Xcode 26+. The command installs the other tools. You must provide your own games and keys. Compilation can take time, especially for the first game.
![The nxnative Easy desktop app](docs/images/nxnative-easy.png)
![The nxnative Easy desktop app](docs/images/nxnative-mac-v1.3.png)
## What this version adds
@@ -204,7 +204,11 @@ The game apps use Apple's GameController framework on **Mac, iPhone and iPad**:
- **PS5:** DualSense and DualSense Edge.
- **PS4:** DualShock 4.
The preparation app automatically shows connected controller names and **Ready on this Mac** when Apple exposes a compatible gamepad profile. It updates when you connect or disconnect a controller. For an iPhone/iPad build, it reminds you to pair the controller **on that device**; a controller connected to the Mac is not a remote connection to the phone.
The preparation app automatically shows connected controller names and **Ready on this Mac** when Apple exposes a compatible gamepad profile. It updates when you connect or disconnect a controller. The Mac controller panel is hidden when you choose iPhone/iPad. Pair a controller directly with the device where you will play.
In **Mac** mode, click **Test controller** to see live input: buttons and D-pad light up, both sticks move, and trigger bars show pressure. **Input received!** appears only after input is detected. If several controllers are connected, choose which one to test. The tester reads input only while its window is visible and does not take input handlers away from games.
![Live controller test on Mac](docs/images/nxnative-controller-test.png)
Pair your controller in the device's **Bluetooth settings**, then open the prepared game. No extra driver or launcher setting is needed. Sticks, D-pad, face buttons, shoulders, triggers, stick clicks and menu buttons are mapped automatically. On iPhone/iPad, touch controls hide while a physical controller is connected.
+3 -2
View File
@@ -2,7 +2,7 @@ import AppKit
import Combine
import GameController
struct DetectedController: Identifiable {
struct DetectedController: Identifiable, Equatable {
let id: ObjectIdentifier
let name: String
let supported: Bool
@@ -28,9 +28,10 @@ struct DetectedController: Identifiable {
}
deinit {for observer in observers {center.removeObserver(observer)}}
func refresh() {
controllers=query().map {
let next=query().map {
DetectedController(id:ObjectIdentifier($0),name:$0.vendorName ?? "",supported:$0.extendedGamepad != nil)
}
if next != controllers {controllers=next}
}
}
+161
View File
@@ -0,0 +1,161 @@
import SwiftUI
import Combine
import GameController
struct ControllerInputState: Equatable {
var buttons: Set<String> = []
var lx: Float = 0, ly: Float = 0, rx: Float = 0, ry: Float = 0
var lt: Float = 0, rt: Float = 0
var playStation = false
var playStation5 = false
var active: Bool {
!buttons.isEmpty || [lx,ly,rx,ry].contains {abs($0)>0.15} || lt>0.1 || rt>0.1
}
init() {}
init(_ g:GCExtendedGamepad) {
let inputs: [(String,GCControllerButtonInput?)] = [
("a",g.buttonA),("b",g.buttonB),("x",g.buttonX),("y",g.buttonY),
("lb",g.leftShoulder),("rb",g.rightShoulder),("ls",g.leftThumbstickButton),("rs",g.rightThumbstickButton),
("menu",g.buttonMenu),("options",g.buttonOptions),
("up",g.dpad.up),("down",g.dpad.down),("left",g.dpad.left),("right",g.dpad.right)]
buttons=Set(inputs.compactMap {$0.1?.isPressed == true ? $0.0 : nil})
lx=g.leftThumbstick.xAxis.value;ly=g.leftThumbstick.yAxis.value
rx=g.rightThumbstick.xAxis.value;ry=g.rightThumbstick.yAxis.value
lt=g.leftTrigger.value;rt=g.rightTrigger.value
playStation5=g is GCDualSenseGamepad
playStation=playStation5 || g is GCDualShockGamepad
}
}
// Sample only while the tester is visible. No handlers, pairing or background capture.
@MainActor final class ControllerTester: ObservableObject {
@Published private(set) var devices: [DetectedController] = []
@Published var selected: ObjectIdentifier? {
didSet {if selected != oldValue {clear();confirmed=false}}
}
@Published private(set) var state=ControllerInputState()
@Published private(set) var confirmed=false
private let query: () -> [GCController]
init(query:(() -> [GCController])? = nil) {self.query=query ?? {GCController.controllers()};sample()}
func clear() {state=ControllerInputState()}
func sample() {
let connected=query().filter {$0.extendedGamepad != nil}
let next=connected.map {DetectedController(id:ObjectIdentifier($0),name:$0.vendorName ?? "",supported:true)}
if next.map(\.id) != devices.map(\.id) {devices=next}
if !next.contains(where:{$0.id == selected}) {selected=next.first?.id}
guard let controller=connected.first(where:{ObjectIdentifier($0)==selected}),let pad=controller.extendedGamepad else {
clear();confirmed=false;return
}
let current=ControllerInputState(pad)
if current != state {state=current}
if current.active && !confirmed {confirmed=true}
}
}
struct ControllerTestView: View {
let text: (String) -> String
@Environment(\.dismiss) private var dismiss
@StateObject private var tester=ControllerTester()
private let lime=Color(red:0.79,green:0.95,blue:0.37)
private let tick=Timer.publish(every:1.0/60.0,on:.main,in:.common).autoconnect()
private var state: ControllerInputState {tester.state}
private func button(_ key:String,_ label:String,_ size:CGFloat=36) -> some View {
let pressed=state.buttons.contains(key)
return Text(label).font(.system(size:13,weight:.bold,design:.rounded)).lineLimit(1).minimumScaleFactor(0.7)
.frame(width:size,height:size).foregroundStyle(pressed ? Color.black : Color.white.opacity(0.75))
.background(pressed ? lime : Color.white.opacity(0.08),in:RoundedRectangle(cornerRadius:size/3))
.overlay(RoundedRectangle(cornerRadius:size/3).stroke(pressed ? lime : Color.white.opacity(0.12)))
.accessibilityLabel(label).accessibilityValue(pressed ? text("Presionado") : text("Suelto"))
}
private func stick(_ key:String,_ label:String,_ x:Float,_ y:Float) -> some View {
VStack(spacing:8) {
ZStack {
Circle().fill(Color.black.opacity(0.24)).frame(width:92,height:92)
Path {p in p.move(to:CGPoint(x:46,y:4));p.addLine(to:CGPoint(x:46,y:88));p.move(to:CGPoint(x:4,y:46));p.addLine(to:CGPoint(x:88,y:46))}
.stroke(Color.white.opacity(0.08),lineWidth:1).frame(width:92,height:92)
Circle().fill(state.buttons.contains(key) ? lime : Color.white.opacity(0.72)).frame(width:38,height:38)
.offset(x:CGFloat(x)*26,y:-CGFloat(y)*26)
}
Text(label).font(.system(size:11,weight:.semibold)).foregroundStyle(.secondary)
}.accessibilityElement(children:.ignore).accessibilityLabel(label)
.accessibilityValue(String(format:"X %.2f, Y %.2f",x,y))
}
private func trigger(_ label:String,_ value:Float) -> some View {
VStack(alignment:.leading,spacing:7) {
HStack {Text(label);Spacer();Text("\(Int(value*100))%").monospacedDigit()}
.font(.system(size:12,weight:.semibold)).foregroundStyle(value>0.1 ? lime : .secondary)
ProgressView(value:Double(value)).tint(lime)
}.frame(width:170)
}
var body: some View {
VStack(alignment:.leading,spacing:20) {
HStack {
VStack(alignment:.leading,spacing:5) {
Text(text("Prueba tu control")).font(.system(size:25,weight:.bold,design:.rounded))
Text(text("Pulsa botones y mueve los sticks. Verás cada movimiento aquí."))
.font(.system(size:13)).foregroundStyle(.secondary)
}
Spacer()
Button(text("Cerrar")){dismiss()}.keyboardShortcut(.cancelAction)
}
if tester.devices.isEmpty {
Label(text("Conecta un control a este Mac por Bluetooth."),systemImage:"gamecontroller")
.font(.system(size:13)).foregroundStyle(.secondary)
} else {
HStack {
if tester.devices.count>1 {
Picker(text("Control"),selection:$tester.selected) {
ForEach(tester.devices) {device in
Text(device.name.isEmpty ? text("Control") : device.name).tag(Optional(device.id))
}
}.frame(maxWidth:310)
} else {
Text(tester.devices.first?.name.isEmpty == false ? tester.devices.first!.name : text("Control"))
.font(.system(size:14,weight:.semibold))
}
Spacer()
Label(text(tester.confirmed ? "¡Está respondiendo!" : "Esperando tu primera pulsación…"),
systemImage:tester.confirmed ? "checkmark.circle.fill" : "hand.tap")
.font(.system(size:12,weight:.medium)).foregroundStyle(tester.confirmed ? lime : .secondary)
}
}
VStack(spacing:22) {
HStack {
trigger(state.playStation ? "L2" : "LT",state.lt)
Spacer()
trigger(state.playStation ? "R2" : "RT",state.rt)
}
HStack {
button("lb",state.playStation ? "L1" : "LB",42)
Spacer()
button("options",state.playStation5 ? "Create" : state.playStation ? "Share" : "View",42)
button("menu",state.playStation ? "Options" : "Menu",42)
Spacer()
button("rb",state.playStation ? "R1" : "RB",42)
}
HStack(alignment:.center) {
stick("ls","L3",state.lx,state.ly)
Spacer()
VStack(spacing:3) {
button("up","↑")
HStack(spacing:3) {button("left","←");Color.clear.frame(width:36,height:36);button("right","→")}
button("down","↓")
}
Spacer()
VStack(spacing:3) {
button("y",state.playStation ? "△" : "Y")
HStack(spacing:3) {button("x",state.playStation ? "□" : "X");button("b",state.playStation ? "○" : "B")}
button("a",state.playStation ? "×" : "A")
}
Spacer()
stick("rs","R3",state.rx,state.ry)
}
}.padding(26).background(Color.white.opacity(0.045),in:RoundedRectangle(cornerRadius:24))
Text(text("Esta prueba comprueba la entrada del control en este Mac, no la compatibilidad de un juego."))
.font(.system(size:11)).foregroundStyle(.secondary)
}.padding(26).frame(width:690).preferredColorScheme(.dark)
.onReceive(tick) {_ in if NSApplication.shared.isActive {tester.sample()} else {tester.clear()}}
.onDisappear {tester.clear()}
}
}
+9
View File
@@ -12,6 +12,15 @@ enum EasyLanguage: String, CaseIterable {
struct EasyStrings {
static let english: [String:String] = [
"Probar control": "Test controller",
"Prueba tu control": "Test your controller",
"Pulsa botones y mueve los sticks. Verás cada movimiento aquí.": "Press buttons and move the sticks. Every movement appears here.",
"Conecta un control a este Mac por Bluetooth.": "Connect a controller to this Mac via Bluetooth.",
"¡Está respondiendo!": "Input received!",
"Esperando tu primera pulsación…": "Waiting for your first input…",
"Presionado": "Pressed",
"Suelto": "Released",
"Esta prueba comprueba la entrada del control en este Mac, no la compatibilidad de un juego.": "This tests controller input on this Mac, not game compatibility.",
"Sin control conectado en este Mac": "No controller connected to this Mac",
"Conéctalo por Bluetooth y aparecerá aquí.": "Connect it via Bluetooth and it will appear here.",
"Control": "Controller",
+31 -19
View File
@@ -227,17 +227,19 @@ struct SupportCarousel: View {
// Apply the compact launch height even when macOS restores an older window size.
struct CompactWindow: NSViewRepresentable {
func makeNSView(context:Context) -> NSView { WindowView() }
func updateNSView(_ nsView:NSView,context:Context) {}
let height: CGFloat
func makeNSView(context:Context) -> NSView {let view=WindowView();view.height=height;return view}
func updateNSView(_ nsView:NSView,context:Context) {(nsView as? WindowView)?.height=height}
private final class WindowView: NSView {
private var adjusted = false
override func viewDidMoveToWindow() {
super.viewDidMoveToWindow()
guard let window=window, !adjusted else {return}
adjusted=true
var height: CGFloat=650 {didSet {if height != oldValue {resize()}}}
private var lastHeight: CGFloat?
override func viewDidMoveToWindow() {super.viewDidMoveToWindow();resize()}
private func resize() {
guard let window=window, lastHeight != height else {return}
let target=height;lastHeight=target
DispatchQueue.main.async {
let width=max(760,window.contentLayoutRect.width)
window.setContentSize(NSSize(width:width,height:710))
guard !window.styleMask.contains(.fullScreen) else {return}
window.setContentSize(NSSize(width:max(760,window.contentLayoutRect.width),height:target))
}
}
}
@@ -254,6 +256,7 @@ enum AppArtwork {
struct ContentView: View {
@StateObject private var model=BuildModel()
@StateObject private var controllerMonitor=ControllerMonitor()
@State private var showControllerTest=false
private let ink=Color(red:0.93,green:0.94,blue:0.90)
private let lime=Color(red:0.79,green:0.95,blue:0.37)
var body: some View {
@@ -293,11 +296,19 @@ struct ContentView: View {
SupportCarousel(games:model.catalog)
}
VStack(alignment:.leading,spacing:18) {
Picker(model.text("¿Dónde quieres jugar?"),selection:$model.target) {
Text(model.text("Elige un dispositivo")).tag("")
Text("iPhone / iPad").tag("ios")
Text("Mac").tag("mac")
}.pickerStyle(.menu).disabled(model.running)
HStack {
Text(model.text("¿Dónde quieres jugar?")).font(.system(size:13,weight:.medium))
Spacer()
ForEach(["ios","mac"],id:\.self) { target in
Button {model.target=target} label: {
Text(target == "ios" ? "iPhone / iPad" : "Mac")
.font(.system(size:13,weight:.semibold)).padding(.horizontal,16).padding(.vertical,9)
.foregroundStyle(model.target == target ? Color.black : ink)
.background(model.target == target ? lime : Color.white.opacity(0.06),in:RoundedRectangle(cornerRadius:9))
}.buttonStyle(.plain).disabled(model.running).accessibilityIdentifier("target-"+target)
}
}
if model.isMac {
HStack(alignment:.top,spacing:10) {
Image(systemName:controllerMonitor.controllers.contains(where:{$0.supported}) ? "checkmark.circle.fill" : "gamecontroller")
.foregroundStyle(controllerMonitor.controllers.contains(where:{$0.supported}) ? lime : .gray)
@@ -311,12 +322,12 @@ struct ContentView: View {
.font(.system(size:13,weight:.medium))
}
}
if model.target == "ios" {
Text(model.text("Para jugar en iPhone/iPad, conecta el control a ese dispositivo."))
.font(.system(size:12)).foregroundStyle(.secondary)
}
}
Spacer()
Button(model.text("Probar control")){showControllerTest=true}
.disabled(model.running).accessibilityIdentifier("test-controller")
}.accessibilityIdentifier("controller-status")
}
HStack(spacing:12) {
Image(systemName:"folder.fill").font(.system(size:24)).foregroundStyle(lime)
VStack(alignment:.leading,spacing:4) {
@@ -405,8 +416,9 @@ struct ContentView: View {
}
}.padding(28)
}
}.frame(minWidth:760,minHeight:530).background(CompactWindow()).preferredColorScheme(.dark)
}.frame(minWidth:760,minHeight:530).background(CompactWindow(height:model.isMac ? 710 : 650)).preferredColorScheme(.dark)
.sheet(isPresented:$model.showCatalog) {GameCatalogView(model:model)}
.sheet(isPresented:$showControllerTest) {ControllerTestView(text:model.text)}
.onReceive(NotificationCenter.default.publisher(for:NSApplication.willTerminateNotification)) {_ in model.cancel()}
}
}
@@ -0,0 +1,38 @@
import GameController
@main struct InputChecks {
@MainActor static func main() {
let controller=GCController.withExtendedGamepad()
let gamepad=controller.extendedGamepad!
var connected=[controller]
let tester=ControllerTester(query:{connected})
precondition(!tester.confirmed && !tester.state.active)
gamepad.buttonA.setValue(1)
gamepad.leftShoulder.setValue(1)
gamepad.dpad.setValueForXAxis(-1,yAxis:1)
gamepad.leftThumbstick.setValueForXAxis(0.75,yAxis:-0.5)
gamepad.rightThumbstick.setValueForXAxis(-0.5,yAxis:0.25)
gamepad.leftTrigger.setValue(0.6)
tester.sample()
precondition(tester.state.buttons.contains("a") && tester.state.buttons.contains("lb"))
precondition(tester.state.buttons.contains("left") && tester.state.buttons.contains("up"))
precondition(abs(tester.state.lx-0.75)<0.01 && abs(tester.state.ly+0.5)<0.01)
precondition(abs(tester.state.rx+0.5)<0.01 && abs(tester.state.ry-0.25)<0.01)
precondition(abs(tester.state.lt-0.6)<0.01 && tester.confirmed)
gamepad.buttonA.setValue(0);gamepad.leftShoulder.setValue(0)
gamepad.dpad.setValueForXAxis(0,yAxis:0)
gamepad.leftThumbstick.setValueForXAxis(0,yAxis:0)
gamepad.rightThumbstick.setValueForXAxis(0,yAxis:0)
gamepad.leftTrigger.setValue(0)
tester.sample()
precondition(!tester.state.active && tester.state.buttons.isEmpty && tester.confirmed)
let second=GCController.withExtendedGamepad()
connected=[controller,second];tester.sample()
tester.selected=ObjectIdentifier(second)
gamepad.buttonA.setValue(1);tester.sample()
precondition(!tester.state.active && !tester.confirmed,"A different controller must not light up the selected gamepad")
connected=[];tester.sample()
precondition(tester.devices.isEmpty && !tester.state.active && !tester.confirmed)
print("Live input: buttons, D-pad, both sticks, trigger pressure, release and disconnect passed")
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 418 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

+15 -2
View File
@@ -199,8 +199,8 @@ required. Successful packaging is not a new claim of gameplay verification.
`desktop/NxNativeEasy/ControllerMonitor.swift` enumerates `GCController.controllers`
at launch and refreshes on connect, disconnect and foreground notifications. Only
an extended gamepad profile is marked ready. This is local Mac input readiness,
not a claim of a phone connection or a successful game boot. It does not install
an extended gamepad profile is marked ready. The entire controller panel is displayed only for Mac builds. This is local Mac
input readiness, not a claim of a phone connection or a successful game boot. It does not install
input handlers or change pairing, so it cannot take input away from a game.
Notification tests use Apple's snapshot controllers, including unsupported profiles;
they do not claim testing physical Xbox or PlayStation hardware.
@@ -208,3 +208,16 @@ they do not claim testing physical Xbox or PlayStation hardware.
The launcher has one SwiftUI Window scene and activates an existing process with
the same bundle ID rather than keeping a duplicate process. The installer and
local opening command now use `~/Applications/nxnative Easy.app` by default.
## Live controller test
The Mac-only Test controller sheet samples `GCExtendedGamepad` values at 60 Hz
while visible and foreground. It draws actual button/D-pad presses, both stick
positions, thumbstick clicks, menu buttons and analog trigger pressure. The success
label requires input, not a connection alone. PlayStation gamepad profiles use
PlayStation face-button symbols. Disconnecting or changing the selected controller
clears stale input; switching to another app clears the live drawing. Sampling
never replaces input handlers or enables background controller capture.
Snapshot-controller tests check pressure, axes, releases, disconnect and selecting
a second device. They are not physical-hardware or game compatibility validation.
+2 -2
View File
@@ -20,7 +20,7 @@ def main():
if output.suffix!='.app':parser.error('Output must end with .app')
contents=output/'Contents';macos=contents/'MacOS';macos.mkdir(parents=True,exist_ok=True)
subprocess.run(['xcrun','swiftc','-parse-as-library','-swift-version','5',
str(REPO/'desktop/NxNativeEasy/main.swift'),str(REPO/'desktop/NxNativeEasy/Localization.swift'),str(REPO/'desktop/NxNativeEasy/GameCatalog.swift'),str(REPO/'desktop/NxNativeEasy/ControllerMonitor.swift'),'-o',str(macos/'NxNativeEasy'),
str(REPO/'desktop/NxNativeEasy/main.swift'),str(REPO/'desktop/NxNativeEasy/Localization.swift'),str(REPO/'desktop/NxNativeEasy/GameCatalog.swift'),str(REPO/'desktop/NxNativeEasy/ControllerMonitor.swift'),str(REPO/'desktop/NxNativeEasy/ControllerTest.swift'),'-o',str(macos/'NxNativeEasy'),
'-framework','SwiftUI','-framework','AppKit','-framework','GameController'],check=True)
resources=contents/'Resources';resources.mkdir(exist_ok=True)
shutil.copy2(REPO/'desktop/NxNativeEasy/Assets/AppIcon.png',resources/'BrandIcon.png')
@@ -30,7 +30,7 @@ def main():
subprocess.run(['iconutil','-c','icns',str(iconset),'-o',str(resources/'AppIcon.icns')],check=True)
info=dict(CFBundleIconFile='AppIcon', CFBundleIdentifier='com.nalancer08.nxnative.easy',CFBundleName='nxnative Easy',
CFBundleDisplayName='nxnative Easy',CFBundleExecutable='NxNativeEasy',
CFBundlePackageType='APPL',CFBundleShortVersionString='1.2',CFBundleVersion='3',
CFBundlePackageType='APPL',CFBundleShortVersionString='1.3',CFBundleVersion='4',
LSMinimumSystemVersion='26.0',CFBundleDevelopmentRegion='en',CFBundleLocalizations=['en','es'],NSHighResolutionCapable=True,NxNativeSource=str(REPO))
(contents/'Info.plist').write_bytes(plistlib.dumps(info))
# Explicit ad-hoc signing: '-' never selects an Apple certificate or team.
+10
View File
@@ -16,3 +16,13 @@ class DesktopController(unittest.TestCase):
str(ROOT/'desktop/NxNativeEasy/tests/controller_check.swift'),
'-framework','AppKit','-framework','GameController','-o',str(executable)],check=True)
subprocess.run([str(executable)],check=True)
def test_live_input_and_release(self):
with tempfile.TemporaryDirectory() as tmp:
executable=Path(tmp)/'input'
subprocess.run(['xcrun','swiftc','-parse-as-library','-swift-version','5',
str(ROOT/'desktop/NxNativeEasy/ControllerMonitor.swift'),
str(ROOT/'desktop/NxNativeEasy/ControllerTest.swift'),
str(ROOT/'desktop/NxNativeEasy/tests/controller_input_check.swift'),
'-framework','SwiftUI','-framework','AppKit','-framework','GameController','-o',str(executable)],check=True)
subprocess.run([str(executable)],check=True)