Repository navigation
feat(desktop): port desktop app and platform integrations to macOS - #24
Conversation
|
Closed for local testing |
|
Hello @saivinay1805 Cool stuff. Really like seeing this. Cheers |
|
Thanks for reaching out @jochenkirstaetter ! I looked through #23 — having the shared core/QML changes (Unix key permissions, window geometry persistence, RPC timeouts, and Once #23 lands in |
|
Reopening after successful local testing on macOS (tested device pairing, UI, and test suite on Apple Silicon). |
|
Thanks for this, and for testing it on Apple Silicon. A macOS build is something people have asked for, so it's great to see it started. Before you go further, a few things about how it should fit in, so the work doesn't have to be redone later:
If it helps, splitting it like #23 and #12 (shared changes first, then the macOS code) also makes it much easier to review. Happy to answer questions on any of this. |
|
Hi @saivinay1805, #23 is merged now, so |
422f40b to
96370de
Compare
|
Hi @thtbee, Thanks for merging #23! I have rebased this PR cleanly onto
Local checks passed; awaiting CI approval and review whenever convenient! |
thtbee
left a comment
There was a problem hiding this comment.
Thanks for the rebase. I compared the four wrapped files with main, and the Windows code is the same apart from the cfg wrapper and rustfmt.
What needs fixing:
- Windows x64 CI fails clippy:
decode_imageis unused on Windows, anduse super::*in core_host's tests is unused there since the only test is macOS-only. - Clipboard privacy. Sending the PC clipboard is on by default, and the macOS watcher reads
pbpastewithout checkingorg.nspasteboard.ConcealedType/TransientType, so a password copied from a password manager goes to the phone. Windows skips those (Clip::Private). NSPasteboard would fix it, and itschangeCountavoids starting a process twice a second. The first poll also sends whatever is on the clipboard at startup. - Close to tray is on by default and the tray is a stub, so closing the window leaves the app running with no way back. Turn it off on macOS or handle the Dock reopen.
- Some stubs still report success: the Doctor firewall/network checks and
fix_firewall,elevated::start,wol::wake(andpc.wakeis advertised with no adapters), andlock(), which doesn't check whether osascript was allowed. These should return unsupported or not be offered.
Smaller: stop_ringing kills every afplay the user has running, unmuting the mic sets 75 instead of the old level, query_live_speaker_state needs real audio hardware, the LaunchAgent plist doesn't escape the path, and mac/mod.rs still has a blanket allow(dead_code, unused_imports).
|
Hi @saivinay1805 and @thtbee — are additional contributors welcome? We’re working locally from this macOS port, targeting Apple silicon Macs and Galaxy Fold phones. We’d like to contribute reusable macOS fixes and features, along with Android app improvements, back upstream rather than maintain a separate implementation. Happy to coordinate and submit small, focused PRs that follow the project’s architecture and contribution guidelines. Are there particular areas or priorities you’d prefer us to help with, so we avoid duplicating ongoing work? |
|
Updated in
All local checks pass (112 core unit tests, 148 desktop tests, zero Clippy warnings, fmt, and whitespace checks). Awaiting CI approval and review. |
…ter gating, audio status, download limits) Merge canonical thtbee/nectarlink refs/pull/24/head at exact 9c0f13d, direct child of previous pin e71dee4 (already in product), preserving upstream author/history. No rebase, no snapshot copy. Fetched only that one HTTPS ref with prompts disabled; merge-base resolved to e71dee4, so no deepen/unshallow. One upstream commit, four files. Clean takes: desktop/app/src/mac/audio.rs (osascript exit-status checks, mic-volume read failure is now an honest error) and desktop/app/src/mac/http.rs (bounded curl download: pre/post size checks, --max-filesize, exit-63 too-large deletes, partials kept on other failures). desktop/app/cpp/app_helpers.cpp also merged cleanly: changeCount-verified clipboard reads (two attempts, torn/private-race reads discarded) and setString result propagation; this C++ bridge is dead_code with no Rust callers, and the live Rust objc2 clipboard in mac/clipboard.rs (stronger: torn-snapshot discard, both private markers checked with fail-closed clear, legacy transient type) is untouched and not replaced. 1 conflict file, desktop/app/src/updater.rs (2 hunks): keep the fork's macOS Sparkle route first in can_update() and install() (bundled WaypairUpdater helper + Sparkle.framework, SUFeedURL/SUPublicEDKey from the host bundle), and apply upstream's non-Windows disable only to the GitHub Windows-installer route (newer_release returns None off Windows, can_update false off Windows/macOS, install unsupported off Windows/macOS), so a Mac can never discover or install a Windows installer. Windows paths unchanged. Identities, keys, feeds and license notices untouched; no new capabilities. Not built or tested here by instruction: Root is sole compiler and cross-reviews (focused commands in UPSTREAM-INTEGRATION.md wave report). Co-authored-by: Cursor <cursoragent@cursor.com>
…y1805 watch policy UPSTREAM_BASE.json: move the macos-pr24 pin from e71dee4 to 9c0f13d (one reviewed commit on top of e71dee4, author Signed-off-by present) and record that it enters via merge 435ffa2 on alex.s/upstream-9c0-20261019, preserving upstream parentage. UPSTREAM_SYNC.md: update the pin table and record Alex's latest watch policy: saivinay1805's macOS updates are always incorporated after exact-SHA review via merge (never auto-merge, never an unpinned fork branch), resolving against the fork's Sparkle R2 updater, native objc2 clipboard privacy, identities/keys and license notices. Co-authored-by: Cursor <cursoragent@cursor.com>
thtbee
left a comment
There was a problem hiding this comment.
Thanks, this covers the last review. I read the NSPasteboard code: the concealed/transient check and recording changeCount at startup look right, and the stubs fail honestly now.
What's left:
- It conflicts with main in
MainWindow.qml. I pushed a fix there this morning (e4b279a): on Windows the window from #29 didn't open at all, because NativeWindow's QML type doesn't exposeonWindowVisibilityChangedorscreen. Main now uses a Connections block like yours and drops thescreenassignment, since a position in desktop coordinates already picks the monitor. Please take main's version when you rebase.setScreenByNameshouldn't be needed then; if macOS does need it to move between monitors, call it afterbringToFront(). - #34 (Linux part 1) changes 16 of the same files in a different way. I'd like to merge this one first, since it has had two rounds and is close, and then have #34 rebase onto it. Your
#[cfg(windows)] mod impwith one#[cfg(not(windows))]stub module works well for that, since Linux can start from the same stubs and replace them part by part.
Small: I think CGSession is gone since Big Sur, so lock() will always take the osascript path. That's fine, but the comment says otherwise.
|
@alexfoxtm yes, welcome. Small, focused PRs are the best way to do it. To avoid doing the same work twice: #24 is the macOS port and #34 starts the Linux series. For macOS, it's best to wait until #24 is merged and then pick up what it leaves unsupported, like screen mirroring and H.264 decoding, PC audio streaming, and the tray. For Android, including Fold layouts, please open an issue first with what you have in mind so we can agree on the approach before you write it. CONTRIBUTING.md has the rest (sign-off and checks). |
|
Heads-up for the rebase: main now has photo backup and joining the phone's hotspot, which add a few things under |
Signed-off-by: saivinay1805 <saivinayreddyvavilla@gmail.com>
- Guard Qt::ExpandedClientAreaHint with #ifdef _WIN32 so the QML client area sits below the native macOS titlebar - Hide CaptionButtons on non-Windows platforms and collapse its width to 0 - Allow unused items on non-Windows in main.rs to silence dead-code warnings for platform stubs Signed-off-by: saivinay1805 <saivinayreddyvavilla@gmail.com>
- Add macos-latest to core CI matrix for locked clippy and unit tests - Add desktop-macos job with Qt 6.12 to test and build nectarlink-desktop Signed-off-by: saivinay1805 <saivinayreddyvavilla@gmail.com>
…on non-Windows Signed-off-by: saivinay1805 <saivinayreddyvavilla@gmail.com>
…ust_strings.h Signed-off-by: saivinay1805 <saivinayreddyvavilla@gmail.com>
…pability advertising for macOS - Restore storage.rs, pc_audio_stream.rs, pc_mirror.rs, and play_on_phone.rs to single files with internal cfg blocks (matching Linux thtbee#12 architecture). - Mount macOS platform code via #[cfg(target_os = "macos")] #[path = "mac/mod.rs"] mod win in main.rs and remove crate-wide allow(unused) attributes from desktop and spike crates. - Remove mirror.view from BASE_CAPABILITIES in core so non-decoding desktop platforms can cleanly opt out. - Restrict Windows-only capabilities (media.control, storage.mount, etc.) to cfg(windows) in core_host.rs and report std::env::consts::OS. - Implement real QImage bitmap decoding and AppleScript pasteboard image copy with automatic format conversion and collision-free temporary files. - Return explicit unsupported errors for H.264 video decoding, MP4 recordings, and streamed audio playback instead of silent success stubs. - Fix AppleScript speaker volume and mute status query parser. - Add capability matrix unit test in core and live capability assertion test in desktop. - Add macOS platform support summary and build requirements to README.md. Signed-off-by: saivinay1805 <saivinayreddyvavilla@gmail.com>
…d macOS tray fallbacks - Implement NativeWindow::setScreenByName to map QML screen names to native QScreen pointers without QObject type mismatch errors. - Wrap native NSPasteboard C++ helpers in AutoReleasePool to prevent thread-local memory accumulation. - Restore exact microphone volume on unmute without artificial minimum clamping. - Preserve comb_motion preference initialization across platforms. - Disable close_to_tray toggle and enforce quit-on-close on macOS where system tray is unavailable. - Avoid unused_mut on non-macOS platforms in on_close_to_tray_changed listener. - Guard macOS-only unit test module in core_host.rs and allow unused bridge helpers to resolve Windows CI Clippy failures. - Remove pc.wake from macOS advertised capabilities and supply honest error responses for unimplemented stubs. - Clean up granular dead_code allowances across mac/ platform modules. Signed-off-by: saivinay1805 <saivinayreddyvavilla@gmail.com>
…us, and download limits - Verify changeCount before and after reading clipboard text to avoid race condition with private data - Propagate pasteboard write results in mac_clipboard_write_text - Disable updater and Windows installer discovery on non-Windows - Enforce HTTP download size limits while preserving partial files on network errors - Check osascript execution status in audio commands and fail if reading microphone state fails Signed-off-by: saivinay1805 <saivinayreddyvavilla@gmail.com>
…t latest upstream APIs - Rebase onto upstream main, taking main's MainWindow.qml without conflict - Remove setScreenByName from NativeWindow as window positioning in desktop coordinates handles monitor placement - Update CGSession comment in mac/shell.rs regarding modern macOS fallback - Add macOS stubs and helpers for new upstream features (wifi module for hotspot, local_offset_ms and free_space for photo backup, preserving Some(0) on full drive) Signed-off-by: saivinay1805 <saivinayreddyvavilla@gmail.com>
9c0f13d to
749b7fd
Compare
|
Rebased onto
All local checks pass (164 desktop tests, 112 core tests, zero Clippy warnings, fmt, and whitespace checks). |
|
Thanks, the rebase looks right: MainWindow.qml is the same as main's, nothing from main was undone, and Two small things: photo backup's Open button runs Jochen is checking how #34 and the rest of the Linux series line up with this, so I'll wait for that before merging either one. |
thtbee
left a comment
There was a problem hiding this comment.
Went through it once more as a whole: the Windows code in the shared files is the same as main's apart from the cfg wrappers, the updater's Windows path is unchanged, and CI is green on all three platforms. Thanks for sticking with all the rounds.
Both ports' CI jobs, link libraries and platform modules side by side; vcam keeps this branch's shm, which serves macOS too, instead of thtbee#24's stubs.
Summary
This pull request ports the NectarLink desktop application to macOS, delivering on the roadmap goal in docs/PLAN.md §10.7 to bring the Qt Quick + Rust desktop core cross-platform.
It incorporates maintainer feedback from PR #24 and PR #23:
main(incorporating PR core, desktop, android: cross-platform Linux preparation & USB session reliability #23 Unix groundwork and USB session fixes).storage.rs,pc_audio_stream.rs,pc_mirror.rs, andplay_on_phone.rs) with internal#[cfg]blocks.Feature Scope on macOS
Supported Today
Intentionally Not Advertised / Not Supported Yet
mirror.viewis removed fromBASE_CAPABILITIESand restricted to Windows, preventing unhandled video stream initiation.Errreturns on unsupported stream initialization rather than silent success stubs.media.control): Omitted from macOS desktop capabilities.storage.mount) is Windows-only.Key Changes
macOS Platform Subsystem (
desktop/app/src/mac/):clipboard: Two-way text synchronization viapbcopy/pbpaste, plus AppleScript«class PNGf»pasteboard image writing with automatic format conversion and collision-free temporary files.image: Real bitmap decoding using QtQImage::loadFromDatainto BGRA pixels.audio: Syntax-safe AppleScript speaker volume and mute status query parser.single_instance: Unix domain socket-based single instance detection (nectarlink.sock) with automatic stale socket cleanup and activation wakeups.toast: Native macOS system notifications viaosascript.http,browser,shell,autostart,doctor, andwol.h264.rs,mp4.rs, andaudio_out.rs.Module Layout (matching PR feat(linux): Linux desktop & CLI implementation (stacked on #23) #12):
storage.rs,pc_audio_stream.rs,pc_mirror.rs, andplay_on_phone.rsas single files with internal#[cfg(windows)]and#[cfg(not(windows))]blocks.main.rsvia#[cfg(target_os = "macos")] #[path = "mac/mod.rs"] mod win;.Capability Negotiation:
mirror.viewfromBASE_CAPABILITIESinnectarlink-coreso non-decoding desktop builds can opt out.media.control,storage.mount,vcam, etc.) to#[cfg(windows)]incore_host.rs.os: std::env::consts::OS.into()("macos").core(macos_desktop_matrix_reflects_supported_and_unsupported_features) anddesktop(macos_advertises_only_supported_capabilities).Warnings & CI:
#![cfg_attr(not(windows), allow(unused))]acrossdesktopandspikecrates.desktop-macosGitHub Actions CI workflow with Qt 6.12.Documentation:
README.mdwith macOS build requirements and a comprehensive Platform Support matrix.Testing & Verification
cargo test -p nectarlink-protocol: 62 passed; 0 failedcargo test -p nectarlink-core --lib: 112 passed; 0 failedcargo test -p nectarlink-desktop: 148 passed; 0 failedcargo clippy --locked -p nectarlink-desktop --all-targets -- -D warnings: 0 warningscargo clippy --locked -p s1-qt-rust --all-targets -- -D warnings: 0 warningscargo fmt --all --check&cargo xtask strings --check: Cleangit diff --check: CleanSigned-off-by) adhering to DCO guidelines.