Build & contribute
Porting to macOS and Linux
What is Windows-specific in HotTake today, what to change for macOS and Linux, a first-build plan, a checklist, and how to report results.
HotTake ships for Windows only today, but it was written to stay portable: Rust, Tauri 2 and plain web code, no Windows UI frameworks. This page lists what is Windows-specific right now and what a macOS or Linux build needs.
Each item is marked:
- Verified in code: found in the HotTake source as described.
- General guidance: standard Tauri or OS practice, not yet tried with HotTake. Treat these as starting points and report back.
Audit: what is Windows-specific today
Config folder uses %APPDATA% (verified in code)
host_core::config_dir() in crates/host-core/src/lib.rs returns HOTTAKE_CONFIG_DIR if set, else %APPDATA%\HotTake. macOS and Linux have no APPDATA variable, so it returns nothing. As a result:
- The game library is off. Imports fail with "the game library is unavailable (no config folder)".
- Online play finds no
online.jsonand stays hidden.
Change: add a per-OS fallback. A sketch, not yet in the code:
pub fn config_dir() -> Option<std::path::PathBuf> {
use std::path::PathBuf;
if let Some(dir) = std::env::var_os("HOTTAKE_CONFIG_DIR") {
return Some(dir.into());
}
#[cfg(windows)]
return std::env::var_os("APPDATA").map(|d| PathBuf::from(d).join("HotTake"));
#[cfg(target_os = "macos")]
return std::env::var_os("HOME").map(|h| PathBuf::from(h).join("Library/Application Support/HotTake"));
#[cfg(all(unix, not(target_os = "macos")))]
return std::env::var_os("XDG_CONFIG_HOME")
.map(PathBuf::from)
.or_else(|| std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".config")))
.map(|d| d.join("HotTake"));
}Until then, set HOTTAKE_CONFIG_DIR when testing.
cloudflared lookup and lifetime (verified in code)
In crates/host-core/src/online.rs:
find_cloudflared()checksHOTTAKE_CLOUDFLARED, thenPATH, thenProgram Filesfolders. It already usescloudflared(no.exe) off Windows.- On Windows,
cloudflaredis started withCREATE_NO_WINDOWand put in a job object (windows-sys, acfg(windows)dependency incrates/host-core/Cargo.toml). The job kills the tunnel when HotTake exits, even on a forced close. Elsewhere onlykill_on_dropis left, so a crash or force-quit can leavecloudflaredrunning.
Change on macOS (general guidance): apps started from Finder get a minimal PATH without Homebrew, so also check /opt/homebrew/bin and /usr/local/bin. There is no job-object equivalent. One option: remember the child PID and kill a stale cloudflared at the next start.
Change on Linux (general guidance): add /usr/local/bin and /usr/bin. Use prctl(PR_SET_PDEATHSIG) in a pre_exec hook so the child dies with HotTake.
Hotspot detection and adapter names (verified in code)
crates/host-core/src/net.rs picks the LAN addresses for the QR code:
is_windows_hotspot()treats192.168.137.xas Windows Mobile Hotspot or Wi-Fi Direct. It ranks it after the shared Wi-Fi and labels it "Laptop hotspot / direct link".- Virtual adapters are skipped by name hints:
vethernet,virtualbox,vmware,wsl,hyper-v,docker,tailscale,zerotier,loopback. - Other addresses are labelled with the raw adapter name, which on Windows reads nicely ("Wi-Fi").
Change (general guidance, verify the subnets on a real machine):
- macOS Internet Sharing usually puts clients on a
bridge100interface, commonly192.168.2.x. Add a macOS hotspot rule. Mapen0-style names to friendlier labels. - NetworkManager hotspots on Linux commonly use
10.42.0.1. Add hints forvirbr,veth,br-andlxc.
Windows-only tests (verified in code)
Three tests are #[cfg(windows)] and simply don't run elsewhere:
library.rs:hidden_folders_are_refused_through_links_and_short_names, which usescmd /C mklink /Jand 8.3 short names.library.rs:removing_a_locked_imported_game_is_a_disk_error, which uses Windows share modes.tests/library.rs:a_junction_out_of_a_dev_folder_is_not_followed, which usesmklink /J.
The code they protect (canonical-path checks) is portable. Change: add #[cfg(unix)] twins that use std::os::unix::fs::symlink, so the escape guards are tested on macOS and Linux too.
Path rules that stay as they are (verified in code)
Zip import and manifest paths refuse backslashes, drive letters, Windows device names (con, nul…), characters Windows cannot store, and names that differ only in case. These rules are stricter than macOS or Linux need, and that is deliberate: a game zipped on a Mac must still unpack on Windows. Keep them. Rust code joins paths with PathBuf::join; no hard-coded separators were found outside tests.
Firewall (verified in code)
There is no netsh or firewall code. On Windows the OS prompts the first time HotTake listens on 0.0.0.0. HotTake needs 47801/tcp from the LAN and 47810–47899/udp for WebRTC. Port 47802 is loopback-only.
Webview assumptions (verified in code, plus guidance)
The window loads http://127.0.0.1:47801/host/ as an external URL (apps/desktop/src-tauri/src/main.rs). No IPC is used, and no WebView2-only API was found in web/. WebRTC is handled in the Rust core, not the webview, so WebKitGTK's lack of RTCPeerConnection doesn't matter. Still check:
- macOS WKWebView (general guidance): that plain-HTTP loads from
127.0.0.1are allowed (if not, addNSAllowsLocalNetworkingunderNSAppTransportSecurity). Also check the Gamepad API, fullscreen, audio autoplay, and the WebGL games (Sushi Paws, Cartastrophe, Night Siege). The project's research notes say WKWebView capsrequestAnimationFrameat about 60 fps. - Linux WebKitGTK (general guidance): it can silently fall back to software WebGL. Gamepad support depends on how WebKitGTK was built. Test the heavy games first.
Bundling (verified in code)
tauri.conf.json sets bundle.targets to ["nsis"] with a Windows-only nsis block. icon.icns is already in the icon list. The windows_subsystem attribute in main.rs is ignored on other targets. (app.windows: [] in the config is the Tauri window list, not the OS.)
Tooling and docs (verified in code)
scripts/screenshot-window.ps1is PowerShell callinguser32.dll: Windows only.- The README's automated runs use headless
msedge, and--cdpagainst the desktop app usesWEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS. WKWebView and WebKitGTK don't speak the Chrome DevTools Protocol. On macOS and Linux, run--cdpbot runs againstnpm run devwith headless Chrome or Chromium on the host page. - The Developer tab's path placeholder shows
C:\games\my-game. The error page's font stack falls back toSegoe UI. Both are cosmetic. scripts/build-ports.mjsandscripts/disk-budget.mjsalready handle both platforms.- The website's upload script sends installers with a Windows executable content type, and the site offers a Windows download only.
macOS specifics (general guidance)
-
Platform config: add
apps/desktop/src-tauri/tauri.macos.conf.json; Tauri merges it overtauri.conf.jsonon macOS:{ "bundle": { "targets": ["app", "dmg"], "macOS": { "minimumSystemVersion": "11.0" } } } -
Info.plist: Tauri merges
src-tauri/Info.plist. On macOS 15 and later, the laptop sends WebRTC checks to phones on the LAN, which triggers the Local Network prompt. Add a reason string:<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>NSLocalNetworkUsageDescription</key> <string>HotTake lets phones on your Wi-Fi join as game controllers.</string> </dict> </plist> -
Firewall: with the application firewall on, macOS asks to accept incoming connections. Unsigned builds may be asked again after every rebuild; a signed app keeps its answer.
-
Universal binary:
rustup target add aarch64-apple-darwin x86_64-apple-darwin, thennpm run app:build -- --target universal-apple-darwin. -
Signing and notarization: needs an Apple Developer ID. Tauri signs and notarizes during
tauri buildwhenAPPLE_SIGNING_IDENTITYand the notarization variables (APPLE_ID,APPLE_PASSWORD,APPLE_TEAM_ID, or the App Store Connect API key variables) are set. Never commit them.
Linux specifics (general guidance)
-
Build dependencies (Debian/Ubuntu):
sudo apt install libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev libayatana-appindicator3-dev librsvg2-dev -
Platform config:
apps/desktop/src-tauri/tauri.linux.conf.jsonwith"bundle": { "targets": ["deb", "appimage"] }. -
Firewall: nothing prompts. With ufw on, open the ports:
sudo ufw allow 47801/tcp sudo ufw allow 47810:47899/udp -
Graphics: if the window is blank or slow on some GPU drivers, try
WEBKIT_DISABLE_DMABUF_RENDERER=1, and measure frame times.
First macOS build: step by step
- Install the Xcode command line tools (
xcode-select --install), Rust via rustup, and Node 22.18 or newer. - In the repo:
npm install, thennpm test. cargo test --workspace. Expect the three Windows-only tests to be skipped; everything else should pass. Note any failures.- Run the headless host with a config folder:
HOTTAKE_CONFIG_DIR=$HOME/hottake-dev npm run dev. Openhttp://127.0.0.1:47801/host/in Chrome and join from a phone on the same Wi-Fi. This checks the Rust core and networking without the webview. - Patch
config_dir()(above) so the app works without the variable. - Add
tauri.macos.conf.jsonandInfo.plist, thennpm run app:dev. Check the window, fullscreen (F11), phones joining, the firewall and Local Network prompts, the path shown on the phone, and a WebGL game. - Run the bots against it:
node tests/e2e/bots.mjs 4 0 http://127.0.0.1:47801 --start --game=coin-clash --until-end. npm run app:buildand open the.dmgfromtarget/release/bundle/dmg/.- Optional: a universal build, then signing and notarization.
- Install on a second Mac that has never seen HotTake and repeat the phone test.
Linux follows the same path with its dependencies and config file instead.
Porting checklist
-
config_dir()has macOS and Linux fallbacks -
cloudflaredfound in Homebrew and system paths; orphan handling on macOS and Linux - Hotspot subnets and adapter labels for macOS and Linux in
net.rs - Unix twins for the three Windows-only tests
-
tauri.macos.conf.jsonandtauri.linux.conf.jsonwith bundle targets -
Info.plistwithNSLocalNetworkUsageDescription - Host screen, fullscreen, gamepads and audio checked in WKWebView and WebKitGTK
- All built-in games pass a bot run on the new platform
- Real phones join over Wi-Fi; online play works with
cloudflared - Signed and notarized
.dmg;.deband AppImage install on a clean machine - Website upload script and download page handle more than one platform
How to help
If you try HotTake on macOS or Linux, please share what happened, even if it's only "the tests pass". Open an issue, or a PR if you have a fix. Include:
- OS and version, CPU (Apple silicon, Intel, x86_64, ARM) and, for Linux, the distro and WebKitGTK version.
- Which steps above you got through, and the first one that failed, with the error text.
- Which games ran well and which were slow, plus the phone path you saw (
WebRTC,WebRTC relayorWebSocket). - Whether you changed code, and how.
See Contributing for how to get the source and how changes are proposed.