Skip to content

feat: add macOS support alongside Windows#27

Open
script8888 wants to merge 2 commits into
NUber-dev:mainfrom
script8888:feat/macos-support
Open

feat: add macOS support alongside Windows#27
script8888 wants to merge 2 commits into
NUber-dev:mainfrom
script8888:feat/macos-support

Conversation

@script8888

@script8888 script8888 commented Jul 11, 2026

Copy link
Copy Markdown

Summary

Adds first-class macOS support (Apple Silicon + Intel) while keeping Windows fully intact — all Windows code stays behind its existing cfg(windows) gates, NSIS keeps building, and the release workflow becomes a two-platform matrix.

Tested end-to-end on an Apple Silicon Mac (macOS 15): build, sign-in, playback, session persistence across restart, .app/.dmg bundling.

Native window chrome

  • New src-tauri/tauri.macos.conf.json platform overlay: native decorations with titleBarStyle: Overlay + hidden title. The base config (and Windows behavior) is unchanged.
  • New src/lib/platform.ts (IS_MAC); the top bar skips the custom min/max/close cluster on mac and pads the left nav past the traffic lights. Drag region, close-to-tray routing, and the floating player are unchanged.

Cookie encryption (macOS counterpart of DPAPI)

  • secure_store gains a macOS branch: a random 32-byte data key stored in the login Keychain (keyring crate, apple-native), cookie jars framed as YTBC1 ++ 12-byte nonce ++ AES-256-GCM (aes-gcm).
  • Blobs without the framing (jars written before this lands) pass through as plaintext and get re-encrypted on the next write — no migration step needed.
  • The AEAD framing is unit-tested with a fixed key so CI never touches a Keychain. Windows DPAPI branch untouched; Linux keeps the plaintext fallback + FIXME.

Google sign-in on WKWebView

  • YT_LOGIN_UA is now platform-specific. Presenting the Chrome UA from WKWebView trips Google's "This browser or app may not be secure" block (UA contradicts the engine fingerprint); a Safari 17.6 UA matches the engine and signs in fine. Applies to both the login window and the session-keeper so the issued session is renewed under the same UA. WebView2 keeps the Chrome UA.
  • Note: passkey sign-in doesn't work in WKWebView (WebAuthn needs a browser entitlement) — password sign-in works.

Dock / menu-bar semantics

  • RunEvent::Reopen handler restores the hidden main window on Dock-icon click (builder now goes through .build(...) + .run(closure)).
  • Tray menu opens on left-click on mac (menu-bar idiom); Windows keeps left-click = show window.
  • Settings copy reads "Close to menu bar" on mac.

Media controls

  • No functional change needed: souvlaki's macOS backend ignores PlatformConfig, so the existing hwnd: None path drives Now Playing / media keys. Comments updated, Manager import cfg-gated.

Distribution

  • macOS bundles app + dmg (minimumSystemVersion: 10.15); createUpdaterArtifacts already yields .app.tar.gz + .sig.
  • release.yml: matrix of macos-latest (--target universal-apple-darwin) + windows-latest; tauri-action merges both into one latest.json. APPLE_* signing/notarization secrets are pre-wired — while unset the build ships unsigned, adding them later activates signing with no workflow change.
  • ci.yml: rust job runs on ubuntu + macos.
  • README: macOS install info + Gatekeeper first-launch FAQ (right-click → Open or xattr -cr).
  • Good news found during the port: yt-dlp_macos is now a universal binary, so no Rosetta requirement on Apple Silicon.

Library → Songs fix (platform-independent bug found while testing)

  • The Songs tab fetched the auto-generated LM "Your Likes" playlist — the cross-YouTube likes bucket — so Shorts and regular videos liked in the YouTube app appeared in the library as songs (on Windows too).
  • Switched to the dedicated library feed (browse FEmusic_liked_videos), which is music-only server-side and is what the official client's Songs tab reads. New fetchLibrarySongsFirstPage / fetchLibrarySongsContinuation reuse the shared row mapper and continuation machinery.
  • The heart-fill cache and the "Liked Music" playlist page still read LM, which genuinely is the likes bucket.

Test plan

  • cargo test --lib (12/12, incl. new AEAD round-trip/tamper/nonce/passthrough tests), cargo check clean on macOS
  • pnpm build (tsc + vite), eslint (no new warnings), vitest 51/51
  • pnpm tauri dev on Apple Silicon: sign-in (Safari UA), playback via yt-dlp → local stream server, session persists across restart
  • Library → Songs shows library songs only (no liked Shorts/videos); infinite-scroll pagination and like/unlike invalidation still work
  • pnpm tauri build: .app + .dmg + .app.tar.gz produced and the bundled app runs (updater signature step needs the TAURI_SIGNING_PRIVATE_KEY secret, as on Windows)
  • Windows regression run on CI (existing rust job now also builds/tests on macos)
  • Tag a test prerelease to exercise the two-platform release matrix and merged latest.json

🤖 Generated with Claude Code

script8888 and others added 2 commits July 11, 2026 17:32
Adds first-class macOS support while keeping every Windows path
untouched behind its existing cfg(windows) gates.

- Native titlebar: tauri.macos.conf.json platform overlay enables
  decorations with overlay traffic lights; the custom min/max/close
  cluster renders only off-mac (new src/lib/platform.ts IS_MAC gate)
- Cookie encryption: secure_store gains a macOS branch — random
  32-byte key in the login Keychain, jars framed as
  YTBC1 + nonce + AES-256-GCM; unframed (pre-existing plaintext) jars
  pass through and re-encrypt on next write. Unit-tested with a fixed
  key so CI never touches the Keychain
- Google sign-in: YT_LOGIN_UA is now platform-specific — WKWebView
  presents a Safari UA (a Chrome UA on a WebKit engine trips Google's
  "browser not secure" block); WebView2 keeps the Chrome UA
- Dock/menu-bar semantics: RunEvent::Reopen restores the hidden main
  window on Dock click; tray menu opens on left-click on mac;
  Settings copy reads "menu bar" instead of "tray" on mac
- Media controls: souvlaki's hwnd-less macOS path documented (drives
  Now Playing / media keys as-is); Manager import cfg-gated
- Distribution: app+dmg bundle targets (min macOS 10.15), release
  workflow becomes a macos-universal + windows matrix with APPLE_*
  signing secrets pre-wired (unsigned until secrets are set), CI rust
  job runs on ubuntu + macos, README documents the Gatekeeper
  first-launch workaround

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ylist

The Songs tab fetched the auto-generated LM "Your Likes" playlist,
which is the cross-YouTube likes bucket — thumbing up a Short or any
regular video in the YouTube app made it show up as a "song". The
dedicated library feed (browse FEmusic_liked_videos) is music-only
server-side and is what the official client's Songs tab reads.

- library.ts: fetchLibrarySongsFirstPage / fetchLibrarySongsContinuation
  reusing the shared row mapper and continuation machinery
- library.tsx: Songs tab switches to the new feed
  (queryKey ["library","songs-pages"])
- like-buttons.tsx: like/unlike invalidates the renamed key; the
  heart-fill cache and the Liked Music playlist page still read LM,
  which genuinely is the likes bucket

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant