diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8f90f275..c161f1bc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,8 +31,9 @@ jobs: run: flutter test # ── macOS ────────────────────────────────────────────────────────────────── - # Apple Silicon only (macos-latest = arm64). Intel (macos-13) dropped due - # to chronic runner unavailability and near-zero M-chip user base. + # One universal (arm64 + x86_64) build from the arm64 runner: Flutter builds + # both slices by default and build.sh lipos the Rust dylib, so Intel Macs are + # covered without depending on GitHub's flaky Intel runners. build-macos: needs: test runs-on: macos-latest @@ -56,9 +57,13 @@ jobs: working-directory: app run: flutter pub get + # --split-debug-info strips Dart symbols from the AOT snapshot into + # build/symbols (20-30% smaller binary). Crash stack traces become raw + # offsets — symbolicate with: flutter symbolize -d . + # The per-release *-symbols.zip attached below is the only copy; keep it. - name: Build macOS working-directory: app - run: flutter build macos --release + run: flutter build macos --release --split-debug-info=build/symbols - name: Bundle RDP library into app run: | @@ -67,6 +72,20 @@ jobs: cp packages/yourssh_rdp/assets/native/macos/libyourssh_rdp.dylib \ "$APP/Contents/Frameworks/" + # Fail fast if either the app or the Rust dylib lost an architecture — + # an arm64-only artifact named "universal" would brick Intel installs. + - name: Verify universal binaries + run: | + APP=$(find "app/build/macos/Build/Products/Release" -name "*.app" -maxdepth 1 | head -1) + for BIN in "$APP/Contents/MacOS/"* "$APP/Contents/Frameworks/libyourssh_rdp.dylib"; do + ARCHS=$(lipo -archs "$BIN") + echo "$BIN: $ARCHS" + case "$ARCHS" in + *arm64*x86_64*|*x86_64*arm64*) ;; + *) echo "::error::$BIN is not universal ($ARCHS)"; exit 1 ;; + esac + done + - name: Extract version id: version run: | @@ -76,7 +95,12 @@ jobs: - name: Create ZIP run: | APP=$(find "app/build/macos/Build/Products/Release" -name "*.app" -maxdepth 1 | head -1) - zip -r "YourSSH-${{ steps.version.outputs.version }}-macOS-arm64.zip" "$APP" + zip -r "YourSSH-${{ steps.version.outputs.version }}-macOS-universal.zip" "$APP" + + - name: Zip debug symbols + run: | + cd app/build/symbols + zip -r "$GITHUB_WORKSPACE/YourSSH-${{ steps.version.outputs.version }}-macOS-universal-symbols.zip" . - name: Install create-dmg run: brew install create-dmg @@ -89,15 +113,16 @@ jobs: --window-size 600 400 \ --icon-size 128 \ --app-drop-link 450 185 \ - "YourSSH-${{ steps.version.outputs.version }}-macOS-arm64.dmg" \ + "YourSSH-${{ steps.version.outputs.version }}-macOS-universal.dmg" \ "$APP" - uses: actions/upload-artifact@v4 with: - name: macos-arm64 + name: macos-universal path: | - YourSSH-${{ steps.version.outputs.version }}-macOS-arm64.zip - YourSSH-${{ steps.version.outputs.version }}-macOS-arm64.dmg + YourSSH-${{ steps.version.outputs.version }}-macOS-universal.zip + YourSSH-${{ steps.version.outputs.version }}-macOS-universal.dmg + YourSSH-${{ steps.version.outputs.version }}-macOS-universal-symbols.zip # ── Windows ──────────────────────────────────────────────────────────────── # x64 is the standard target. arm64 (Windows on ARM / Snapdragon) is @@ -144,9 +169,10 @@ jobs: working-directory: app run: flutter pub get + # Dart symbols split out for size — see the macOS build step comment. - name: Build Windows working-directory: app - run: flutter build windows --release + run: flutter build windows --release --split-debug-info=build/symbols - name: Bundle RDP library into release shell: pwsh @@ -188,6 +214,14 @@ jobs: Compress-Archive -Path "app\build\windows\$arch\runner\Release\*" ` -DestinationPath "YourSSH-${ver}-Windows-${arch}-portable.zip" + - name: Zip debug symbols + shell: pwsh + run: | + $ver = "${{ steps.version.outputs.version }}" + $arch = "${{ matrix.arch }}" + Compress-Archive -Path "app\build\symbols\*" ` + -DestinationPath "YourSSH-${ver}-Windows-${arch}-symbols.zip" + - name: Install Inno Setup (ARM64 runner only) if: matrix.arch == 'arm64' shell: pwsh @@ -225,6 +259,7 @@ jobs: path: | YourSSH-${{ steps.version.outputs.version }}-Windows-${{ matrix.arch }}-portable.zip YourSSH.Setup.${{ steps.version.outputs.version }}-Windows-${{ matrix.arch }}.exe + YourSSH-${{ steps.version.outputs.version }}-Windows-${{ matrix.arch }}-symbols.zip # ── Linux ────────────────────────────────────────────────────────────────── # Produces a .deb package and a portable tar.gz for both x86_64 and arm64. @@ -281,9 +316,10 @@ jobs: working-directory: app run: flutter pub get + # Dart symbols split out for size — see the macOS build step comment. - name: Build Linux working-directory: app - run: flutter build linux --release + run: flutter build linux --release --split-debug-info=build/symbols - name: Extract version id: version @@ -347,12 +383,18 @@ jobs: tar -czf "YourSSH-${VERSION}-Linux-${ARCH}.tar.gz" \ -C "$(dirname "$BUNDLE")" "$(basename "$BUNDLE")" + - name: Zip debug symbols + run: | + cd app/build/symbols + zip -r "$GITHUB_WORKSPACE/YourSSH-${{ steps.version.outputs.version }}-Linux-${{ matrix.arch }}-symbols.zip" . + - uses: actions/upload-artifact@v4 with: name: linux-${{ matrix.arch }} path: | yourssh_${{ steps.version.outputs.version }}_${{ matrix.deb_arch }}.deb YourSSH-${{ steps.version.outputs.version }}-Linux-${{ matrix.arch }}.tar.gz + YourSSH-${{ steps.version.outputs.version }}-Linux-${{ matrix.arch }}-symbols.zip # ── GitHub Release ───────────────────────────────────────────────────────── release: @@ -389,8 +431,8 @@ jobs: generate_release_notes: true fail_on_unmatched_files: false files: | - YourSSH-${{ steps.version.outputs.version }}-macOS-arm64.zip - YourSSH-${{ steps.version.outputs.version }}-macOS-arm64.dmg + YourSSH-${{ steps.version.outputs.version }}-macOS-universal.zip + YourSSH-${{ steps.version.outputs.version }}-macOS-universal.dmg YourSSH-${{ steps.version.outputs.version }}-Windows-x64-portable.zip YourSSH.Setup.${{ steps.version.outputs.version }}-Windows-x64.exe YourSSH-${{ steps.version.outputs.version }}-Windows-arm64-portable.zip @@ -399,3 +441,4 @@ jobs: YourSSH-${{ steps.version.outputs.version }}-Linux-x86_64.tar.gz yourssh_${{ steps.version.outputs.version }}_arm64.deb YourSSH-${{ steps.version.outputs.version }}-Linux-arm64.tar.gz + YourSSH-${{ steps.version.outputs.version }}-*-symbols.zip diff --git a/CLAUDE.md b/CLAUDE.md index 9bdb98b0..f24832d8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -125,7 +125,7 @@ Flutter UI (widgets/screens) - `RecordingService` — writes asciicast v2 (`.cast`) files; tracks active recordings keyed by `sessionId`; passive intercept pattern — `SshService` always calls `writeOutput()` / `onShellClosed()`, which no-op when not recording; when `redact:` is on (effective = `SettingsProvider.recordingRedactionEnabled` AND `Host.recordingRedaction`, both default true; sampled once at start via `RecordingProvider.redactionPolicy` wired in main.dart with a fresh `HostProvider` lookup — the session's Host snapshot goes stale after a panel edit), output is line-buffered (split at the last newline, start-once `flushDelay` timer, default 500 ms, stop flushes the tail) and passed through `AuditRedactor.redact()` before writing — coalesces events per line, which also strips keystroke timing; ANSI escapes inside a secret and a secret straddling a flushDelay boundary defeat the regexes (defense-in-depth, not a guarantee) - `ShellIntegrationService` — pure (no Flutter/IO): `parseOsc(code, args)` maps xterm `onPrivateOSC` to a typed `ShellOscEvent` (OSC 7 cwd, OSC 133 A/D; C ignored); `buildInjectionScript()` is the guarded one-line bash/zsh prompt-hook installer (auto-on, opt-out via `Host.shellIntegration` + `SettingsProvider.shellIntegrationEnabled`), delivered **invisibly** via a two-phase handshake: `buildBootstrapLine()` (short line that disables tty echo and blocks in `read -rs`, printing `__YS_RDY__`/`__YS_DONE__` sentinels) + `buildPayloadLine({includeInstaller, workingDir, envVars})` (the installer plus the per-host session-template setup — `cd -- ''` + `export K='v'`, single-quote-escaped via `shQuote`, keys checked by `isValidEnvKey`; a failing cd prints a warning placed *after* the DONE sentinel so it survives the gate discard; consumed by `read` so never echoed). `SshService.openShell` wires `terminal.onPrivateOSC` and injects only when `injection_gate.dart`'s `InjectionReadiness` confirms the line editor is reading (bracketed-paste `ESC[?2004h` toggle + settle, bare-`\n` probe fallback for bash ≤ 5.0; skipped on alt-screen/user typing/never-confirmed) while `InjectionGate` withholds and discards the bootstrap echo; `path_completion.dart` (pure) plans cwd-aware path completion for the input bar over `SshService.listDirectory`. Design: `docs/superpowers/specs/2026-06-03-invisible-shell-integration-design.md` - `AuditService` / `AuditRedactor` — local SQLite audit trail (`sqlite3` + `sqlite3_flutter_libs`, WAL, `/audit.db`): `connect`/`disconnect`/`exec`/`input` events with denormalized host fields; commands pass `AuditRedactor` (pure regex masking: `key=value` secrets incl. prefixed `PGPASSWORD=`, Bearer tokens, `sshpass -p`, mysql/mariadb attached `-p`, URL userinfo — psql `-p` is the port, deliberately excluded) **before** insert; every write fail-soft (never breaks SSH ops); `SshService.exec` takes `auditSource` (`'app'` default; `'bulk'`/`'devops'`/`'plugin:'`/`'plugin:js'` threaded by callers; `null` = skip — used by the network-stats poll so it can't flood the log); connect failures logged only when no retry is scheduled; retention pruned at startup (`auditRetentionDays`, default 90, 0 = forever); CSV/JSON export of the filtered view -- `UpdateService` — in-app update glue: `fetchLatestRelease()` (GitHub `releases/latest`, stable-only), pure `isNewerVersion` (semver, fail-closed on blank) and `assetForPlatform` (OS/arch → release asset; macOS arm64-only → null on Intel), `downloadAsset` (streamed to Downloads with progress, cleans up partial file), `launchInstaller` (macOS: strip `com.apple.quarantine` + `open` DMG; Windows: run installer `.exe`; Linux: `xdg-open`); throws typed `UpdateException`; takes an injectable `http.Client` for testing +- `UpdateService` — in-app update glue: `fetchLatestRelease()` (GitHub `releases/latest`, stable-only), pure `isNewerVersion` (semver, fail-closed on blank) and `assetForPlatform` (OS/arch → release asset; macOS ships a universal DMG — Intel only matches it, falling back to the browser on pre-universal releases), `downloadAsset` (streamed to Downloads with progress, cleans up partial file), `launchInstaller` (macOS: strip `com.apple.quarantine` + `open` DMG; Windows: run installer `.exe`; Linux: `xdg-open`); throws typed `UpdateException`; takes an injectable `http.Client` for testing **Utils** (`app/lib/util/`): - `file_mode.dart` — POSIX mode helpers: `modeToOctal` / `parseOctal` (3–4 octal digits only — shorter is a partially-typed mode and never parses), the 9 `kMode*` permission-bit constants used by `PermissionsDialog`'s rwx grid, and `chmodLocal` (system `chmod`, macOS/Linux; callers hide the menu item on Windows) diff --git a/README.md b/README.md index 8acd5436..8dde87cc 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ YourSSH also checks for new releases on launch and from **Settings → Updates** | Platform | File | |---|---| -| macOS (Apple Silicon) | `YourSSH-x.x.x-macOS-arm64.dmg` | +| macOS (Apple Silicon & Intel) | `YourSSH-x.x.x-macOS-universal.dmg` | | Windows (x64) | `YourSSH.Setup.x.x.x-Windows-x64.exe` | | Windows (ARM64 — Surface, Snapdragon) | `YourSSH.Setup.x.x.x-Windows-arm64.exe` | | Linux (Debian/Ubuntu — x86_64) | `yourssh_x.x.x_amd64.deb` | diff --git a/app/assets/fonts/nerd/MesloLGS NF Bold Italic.ttf b/app/assets/fonts/nerd/MesloLGS NF Bold Italic.ttf deleted file mode 100644 index 9548f2ba..00000000 Binary files a/app/assets/fonts/nerd/MesloLGS NF Bold Italic.ttf and /dev/null differ diff --git a/app/assets/fonts/nerd/MesloLGS NF Italic.ttf b/app/assets/fonts/nerd/MesloLGS NF Italic.ttf deleted file mode 100644 index 95c2cada..00000000 Binary files a/app/assets/fonts/nerd/MesloLGS NF Italic.ttf and /dev/null differ diff --git a/app/lib/providers/session_provider.dart b/app/lib/providers/session_provider.dart index 0536be4d..3bcd7f7a 100644 --- a/app/lib/providers/session_provider.dart +++ b/app/lib/providers/session_provider.dart @@ -136,6 +136,7 @@ class SessionProvider extends ChangeNotifier { } void setActive(String sessionId) { + if (_activeSessionId == sessionId) return; // re-clicking the active tab _activeSessionId = sessionId; _safeNotify(); } diff --git a/app/lib/providers/settings_provider.dart b/app/lib/providers/settings_provider.dart index b0255138..5e7374cf 100644 --- a/app/lib/providers/settings_provider.dart +++ b/app/lib/providers/settings_provider.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:path/path.dart' as p; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:xterm/xterm.dart' as xterm; import '../models/keyword_highlight_rule.dart'; import '../models/shell_profile.dart'; @@ -54,6 +55,32 @@ class SettingsProvider extends ChangeNotifier { bool keywordHighlightingEnabled = true; List keywordHighlightRules = kDefaultKeywordHighlightRules; + List? _xtermRulesCache; + List? _xtermRulesSource; + bool _xtermRulesEnabled = false; + + /// The enabled rules compiled for xterm, shared by every terminal surface. + /// Memoized: [keywordHighlightRules] is only ever replaced wholesale (load + /// and updateSettings assign a new list, never mutate), so list identity + + /// the enabled flag fully key the cache — without it each terminal build + /// recompiled every rule's RegExp. The stable list identity also lets + /// rebuild diffing treat the rules as unchanged. + List get xtermKeywordRules { + if (!identical(_xtermRulesSource, keywordHighlightRules) || + _xtermRulesEnabled != keywordHighlightingEnabled) { + _xtermRulesSource = keywordHighlightRules; + _xtermRulesEnabled = keywordHighlightingEnabled; + _xtermRulesCache = keywordHighlightingEnabled + ? keywordHighlightRules + .where((r) => r.enabled) + .map((r) => r.toXtermRule()) + .whereType() + .toList(growable: false) + : const []; + } + return _xtermRulesCache!; + } + List get allShellProfiles => [...detectedShellProfiles, ...customShellProfiles]; diff --git a/app/lib/services/system_agent_proxy.dart b/app/lib/services/system_agent_proxy.dart index 3f49a3fb..715880b2 100644 --- a/app/lib/services/system_agent_proxy.dart +++ b/app/lib/services/system_agent_proxy.dart @@ -366,9 +366,10 @@ class _AgentWriter { Uint8List buildMessage() { final body = _buf.toBytes(); - final header = Uint8List(4); - ByteData.view(header.buffer).setUint32(0, body.length, Endian.big); - return Uint8List.fromList([...header, ...body]); + final message = Uint8List(4 + body.length); + ByteData.view(message.buffer).setUint32(0, body.length, Endian.big); + message.setRange(4, message.length, body); + return message; } } diff --git a/app/lib/services/update_service.dart b/app/lib/services/update_service.dart index 3962cf0c..f5540da9 100644 --- a/app/lib/services/update_service.dart +++ b/app/lib/services/update_service.dart @@ -64,9 +64,10 @@ class UpdateService { /// Picks the best matching asset for [os] (`macos`/`windows`/`linux`) and /// [arch] (`arm64`/`x64`/`amd64`). Returns null when no artifact matches - /// (e.g. macOS x64 — only arm64 is shipped); callers then fall back to the - /// browser. For each platform the candidate names are tried in preference - /// order and the first asset whose name matches is returned. + /// (e.g. macOS x64 against a pre-universal release that only shipped + /// arm64); callers then fall back to the browser. For each platform the + /// candidate names are tried in preference order and the first asset whose + /// name matches is returned. ReleaseAsset? assetForPlatform( AppRelease release, { required String os, @@ -75,7 +76,12 @@ class UpdateService { List candidates() { switch (os) { case 'macos': - return arch == 'arm64' ? const ['macOS-arm64.dmg'] : const []; + // Universal DMG serves both archs; arm64 also accepts the + // arm64-only name older releases shipped. Intel must not — an + // arm64-only DMG would install but never launch there. + return arch == 'arm64' + ? const ['macOS-universal.dmg', 'macOS-arm64.dmg'] + : const ['macOS-universal.dmg']; case 'linux': return arch == 'arm64' ? const ['_arm64.deb', 'Linux-arm64.tar.gz'] @@ -137,13 +143,13 @@ class UpdateService { } /// CPU architecture token used by [assetForPlatform]. - /// macOS only ships arm64; Windows reads PROCESSOR_ARCHITECTURE; Linux - /// shells out to `uname -m`. + /// macOS shells out to `uname -m`; Windows reads PROCESSOR_ARCHITECTURE; + /// Linux shells out to `uname -m`. String currentArch() { if (Platform.isMacOS) { - // Only arm64 artifacts are published. Detect the real arch so Intel Macs - // return 'x64' -> assetForPlatform returns null -> caller falls back to - // the browser, rather than being handed an arm64-only DMG. + // Detect the real arch so Intel Macs only accept the universal DMG — + // against a pre-universal (arm64-only) release they fall back to the + // browser rather than being handed a DMG that can't launch. try { final m = Process.runSync('uname', const ['-m']).stdout.toString().trim(); return (m == 'arm64' || m == 'aarch64') ? 'arm64' : 'x64'; diff --git a/app/lib/widgets/local_terminal_pane.dart b/app/lib/widgets/local_terminal_pane.dart index d8cb34d1..ef988821 100644 --- a/app/lib/widgets/local_terminal_pane.dart +++ b/app/lib/widgets/local_terminal_pane.dart @@ -47,13 +47,7 @@ class _LocalTerminalPaneState extends State { Widget _terminal(BuildContext context) { final settings = context.watch(); - final keywordRules = settings.keywordHighlightingEnabled - ? settings.keywordHighlightRules - .where((r) => r.enabled) - .map((r) => r.toXtermRule()) - .whereType() - .toList() - : const []; + final keywordRules = settings.xtermKeywordRules; return Stack( children: [ TerminalView( diff --git a/app/lib/widgets/recording_player_widget.dart b/app/lib/widgets/recording_player_widget.dart index ee07e165..1dc8b904 100644 --- a/app/lib/widgets/recording_player_widget.dart +++ b/app/lib/widgets/recording_player_widget.dart @@ -134,13 +134,7 @@ class _RecordingPlayerWidgetState extends State { final settings = context.watch(); final theme = terminalThemeByName(settings.terminalTheme); final progress = _events.isEmpty ? 0.0 : _currentIndex / _events.length; - final keywordRules = settings.keywordHighlightingEnabled - ? settings.keywordHighlightRules - .where((r) => r.enabled) - .map((r) => r.toXtermRule()) - .whereType() - .toList() - : const []; + final keywordRules = settings.xtermKeywordRules; return Column( children: [ diff --git a/app/lib/widgets/terminal_view.dart b/app/lib/widgets/terminal_view.dart index 35e364f4..6211a186 100644 --- a/app/lib/widgets/terminal_view.dart +++ b/app/lib/widgets/terminal_view.dart @@ -430,13 +430,7 @@ class _TerminalWidgetState extends State<_TerminalWidget> { @override Widget build(BuildContext context) { final settings = context.watch(); - final keywordRules = settings.keywordHighlightingEnabled - ? settings.keywordHighlightRules - .where((r) => r.enabled) - .map((r) => r.toXtermRule()) - .whereType() - .toList() - : const []; + final keywordRules = settings.xtermKeywordRules; final appearance = _appearance(watch: true); final theme = terminalThemeByName(appearance.themeName); final showGutter = settings.shellIntegrationEnabled && diff --git a/app/macos/Flutter/GeneratedPluginRegistrant.swift b/app/macos/Flutter/GeneratedPluginRegistrant.swift index 69657688..e64b7a2f 100644 --- a/app/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -10,7 +10,6 @@ import file_picker import file_selector_macos import flutter_secure_storage_macos import hotkey_manager_macos -import local_auth_darwin import local_notifier import network_info_plus import package_info_plus @@ -27,7 +26,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin")) - LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin")) LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin")) NetworkInfoPlusPlugin.register(with: registry.registrar(forPlugin: "NetworkInfoPlusPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) diff --git a/app/pubspec.lock b/app/pubspec.lock index 6daca5d4..5ee1f00c 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -493,14 +493,6 @@ packages: description: flutter source: sdk version: "0.0.0" - intl: - dependency: transitive - description: - name: intl - sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" - url: "https://pub.dev" - source: hosted - version: "0.20.2" jni: dependency: transitive description: @@ -573,46 +565,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.0" - local_auth: - dependency: "direct main" - description: - name: local_auth - sha256: "434d854cf478f17f12ab29a76a02b3067f86a63a6d6c4eb8fbfdcfe4879c1b7b" - url: "https://pub.dev" - source: hosted - version: "2.3.0" - local_auth_android: - dependency: transitive - description: - name: local_auth_android - sha256: a0bdfcc0607050a26ef5b31d6b4b254581c3d3ce3c1816ab4d4f4a9173e84467 - url: "https://pub.dev" - source: hosted - version: "1.0.56" - local_auth_darwin: - dependency: transitive - description: - name: local_auth_darwin - sha256: "699873970067a40ef2f2c09b4c72eb1cfef64224ef041b3df9fdc5c4c1f91f49" - url: "https://pub.dev" - source: hosted - version: "1.6.1" - local_auth_platform_interface: - dependency: transitive - description: - name: local_auth_platform_interface - sha256: f98b8e388588583d3f781f6806e4f4c9f9e189d898d27f0c249b93a1973dd122 - url: "https://pub.dev" - source: hosted - version: "1.1.0" - local_auth_windows: - dependency: transitive - description: - name: local_auth_windows - sha256: bc4e66a29b0fdf751aafbec923b5bed7ad6ed3614875d8151afe2578520b2ab5 - url: "https://pub.dev" - source: hosted - version: "1.0.11" local_notifier: dependency: "direct main" description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index d66bc614..7c227adb 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -94,9 +94,6 @@ dependencies: # Markdown rendering (AI chat) flutter_markdown: ^0.7.1 - # Local authentication / biometrics (vault) - local_auth: ^2.3.0 - # HMAC/SHA hashing for AWS Signature V4 (S3 browser) crypto: ^3.0.3 @@ -171,13 +168,10 @@ flutter: - family: Roboto Mono for Powerline fonts: - asset: assets/fonts/powerline/Roboto Mono for Powerline.ttf + # Italic faces dropped (-4.8 MB): terminals rarely hit italic, and the + # engine falls back to a synthetic slant of the upright faces. - family: MesloLGS NF fonts: - asset: assets/fonts/nerd/MesloLGS NF Regular.ttf - asset: assets/fonts/nerd/MesloLGS NF Bold.ttf weight: 700 - - asset: assets/fonts/nerd/MesloLGS NF Italic.ttf - style: italic - - asset: assets/fonts/nerd/MesloLGS NF Bold Italic.ttf - weight: 700 - style: italic diff --git a/app/test/services/update_service_test.dart b/app/test/services/update_service_test.dart index 955701b6..d944cade 100644 --- a/app/test/services/update_service_test.dart +++ b/app/test/services/update_service_test.dart @@ -106,11 +106,33 @@ void main() { ], }); - test('macOS arm64 -> dmg', () { + AppRelease universalRelease() => AppRelease.fromJson({ + 'tag_name': 'v0.2.0', + 'assets': [ + {'name': 'YourSSH-0.2.0-macOS-universal.dmg', 'browser_download_url': 'u/macuni', 'size': 1}, + {'name': 'YourSSH-0.2.0-macOS-universal.zip', 'browser_download_url': 'u/maczip', 'size': 1}, + ], + }); + + test('macOS arm64 -> universal dmg', () { + expect( + svc + .assetForPlatform(universalRelease(), os: 'macos', arch: 'arm64')! + .name, + 'YourSSH-0.2.0-macOS-universal.dmg'); + }); + test('macOS x64 -> universal dmg', () { + expect( + svc + .assetForPlatform(universalRelease(), os: 'macos', arch: 'x64')! + .name, + 'YourSSH-0.2.0-macOS-universal.dmg'); + }); + test('macOS arm64 falls back to arm64 dmg on a pre-universal release', () { expect(svc.assetForPlatform(release(), os: 'macos', arch: 'arm64')!.name, 'YourSSH-0.2.0-macOS-arm64.dmg'); }); - test('macOS x64 -> null (no Intel artifact)', () { + test('macOS x64 -> null on a pre-universal (arm64-only) release', () { expect(svc.assetForPlatform(release(), os: 'macos', arch: 'x64'), isNull); }); test('Windows x64 prefers Setup installer over portable', () { diff --git a/app/windows/CMakeLists.txt b/app/windows/CMakeLists.txt index e9c63138..7b302484 100644 --- a/app/windows/CMakeLists.txt +++ b/app/windows/CMakeLists.txt @@ -32,6 +32,10 @@ set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") # Use Unicode for all projects. add_definitions(-DUNICODE -D_UNICODE) +# MSVC 14.50+ (VS 2026) hard-errors on (STL1011), +# which local_auth_windows still pulls in via C++/WinRT under C++17. +add_definitions(-D_SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS) + # Compilation settings that should be applied to most targets. # # Be cautious about adding new options here, as plugins use this function by diff --git a/app/windows/flutter/generated_plugin_registrant.cc b/app/windows/flutter/generated_plugin_registrant.cc index d44f501a..f0967f3c 100644 --- a/app/windows/flutter/generated_plugin_registrant.cc +++ b/app/windows/flutter/generated_plugin_registrant.cc @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -26,8 +25,6 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); HotkeyManagerWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("HotkeyManagerWindowsPluginCApi")); - LocalAuthPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("LocalAuthPlugin")); LocalNotifierPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("LocalNotifierPlugin")); ScreenRetrieverPluginRegisterWithRegistrar( diff --git a/app/windows/flutter/generated_plugins.cmake b/app/windows/flutter/generated_plugins.cmake index 87320052..5ddde140 100644 --- a/app/windows/flutter/generated_plugins.cmake +++ b/app/windows/flutter/generated_plugins.cmake @@ -7,7 +7,6 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_windows flutter_secure_storage_windows hotkey_manager_windows - local_auth_windows local_notifier screen_retriever sqlite3_flutter_libs diff --git a/packages/yourssh_rdp/build.sh b/packages/yourssh_rdp/build.sh index a4b1f31f..db7b5f8b 100755 --- a/packages/yourssh_rdp/build.sh +++ b/packages/yourssh_rdp/build.sh @@ -1,13 +1,22 @@ #!/usr/bin/env bash set -euo pipefail cd "$(dirname "$0")/rust" -cargo build --release -cd .. case "$(uname -s)" in Darwin) + # Universal dylib (Apple Silicon + Intel): build both targets and lipo + # them together so the one shipped .app runs on either architecture. + rustup target add aarch64-apple-darwin x86_64-apple-darwin + cargo build --release --target aarch64-apple-darwin + cargo build --release --target x86_64-apple-darwin + cd .. mkdir -p assets/native/macos - cp rust/target/release/libyourssh_rdp.dylib assets/native/macos/ ;; + lipo -create \ + rust/target/aarch64-apple-darwin/release/libyourssh_rdp.dylib \ + rust/target/x86_64-apple-darwin/release/libyourssh_rdp.dylib \ + -output assets/native/macos/libyourssh_rdp.dylib ;; Linux) + cargo build --release + cd .. mkdir -p assets/native/linux cp rust/target/release/libyourssh_rdp.so assets/native/linux/ ;; esac