diff --git a/README-macOS.md b/README-macOS.md index 8a74154..9f4e7d1 100644 --- a/README-macOS.md +++ b/README-macOS.md @@ -198,5 +198,8 @@ versions of FastSM: secondary action, keeping the media player in the background, and moving extra reply mentions to the end. - Advanced — how many pages of posts to fetch per load. +- Updates — choose Stable version releases or every new build from main, and + whether to check automatically at startup. Use Application > Check for Updates + to download the selected Mac disk image. - Confirmation — ask before boosting, unboosting, liking, unliking, clearing a timeline, blocking, unblocking, or deleting a post. diff --git a/apple/shared/AppState.swift b/apple/shared/AppState.swift index 176af96..3e3941e 100644 --- a/apple/shared/AppState.swift +++ b/apple/shared/AppState.swift @@ -472,12 +472,10 @@ final class AppState { } func deleteList(id: String) { client.send("delete_list", ["id": id]) } - // Updates. Mac checks the version-tagged "stable" channel (the commit-based - // "latest" channel needs an embedded build commit, which the Mac build lacks - // — same rationale as Android). - func checkForUpdate(silent: Bool) { - client.send("check_for_update", ["silent": silent, "branch": "stable"]) - } + // The core uses the saved channel; iOS does not invoke this update flow. + func checkForUpdate(silent: Bool) { + client.send("check_for_update", ["silent": silent]) + } func getClientFilter() { client.send("get_client_filter") } func setClientFilter(_ filter: [String: Any]) { client.send("set_client_filter", ["filter": filter]) } diff --git a/apple/shared/CoreEvents.swift b/apple/shared/CoreEvents.swift index 8c6f023..5edb5f5 100644 --- a/apple/shared/CoreEvents.swift +++ b/apple/shared/CoreEvents.swift @@ -494,28 +494,26 @@ struct AccountSettings: Decodable { } } -/// Result of a check_for_update. Mac uses the `dmg_url` (a .dmg release asset); -/// falls back to opening `download_url` if none. +/// Result of a check_for_update. Mac downloads the `dmg_url` asset. struct UpdateStatus: Decodable { var silent = false var available = false + var branch = "stable" var version = "" var notes = "" - var downloadUrl = "" var dmgUrl = "" var error = "" enum CodingKeys: String, CodingKey { - case silent, available, version, notes, error - case downloadUrl = "download_url" + case silent, available, branch, version, notes, error case dmgUrl = "dmg_url" } init(from decoder: Decoder) throws { let c = try decoder.container(keyedBy: CodingKeys.self) silent = try c.decodeIfPresent(Bool.self, forKey: .silent) ?? false available = try c.decodeIfPresent(Bool.self, forKey: .available) ?? false + branch = try c.decodeIfPresent(String.self, forKey: .branch) ?? "stable" version = try c.decodeIfPresent(String.self, forKey: .version) ?? "" notes = try c.decodeIfPresent(String.self, forKey: .notes) ?? "" - downloadUrl = try c.decodeIfPresent(String.self, forKey: .downloadUrl) ?? "" dmgUrl = try c.decodeIfPresent(String.self, forKey: .dmgUrl) ?? "" error = try c.decodeIfPresent(String.self, forKey: .error) ?? "" } diff --git a/core/src/version.cpp b/core/src/version.cpp index 035958e..03b11e6 100644 --- a/core/src/version.cpp +++ b/core/src/version.cpp @@ -1,5 +1,9 @@ #include "fastsm/fastsm.hpp" +#if defined(__APPLE__) && __has_include("fastsm_build_commit.h") +#include "fastsm_build_commit.h" +#endif + namespace fastsm { const char* version() { diff --git a/docs/changelog.txt b/docs/changelog.txt index 6f004fc..4e428f7 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -4,6 +4,7 @@ FastSMRW changelog 0.6.0 ----- +- New: on the Mac, choose Stable version releases or every new build from main in Settings, Updates, and choose whether to check at startup. - Fixed: on iPhone, posts with multiple lines now display with their original line breaks. - New: on iPhone, choose whether the VoiceOver magic tap uses your secondary action, opens compose, or leaves the gesture to iOS; a magic tap set to View Media opens compose when a post has no playable media. - Fixed: on Mastodon, posting no longer fails with an error sound when you have many timelines open; background refreshing now slows down before it uses up your server's request limit. diff --git a/macos/build-core.sh b/macos/build-core.sh index 689d5d9..444fd4d 100755 --- a/macos/build-core.sh +++ b/macos/build-core.sh @@ -19,6 +19,10 @@ CORE=core/src CXXFLAGS=(-std=c++20 -fexceptions -frtti -O2 -g -I core/include -I deps -I deps/stb_vorbis -Wall -Wno-deprecated-declarations) +COMMIT=$(git rev-parse --short HEAD 2>/dev/null || true) +if [ -n "$COMMIT" ]; then + CXXFLAGS+=("-DFASTSM_BUILD_COMMIT=\"$COMMIT\"") +fi # Portable core sources (mirrors android/app/src/main/cpp/CMakeLists.txt), plus # the C-ABI factory. Windows-only winhttp_client is excluded; the Darwin diff --git a/macos/project.yml b/macos/project.yml index c43ecc1..eecbeac 100644 --- a/macos/project.yml +++ b/macos/project.yml @@ -23,6 +23,19 @@ targets: fastsm_core: type: library.static platform: macOS + preBuildScripts: + - name: Embed build commit + basedOnDependencyAnalysis: false + script: | + COMMIT=$(/usr/bin/git -C "$SRCROOT/.." rev-parse --short HEAD 2>/dev/null || true) + HEADER="$DERIVED_FILE_DIR/fastsm_build_commit.h" + mkdir -p "$DERIVED_FILE_DIR" + printf '#define FASTSM_BUILD_COMMIT "%s"\n' "$COMMIT" > "$HEADER.tmp" + if ! cmp -s "$HEADER.tmp" "$HEADER"; then + mv "$HEADER.tmp" "$HEADER" + else + rm "$HEADER.tmp" + fi sources: # The whole portable core, minus the Windows-only WinHTTP transport. This # is capi/fastsm_core.cpp + net/darwin_http_client.mm + everything the @@ -38,6 +51,7 @@ targets: - ../core/include - ../deps - ../deps/stb_vorbis + - $(DERIVED_FILE_DIR) # stb_vorbis.c is noisy; don't let its warnings clutter the build. GCC_WARN_UNUSED_FUNCTION: NO GCC_WARN_UNUSED_VARIABLE: NO diff --git a/macos/src/AppDelegate.swift b/macos/src/AppDelegate.swift index 839d216..f2c8a7d 100644 --- a/macos/src/AppDelegate.swift +++ b/macos/src/AppDelegate.swift @@ -108,25 +108,35 @@ final class AppDelegate: NSObject, NSApplicationDelegate { if !status.silent { let alert = NSAlert() alert.messageText = "You’re up to date." - alert.informativeText = "FastSMRW \(CoreClient.version) is the latest version." + alert.informativeText = status.branch == "latest" + ? "You’re running the latest build of FastSMRW." + : "FastSMRW \(CoreClient.version) is the latest version." alert.addButton(withTitle: "OK") present(alert, on: window) { _ in } } return } + guard !status.dmgUrl.isEmpty else { + if !status.silent { + ErrorAlert.present("Mac update isn't available yet.", + detail: "The selected release has no Mac download yet. Check again later.", + in: window) + } + return + } let alert = NSAlert() - alert.messageText = "Update available: \(status.version)" - alert.informativeText = status.notes.isEmpty ? "A new version of FastSMRW is available." + alert.messageText = status.branch == "latest" + ? "New build available: \(status.version)" + : "Update available: \(status.version)" + alert.informativeText = status.notes.isEmpty + ? (status.branch == "latest" ? "A new build of FastSMRW is available." + : "A new version of FastSMRW is available.") : status.notes alert.addButton(withTitle: "Download") alert.addButton(withTitle: "Later") present(alert, on: window) { [weak self] response in guard response == .alertFirstButtonReturn else { return } - if !status.dmgUrl.isEmpty { - self?.downloadAndOpenDMG(status.dmgUrl) - } else if let url = URL(string: status.downloadUrl) { - NSWorkspace.shared.open(url) // no Mac disk image; open the release page - } + self?.downloadAndOpenDMG(status.dmgUrl) } } diff --git a/macos/src/SettingsWindowController.swift b/macos/src/SettingsWindowController.swift index ef597ba..ccd107f 100644 --- a/macos/src/SettingsWindowController.swift +++ b/macos/src/SettingsWindowController.swift @@ -3,7 +3,7 @@ // // The preferences window: a toolbar-style tab view mirroring the Windows // settings pages (General, Timelines, Audio, Speech, Behavior, Advanced, -// Confirmation). Each control reads from the core's settings and writes back +// Updates, Confirmation). Each control reads from the core's settings and writes back // through update_settings, which the core applies live. // // Deferred: the per-category reorderable "Speech Details" field lists — those @@ -153,6 +153,17 @@ final class SettingsWindowController: NSWindowController { p.intRow("API pages per fetch:", key: "fetch_pages", default: 3, min: 1, max: 10) } + tab("Updates", "arrow.down.circle") { p in + let channels = [("Stable (version updates)", "stable"), + ("Latest (every build)", "latest")] + p.popup("Update channel:", options: channels, + key: "update_branch", default: "stable") + p.checkbox("Check for updates automatically at startup", + key: "check_updates_on_startup", default: true) + p.label("Stable offers versioned releases. Latest follows new builds from main.") + p.label("Use Application > Check for Updates to check now.") + } + tab("Confirmation", "checkmark.shield") { p in p.checkbox("Confirm boost", key: "confirm_boost", default: false) p.checkbox("Confirm un-boost", key: "confirm_unboost", default: false) @@ -342,6 +353,12 @@ final class SettingsPane: NSViewController { stack.addArrangedSubview(button) } + func label(_ text: String) { + let label = NSTextField(wrappingLabelWithString: text) + label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + stack.addArrangedSubview(label) + } + // MARK: Helpers private func currentValue(key: String, default def: Value) -> Value {