Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
41 changes: 18 additions & 23 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v7
with:
go-version: '^1.23.0'

- name: Set up linux dependencies
run: sudo apt-get update && sudo apt-get install -y gcc

- uses: actions/checkout@v4
- uses: actions/checkout@v7
with:
fetch-depth: 0

Expand Down Expand Up @@ -48,11 +48,6 @@ jobs:
goarch: "386"
suffix: zip
name: win-x86
- os: windows-latest
goos: windows
goarch: arm
suffix: zip
name: win-arm
- os: windows-latest
goos: windows
goarch: arm64
Expand All @@ -63,11 +58,11 @@ jobs:

steps:
- name: Inject slug/short variables
uses: rlespinasse/github-slug-action@v4
uses: rlespinasse/github-slug-action@v5

- name: Set up Go
if: ${{ matrix.goos != 'windows' || matrix.os-variant != '7' }}
uses: actions/setup-go@v5
uses: actions/setup-go@v7
with:
go-version: '^1.23.0'

Expand All @@ -80,7 +75,7 @@ jobs:

- run: echo "basename=sni-${{env.GITHUB_REF_SLUG}}-${{matrix.goos}}${{matrix.os-variant}}-${{matrix.goarch}}${{matrix.alt}}" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append

- uses: actions/checkout@v4
- uses: actions/checkout@v7
name: Checkout
with:
fetch-depth: 0
Expand Down Expand Up @@ -130,7 +125,7 @@ jobs:
run: 7z x -o"${{env.basename}}" "${{ runner.temp }}/${{ matrix.snfm }}"

- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{env.basename}}
path: ${{env.basename}}/
Expand Down Expand Up @@ -163,16 +158,16 @@ jobs:

steps:
- name: Inject slug/short variables
uses: rlespinasse/github-slug-action@v4
uses: rlespinasse/github-slug-action@v5

- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v7
with:
go-version: '^1.23.0'

- run: echo "basename=sni-${{env.GITHUB_REF_SLUG}}-darwin-universal${{matrix.alt}}" >> $GITHUB_ENV

- uses: actions/checkout@v4
- uses: actions/checkout@v7
name: Checkout
with:
fetch-depth: 0
Expand Down Expand Up @@ -218,7 +213,7 @@ jobs:
run: 7z x -o"${{env.basename}}" "${{ runner.temp }}/${{ matrix.snfm }}"

- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{env.basename}}
path: ${{env.basename}}/
Expand Down Expand Up @@ -282,10 +277,10 @@ jobs:

steps:
- name: Inject slug/short variables
uses: rlespinasse/github-slug-action@v4
uses: rlespinasse/github-slug-action@v5

- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v7
with:
go-version: '^1.23.0'

Expand All @@ -294,7 +289,7 @@ jobs:

- run: echo "basename=sni-${{env.GITHUB_REF_SLUG}}-${{matrix.goos}}${{matrix.os-variant}}-${{matrix.goarch}}${{matrix.alt}}" >> $GITHUB_ENV

- uses: actions/checkout@v4
- uses: actions/checkout@v7
name: Checkout
with:
fetch-depth: 0
Expand Down Expand Up @@ -336,7 +331,7 @@ jobs:
run: 7z x -o"${{env.basename}}" "${{ runner.temp }}/${{ matrix.snfm }}"

- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{env.basename}}
path: ${{env.basename}}/
Expand All @@ -361,10 +356,10 @@ jobs:
name: manylinux_2_28
steps:
- name: Inject slug/short variables
uses: rlespinasse/github-slug-action@v4
uses: rlespinasse/github-slug-action@v5

- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v7
with:
go-version: '^1.23.0'

Expand All @@ -375,7 +370,7 @@ jobs:

- run: echo "basename=sni-${{env.GITHUB_REF_SLUG}}-manylinux_2_28-amd64" >> $GITHUB_ENV

- uses: actions/checkout@v4
- uses: actions/checkout@v7
name: Checkout
with:
fetch-depth: 0
Expand Down Expand Up @@ -414,7 +409,7 @@ jobs:
tar cJf ${{env.basename}}.tar.xz ${{env.basename}}/

- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: ${{env.basename}}.tar.xz
path: ${{github.workspace}}/${{env.basename}}.tar.xz
Expand Down
27 changes: 24 additions & 3 deletions cmd/sni/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,27 @@ var (
"usb2snes_listen_addrs": "0.0.0.0:23074",
"fxpakpro_disable": false,

// How long the fxpakpro driver waits on a device that has gone quiet or
// stopped accepting data. The firmware does its FAT work inside the USB
// interrupt handler, so a cluster allocation on a large, full, fragmented
// card can stall for seconds before it can answer; raise these if you see
// spurious timeouts on such a card.
"fxpakpro_read_timeout": "15s",
"fxpakpro_write_timeout": "15s",
// Whether a caller's context deadline or cancellation aborts I/O already
// in flight to the device. With this off, a request that has already
// started talking to the fxpakpro runs to completion (bounded by the two
// timeouts above) rather than being cut short mid-transfer by an
// impatient client.
"fxpakpro_honor_caller_deadline": true,
// Optional pause after each 512-byte chunk written during a transfer.
// Zero, the default, writes as fast as the device accepts data. This
// exists to test whether host write pacing affects reliability: setting
// SNI_DEBUG=1 slows transfers dramatically (a hex dump per chunk to a
// file and the console) and has been reported to make failing transfers
// succeed, so this isolates the timing from the logging.
"fxpakpro_chunk_delay": "0s",

"retroarch_disable": false,
"retroarch_hosts": "localhost:55355",
"retroarch_detect_log": false,
Expand All @@ -64,8 +85,8 @@ var (
"emunw_disable": false,
"emunw_detect_log": false,

"proxy_disable": false,
"proxy_backend_host": "",
"proxy_disable": false,
"proxy_backend_host": "",
}
nwaConfigs = map[string]any{
"nwa_port_range": NwaDefaultPort,
Expand All @@ -91,7 +112,7 @@ func InitDir() {
Dir = filepath.Join(Dir, ".sni")

// Follow XDG Base Directory Specification
if _, err := os.Stat(Dir); err != nil {
if _, err := os.Stat(Dir); err != nil {
var xdgConfig = os.Getenv("XDG_CONFIG_HOME")
if xdgConfig == "" {
homeDir, _ := os.UserHomeDir()
Expand Down
6 changes: 6 additions & 0 deletions cmd/sni/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"sni/cmd/sni/appversion"
"sni/cmd/sni/config"
"sni/cmd/sni/logging"
"sni/cmd/sni/power"
"sni/cmd/sni/tray"
"sni/devices/snes/drivers/emunwa"
"sni/devices/snes/drivers/fxpakpro"
Expand Down Expand Up @@ -39,6 +40,11 @@ func main() {
// keep the initial goroutine on this thread for GUI threading purposes
runtime.LockOSThread()

// on macOS, keep this window-less tray process out of App Nap so it keeps
// servicing USB devices promptly instead of being throttled in the
// background (no-op on other platforms):
power.DisableAppNap("SNI maintains real-time communication with SNES devices")

// make the version info public in the appversion package because the main package cannot be imported:
appversion.Init(
version,
Expand Down
43 changes: 43 additions & 0 deletions cmd/sni/power/appnap_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//go:build darwin

package power

/*
#cgo darwin CFLAGS: -x objective-c -fobjc-arc
#cgo darwin LDFLAGS: -framework Foundation
#include <stdlib.h>
#import <Foundation/Foundation.h>

// beginActivity takes a process-level activity assertion and retains the
// returned token so the assertion lives for the entire lifetime of the
// process. The token is intentionally never released; the OS drops it when
// the process exits.
static void beginActivity(const char *reason) {
NSString *r = [NSString stringWithUTF8String:reason];
// NSActivityUserInitiatedAllowingIdleSystemSleep disables App Nap so this
// background, window-less process keeps servicing USB promptly, while still
// allowing the machine to sleep normally when the user walks away.
NSActivityOptions opts = NSActivityUserInitiatedAllowingIdleSystemSleep;
id token = [[NSProcessInfo processInfo] beginActivityWithOptions:opts reason:r];
CFBridgingRetain(token);
}
*/
import "C"

import "unsafe"

// DisableAppNap asks macOS to keep this process out of App Nap for its entire
// lifetime.
//
// App Nap throttles and coalesces the timers and threads of a background,
// window-less application. SNI presents only a status-bar (tray) icon with no
// window, so macOS is free to nap it while a game is in the foreground. A
// napped SNI stops servicing the FX Pak Pro's USB serial stream promptly, which
// makes the device appear to stop responding (reads return zero bytes, writes
// stall) until SNI is restarted. Holding a user-initiated activity assertion
// prevents that.
func DisableAppNap(reason string) {
cReason := C.CString(reason)
defer C.free(unsafe.Pointer(cReason))
C.beginActivity(cReason)
}
6 changes: 6 additions & 0 deletions cmd/sni/power/appnap_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
//go:build !darwin

package power

// DisableAppNap is a no-op on platforms that do not have macOS App Nap.
func DisableAppNap(reason string) {}
Loading