├── .github └── workflows │ ├── abi.yml │ └── build.yml ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── localsend_rs │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── launcher_icon.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets ├── i18n │ ├── strings.i18n.json │ └── strings_zh.i18n.json └── icon │ ├── logo.ico │ ├── logo_128.png │ ├── logo_256.png │ └── logo_512.png ├── build.yaml ├── cliff.toml ├── flutter_launcher_icons.yaml ├── flutter_rust_bridge.yaml ├── integration_test └── simple_test.dart ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h └── RunnerTests │ └── RunnerTests.swift ├── lib ├── common │ ├── constants.dart │ ├── device_info_utils.dart │ ├── string_extensions.dart │ └── utils.dart ├── core │ ├── listeners │ │ ├── core_listener.dart │ │ └── mission_listener.dart │ ├── providers │ │ ├── core_provider.dart │ │ ├── core_provider.g.dart │ │ ├── locale_provider.dart │ │ ├── locale_provider.freezed.dart │ │ ├── locale_provider.g.dart │ │ ├── mission_provider.dart │ │ ├── mission_provider.g.dart │ │ ├── theme_provider.dart │ │ └── theme_provider.g.dart │ ├── rust │ │ ├── actor │ │ │ ├── core.dart │ │ │ ├── mission.dart │ │ │ ├── mission.freezed.dart │ │ │ └── model.dart │ │ ├── api │ │ │ └── model.dart │ │ ├── bridge.dart │ │ ├── frb_generated.dart │ │ ├── frb_generated.io.dart │ │ └── logger.dart │ └── store │ │ └── config_store.dart ├── i18n │ └── strings.g.dart ├── main.dart └── view │ ├── pages │ ├── frame_page.dart │ ├── home_page.dart │ ├── mission_page.dart │ └── setting_page.dart │ └── widget │ ├── common_widget.dart │ ├── device_widget.dart │ ├── discover_widget.dart │ ├── mission_widget.dart │ ├── network_widget.dart │ └── setting_widgets.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── main.cc ├── my_application.cc └── my_application.h ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── app_icon_1024.png │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ └── app_icon_64.png │ ├── Base.lproj │ │ └── MainMenu.xib │ ├── Configs │ │ ├── AppInfo.xcconfig │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements └── RunnerTests │ └── RunnerTests.swift ├── pubspec.lock ├── pubspec.yaml ├── rust ├── .gitignore ├── Cargo.toml ├── build.cmd └── src │ ├── actor │ ├── core.rs │ ├── device.rs │ ├── discovery.rs │ ├── http.rs │ ├── mission.rs │ ├── mission │ │ ├── notify.rs │ │ ├── pending.rs │ │ └── transfer.rs │ ├── mod.rs │ └── model.rs │ ├── api │ ├── mod.rs │ ├── model.rs │ └── v2.rs │ ├── bridge.rs │ ├── frb_generated.io.rs │ ├── frb_generated.rs │ ├── frb_generated.web.rs │ ├── lib.rs │ ├── logger.rs │ └── util.rs ├── rust_builder ├── .gitignore ├── README.md ├── android │ ├── .gitignore │ ├── build.gradle │ ├── settings.gradle │ └── src │ │ └── main │ │ └── AndroidManifest.xml ├── cargokit │ ├── .gitignore │ ├── LICENSE │ ├── README │ ├── build_pod.sh │ ├── build_tool │ │ ├── README.md │ │ ├── analysis_options.yaml │ │ ├── bin │ │ │ └── build_tool.dart │ │ ├── lib │ │ │ ├── build_tool.dart │ │ │ └── src │ │ │ │ ├── android_environment.dart │ │ │ │ ├── artifacts_provider.dart │ │ │ │ ├── build_cmake.dart │ │ │ │ ├── build_gradle.dart │ │ │ │ ├── build_pod.dart │ │ │ │ ├── build_tool.dart │ │ │ │ ├── builder.dart │ │ │ │ ├── cargo.dart │ │ │ │ ├── crate_hash.dart │ │ │ │ ├── environment.dart │ │ │ │ ├── logging.dart │ │ │ │ ├── options.dart │ │ │ │ ├── precompile_binaries.dart │ │ │ │ ├── rustup.dart │ │ │ │ ├── target.dart │ │ │ │ ├── util.dart │ │ │ │ └── verify_binaries.dart │ │ ├── pubspec.lock │ │ └── pubspec.yaml │ ├── cmake │ │ ├── cargokit.cmake │ │ └── resolve_symlinks.ps1 │ ├── gradle │ │ └── plugin.gradle │ ├── run_build_tool.cmd │ └── run_build_tool.sh ├── ios │ ├── Classes │ │ └── dummy_file.c │ └── rust_builder.podspec ├── linux │ └── CMakeLists.txt ├── macos │ ├── Classes │ │ └── dummy_file.c │ └── rust_builder.podspec ├── pubspec.yaml └── windows │ ├── .gitignore │ └── CMakeLists.txt ├── screenshots └── desktop.png ├── setup.dart ├── test_driver └── integration_test.dart └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake └── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources └── app_icon.ico ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h /.github/workflows/abi.yml: -------------------------------------------------------------------------------- 1 | name: Build Abi for Android 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | workflow_call: 7 | 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | defaults: 14 | run: 15 | working-directory: ./rust 16 | 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v2 20 | 21 | # Set up Android NDK and Cargo toolchain for cross-compiling to Android 22 | - name: Set up environment 23 | uses: actions-rs/toolchain@v1 24 | with: 25 | toolchain: stable 26 | 27 | # Download Android NDK for cross-compiling 28 | - name: Download Android NDK 29 | uses: nttld/setup-ndk@v1.5.0 30 | with: 31 | # Exact version to use 32 | ndk-version: r26d 33 | local-cache: true 34 | 35 | - name: Setup cargo ndk 36 | run: | 37 | cargo install cargo-ndk 38 | rustup target add aarch64-linux-android armv7-linux-androideabi 39 | rustup target list 40 | 41 | # Install Cargo target for Android 42 | - name: Build 43 | run: | 44 | mkdir output 45 | cargo ndk -o ./output -t arm64-v8a -t armeabi-v7a --manifest-path ./Cargo.toml build --release 46 | ls ./output 47 | 48 | # Upload the built artifacts as an artifact 49 | - name: Upload Artifacts 50 | uses: actions/upload-artifact@v4.3.3 51 | with: 52 | name: android-abi-arm 53 | path: rust/output 54 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | build-apk: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v4 16 | 17 | - name: Set up Flutter SDK 18 | uses: subosito/flutter-action@v2.16.0 19 | with: 20 | channel: stable 21 | cache: false 22 | 23 | - name: Set up rust 24 | uses: actions-rs/toolchain@v1 25 | with: 26 | toolchain: stable 27 | 28 | - name: Set up java 17 29 | uses: actions/setup-java@v4 30 | with: 31 | distribution: "zulu" # See 'Supported distributions' for available options 32 | java-version: "17" 33 | 34 | - name: Build Prepare 35 | run: | 36 | flutter pub get 37 | chmod +x ./rust_builder 38 | flutter doctor -v 39 | 40 | - name: Build APK 41 | run: | 42 | flutter build apk 43 | ls -alh ./build/app/outputs/flutter-apk/*.apk 44 | cp -r ./build/app/outputs/flutter-apk /tmp/flutter-apk 45 | 46 | # - name: Build Linux 47 | # run: | 48 | # flutter build linux --release 49 | # ls -alh ./build/linux/x64/release/bundle/* 50 | # cp -r ./build/linux/x64/release/bundle /tmp/linux-bundle 51 | 52 | - name: Upload Android 53 | uses: actions/upload-artifact@v4.3.3 54 | with: 55 | name: android-release 56 | path: /tmp/flutter-apk/*.apk 57 | 58 | build-windows: 59 | runs-on: windows-latest 60 | 61 | steps: 62 | - name: Checkout code 63 | uses: actions/checkout@v4 64 | 65 | - name: Set up Flutter SDK 66 | uses: subosito/flutter-action@v2.16.0 67 | with: 68 | channel: stable 69 | cache: true 70 | 71 | - name: Set up rust 72 | uses: actions-rs/toolchain@v1 73 | with: 74 | toolchain: stable 75 | 76 | - name: Build Prepare 77 | run: | 78 | flutter pub get 79 | flutter config --enable-native-assets 80 | 81 | - name: Build Windows 82 | run: | 83 | flutter build windows --release 84 | 85 | - name: Build iss script 86 | run: | 87 | dart setup.dart 88 | ls build\windows 89 | 90 | - name: Upload Windows 91 | uses: actions/upload-artifact@v4.3.3 92 | with: 93 | name: windows-release 94 | path: build\windows\localsend_rs-setup.exe 95 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | .vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Symbolication related 35 | app.*.symbols 36 | 37 | # Obfuscation related 38 | app.*.map.json 39 | 40 | # Android Studio will place build artifacts here 41 | /android/app/debug 42 | /android/app/profile 43 | /android/app/release 44 | 45 | /android/app/src/main/jniLibs -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "db7ef5bf9f59442b0e200a90587e8fa5e0c6336a" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 17 | base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 18 | - platform: android 19 | create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 20 | base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 21 | - platform: ios 22 | create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 23 | base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 24 | - platform: linux 25 | create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 26 | base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 27 | - platform: macos 28 | create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 29 | base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 30 | - platform: web 31 | create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 32 | base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 33 | - platform: windows 34 | create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 35 | base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## [unreleased] 6 | 7 | ### Bug Fixes 8 | 9 | - 🐞 fix: const widget not respond to locale change 10 | 11 | - 🐞 fix: locale loading issue 12 | 13 | 14 | ### Features 15 | 16 | - ✨feature: add adaptive navigation 17 | 18 | - ✨feature: add app icon 19 | 20 | - ✨feature: add app icon in app view 21 | 22 | 23 | ### Others 24 | 25 | - 🔵 other: delete deprecated action workflow 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # localsend_rs 2 | 3 | ![logo](./assets/icon/logo_128.png) 4 | 5 | [![Build](https://github.com/tom8zds/localsend_rs/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/tom8zds/localsend_rs/actions/workflows/build.yml) ![version](https://img.shields.io/badge/version-0.0.1_alpha-red) 6 | 7 | WIP: this repository is still WIP. 8 | 9 | A localsend protocol V2 implementation in flutter and rust for better performance. 10 | 11 | ## Screen shots 12 | 13 | ![logo](./screenshots/desktop.png) 14 | 15 | ## Performance 16 | 17 | Performance compare between localsend original and localsend_rs 18 | 19 | Test condition : 20 | 21 | - router: TpLink AX3000M 22 | - sender: Xiaomi 13 ( localsend ) 23 | - receiver: Windows PC ( localsend_rs / localsend ) 24 | 25 | | sender | receiver | network speed | disk speed | 26 | | --------- | ------------ | ------------- | ---------- | 27 | | localsend | localsend | 144Mbps | 26MB/s | 28 | | localsend | localsend_rs | 511Mbps | 102M/s | 29 | 30 | ## Roadmap 31 | 32 | - [ ] Protocol V2 33 | - [x] Udp announce 34 | - [x] Register 35 | - [x] Prepare upload 36 | - [x] Upload 37 | - [ ] Cancel 38 | - [ ] Send 39 | - [ ] User interface 40 | - [ ] discover page 41 | - [x] device list 42 | - [ ] device favorite 43 | - [x] receive page 44 | - [x] task progress 45 | - [ ] pic preview 46 | - [ ] mission progress 47 | - [ ] send page 48 | - [ ] setting page 49 | - [x] theme setting 50 | - [x] locale setting 51 | - [x] server setting 52 | - [x] start / stop 53 | - [ ] server config 54 | - [x] save directory 55 | - [ ] save pic to album 56 | - [ ] save to history 57 | - [x] Platform 58 | - [x] Windows 59 | - [x] Android 60 | - [ ] linux 61 | 62 | ## Getting Started 63 | 64 | This project is a starting point for a Flutter application. 65 | 66 | A few resources to get you started if this is your first Flutter project: 67 | 68 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) 69 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) 70 | 71 | For help getting started with Flutter development, view the 72 | [online documentation](https://docs.flutter.dev/), which offers tutorials, 73 | samples, guidance on mobile development, and a full API reference. 74 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | id "dev.flutter.flutter-gradle-plugin" 5 | } 6 | 7 | def localProperties = new Properties() 8 | def localPropertiesFile = rootProject.file('local.properties') 9 | if (localPropertiesFile.exists()) { 10 | localPropertiesFile.withReader('UTF-8') { reader -> 11 | localProperties.load(reader) 12 | } 13 | } 14 | 15 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 16 | if (flutterVersionCode == null) { 17 | flutterVersionCode = '1' 18 | } 19 | 20 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 21 | if (flutterVersionName == null) { 22 | flutterVersionName = '1.0' 23 | } 24 | 25 | android { 26 | namespace "github.tom8zds.localsend_rs" 27 | compileSdkVersion flutter.compileSdkVersion 28 | ndkVersion flutter.ndkVersion 29 | 30 | compileOptions { 31 | sourceCompatibility JavaVersion.VERSION_17 32 | targetCompatibility JavaVersion.VERSION_17 33 | } 34 | 35 | kotlinOptions { 36 | jvmTarget = '17' 37 | } 38 | 39 | sourceSets { 40 | main.java.srcDirs += 'src/main/kotlin' 41 | } 42 | 43 | defaultConfig { 44 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 45 | applicationId "github.tom8zds.localsend_rs" 46 | // You can update the following values to match your application needs. 47 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 48 | minSdkVersion flutter.minSdkVersion 49 | targetSdkVersion 35 50 | versionCode flutterVersionCode.toInteger() 51 | versionName flutterVersionName 52 | } 53 | 54 | buildTypes { 55 | release { 56 | // TODO: Add your own signing config for the release build. 57 | // Signing with the debug keys for now, so `flutter run --release` works. 58 | signingConfig signingConfigs.debug 59 | } 60 | } 61 | } 62 | 63 | flutter { 64 | source '../..' 65 | } 66 | 67 | dependencies {} 68 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 29 | 33 | 37 | 40 | 41 | 42 | 43 | 44 | 45 | 47 | 50 | 51 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/localsend_rs/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package github.tom8zds.localsend_rs 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/android/app/src/main/res/mipmap-hdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/android/app/src/main/res/mipmap-mdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '2.0.0' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 10 | } 11 | } 12 | 13 | allprojects { 14 | repositories { 15 | google() 16 | mavenCentral() 17 | } 18 | } 19 | 20 | rootProject.buildDir = '../build' 21 | subprojects { 22 | project.buildDir = "${rootProject.buildDir}/${project.name}" 23 | } 24 | subprojects { 25 | project.evaluationDependsOn(':app') 26 | } 27 | 28 | tasks.register("clean", Delete) { 29 | delete rootProject.buildDir 30 | } 31 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip 6 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | } 9 | settings.ext.flutterSdkPath = flutterSdkPath() 10 | 11 | includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") 12 | 13 | repositories { 14 | google() 15 | mavenCentral() 16 | gradlePluginPortal() 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false 21 | } 22 | } 23 | 24 | plugins { 25 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 26 | id "com.android.application" version "7.3.0" apply false 27 | } 28 | 29 | include ":app" 30 | -------------------------------------------------------------------------------- /assets/i18n/strings.i18n.json: -------------------------------------------------------------------------------- 1 | { 2 | "appTitle": { 3 | "parta": "LocalSend", 4 | "partb": "_RS" 5 | }, 6 | "home": { 7 | "title": "Home Page" 8 | }, 9 | "mission": { 10 | "accept": "Accept", 11 | "cancel": "Cancel", 12 | "complete": "Complete", 13 | "finished": "Finished", 14 | "tranfer": "Transfering", 15 | "pending": "Pending", 16 | "failed": "Failed", 17 | "skip": "Skip", 18 | "advance": "Advance" 19 | }, 20 | "common": { 21 | "file": "File", 22 | "size": "Size" 23 | }, 24 | "setting": { 25 | "title": "Settings", 26 | "common": "Common", 27 | "brightness": { 28 | "title": "Brightness", 29 | "subTitle": "Current mode: $mode", 30 | "themeMode": { 31 | "system": "Follow system", 32 | "light": "Light mode", 33 | "dark": "Dark mode" 34 | } 35 | }, 36 | "language": { 37 | "title": "Language", 38 | "subTitle": "Current language: $language" 39 | }, 40 | "receive": { 41 | "title": "Receive", 42 | "quickSave": "Quick Save", 43 | "quickSaveHint": "Start tranfer without accept", 44 | "saveFolder": "Save Folder", 45 | "selectSaveFolder": "Select" 46 | }, 47 | "core": { 48 | "title": "core setting", 49 | "server": { 50 | "title": "server" 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /assets/i18n/strings_zh.i18n.json: -------------------------------------------------------------------------------- 1 | { 2 | "appTitle": { 3 | "parta": "快传", 4 | "partb": "锈" 5 | }, 6 | "home": { 7 | "title": "主页" 8 | }, 9 | "mission": { 10 | "accept": "接收", 11 | "cancel": "取消", 12 | "complete": "完成", 13 | "finished": "已完成", 14 | "tranfer": "传输中", 15 | "pending": "等待中", 16 | "failed": "失败", 17 | "skip": "跳过", 18 | "advance": "高级" 19 | }, 20 | "common": { 21 | "file": "文件", 22 | "size": "大小" 23 | }, 24 | "setting": { 25 | "title": "设置", 26 | "common": "通用", 27 | "brightness": { 28 | "title": "明暗", 29 | "subTitle": "当前模式: $mode", 30 | "themeMode": { 31 | "system": "跟随系统", 32 | "light": "浅色模式", 33 | "dark": "深色模式" 34 | } 35 | }, 36 | "language": { 37 | "title": "语言", 38 | "subTitle": "当前语言: $language" 39 | }, 40 | "receive": { 41 | "title": "接收设置", 42 | "quickSave": "快速保存", 43 | "quickSaveHint": "不需要等待确认直接接受", 44 | "saveFolder": "保存目录", 45 | "selectSaveFolder": "选择" 46 | }, 47 | "core": { 48 | "title": "核心设置", 49 | "server": { 50 | "title": "服务器" 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /assets/icon/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/assets/icon/logo.ico -------------------------------------------------------------------------------- /assets/icon/logo_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/assets/icon/logo_128.png -------------------------------------------------------------------------------- /assets/icon/logo_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/assets/icon/logo_256.png -------------------------------------------------------------------------------- /assets/icon/logo_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/assets/icon/logo_512.png -------------------------------------------------------------------------------- /build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | slang_build_runner: 5 | options: 6 | input_directory: assets/i18n 7 | output_directory: lib/i18n # defaulting to lib/gen if input is outside of lib/ 8 | sources: 9 | exclude: 10 | # Example that excludes intellij's swap files 11 | - lib/rust/** -------------------------------------------------------------------------------- /cliff.toml: -------------------------------------------------------------------------------- 1 | # git-cliff ~ default configuration file 2 | # https://git-cliff.org/docs/configuration 3 | # 4 | # Lines starting with "#" are comments. 5 | # Configuration options are organized into tables and keys. 6 | # See documentation for more information on available options. 7 | 8 | [changelog] 9 | # changelog header 10 | header = """ 11 | # Changelog\n 12 | All notable changes to this project will be documented in this file.\n 13 | """ 14 | # template for the changelog body 15 | # https://keats.github.io/tera/docs/#introduction 16 | body = """ 17 | {% if version %}\ 18 | ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} 19 | {% else %}\ 20 | ## [unreleased] 21 | {% endif %}\ 22 | {% for group, commits in commits | group_by(attribute="group") %} 23 | ### {{ group | upper_first }} 24 | {% for commit in commits %} 25 | - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }}\ 26 | {% endfor %} 27 | {% endfor %}\n 28 | """ 29 | # remove the leading and trailing whitespace from the template 30 | trim = true 31 | # changelog footer 32 | footer = """ 33 | 34 | """ 35 | # postprocessors 36 | postprocessors = [ 37 | # { pattern = '', replace = "https://github.com/orhun/git-cliff" }, # replace repository URL 38 | ] 39 | [git] 40 | # parse the commits based on https://www.conventionalcommits.org 41 | conventional_commits = false 42 | # filter out the commits that are not conventional 43 | filter_unconventional = false 44 | # process each line of a commit as an individual commit 45 | split_commits = false 46 | # regex for preprocessing the commit messages 47 | commit_preprocessors = [ 48 | # { pattern = '\((\w+\s)?#([0-9]+)\)', replace = "([#${2}](/issues/${2}))"}, # replace issue numbers 49 | ] 50 | # regex for parsing and grouping commits 51 | commit_parsers = [ 52 | { message = ".*feature:", group = "Features" }, 53 | { message = ".*fix:", group = "Bug Fixes" }, 54 | { message = ".*docs:", group = "Documentation" }, 55 | { message = ".*perf:", group = "Performance" }, 56 | { message = ".*other:", group = "Others" }, 57 | ] 58 | # protect breaking changes from being skipped due to matching a skipping commit_parser 59 | protect_breaking_commits = false 60 | # filter out the commits that are not matched by commit parsers 61 | filter_commits = false 62 | # regex for matching git tags 63 | tag_pattern = "v[0-9].*" 64 | 65 | # regex for skipping tags 66 | skip_tags = "v0.1.0-beta.1" 67 | # regex for ignoring tags 68 | ignore_tags = "" 69 | # sort the tags topologically 70 | topo_order = false 71 | # sort the commits inside sections by oldest/newest order 72 | sort_commits = "oldest" 73 | # limit the number of commits included in the changelog. 74 | # limit_commits = 42 75 | -------------------------------------------------------------------------------- /flutter_launcher_icons.yaml: -------------------------------------------------------------------------------- 1 | flutter_launcher_icons: 2 | android: "launcher_icon" 3 | ios: false 4 | image_path: "assets/icon/logo_512.png" 5 | min_sdk_android: 21 # android min sdk min:16, default 21 6 | web: 7 | generate: true 8 | image_path: "assets/icon/logo_128.png" 9 | windows: 10 | generate: true 11 | image_path: "assets/icon/logo_128.png" 12 | icon_size: 48 # min:48, max:256, default: 48 -------------------------------------------------------------------------------- /flutter_rust_bridge.yaml: -------------------------------------------------------------------------------- 1 | rust_input: crate::bridge 2 | rust_root: rust/ 3 | dart_output: lib/core/rust 4 | web: false -------------------------------------------------------------------------------- /integration_test/simple_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:integration_test/integration_test.dart'; 3 | import 'package:localsend_rs/core/rust/frb_generated.dart'; 4 | import 'package:localsend_rs/main.dart'; 5 | 6 | void main() { 7 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 8 | setUpAll(() async => await RustLib.init()); 9 | testWidgets('Can call rust function', (WidgetTester tester) async { 10 | await tester.pumpWidget(const MyApp()); 11 | expect(find.textContaining('Result: `Hello, Tom!`'), findsOneWidget); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Localsend Rs 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | localsend_rs 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /lib/common/constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Language { 4 | final String name; 5 | final String localeName; 6 | 7 | const Language(this.name, this.localeName); 8 | } 9 | 10 | const Map supportLanguages = { 11 | "zh": Language("中文", "zh"), 12 | "en": Language("English", "en"), 13 | }; 14 | 15 | enum LocaleMode { 16 | system, 17 | custom, 18 | } 19 | 20 | enum DeviceType { 21 | mobile, 22 | desktop, 23 | web, 24 | headless, 25 | server, 26 | } 27 | 28 | extension DeviceTypeExt on DeviceType { 29 | IconData get icon { 30 | return switch (this) { 31 | DeviceType.mobile => Icons.smartphone, 32 | DeviceType.desktop => Icons.computer, 33 | DeviceType.web => Icons.language, 34 | DeviceType.headless => Icons.terminal, 35 | DeviceType.server => Icons.dns, 36 | }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/common/string_extensions.dart: -------------------------------------------------------------------------------- 1 | // ignore: depend_on_referenced_packages 2 | import 'package:collection/collection.dart'; 3 | import 'package:slang/builder/model/enums.dart'; 4 | 5 | extension StringExtensions on String { 6 | /// capitalizes a given string 7 | /// 'hello' => 'Hello' 8 | /// 'Hello' => 'Hello' 9 | /// '' => '' 10 | String capitalize() { 11 | if (isEmpty) return ''; 12 | return '${this[0].toUpperCase()}${substring(1).toLowerCase()}'; 13 | } 14 | 15 | /// transforms the string to the specified case 16 | /// if case is null, then no transformation will be applied 17 | String toCase(CaseStyle? style) { 18 | switch (style) { 19 | case CaseStyle.camel: 20 | return getWords() 21 | .mapIndexed((index, word) => 22 | index == 0 ? word.toLowerCase() : word.capitalize()) 23 | .join(''); 24 | case CaseStyle.pascal: 25 | return getWords().map((word) => word.capitalize()).join(''); 26 | case CaseStyle.snake: 27 | return getWords().map((word) => word.toLowerCase()).join('_'); 28 | case null: 29 | return this; 30 | default: 31 | return this; 32 | } 33 | } 34 | 35 | /// de-DE will be interpreted as [de,DE] 36 | /// normally, it would be [de,D,E] which we do not want 37 | String toCaseOfLocale(CaseStyle style) { 38 | return toLowerCase().toCase(style); 39 | } 40 | 41 | /// get word list from string input 42 | /// assume that words are separated by special characters or by camel case 43 | List getWords() { 44 | final input = this; 45 | final StringBuffer buffer = StringBuffer(); 46 | final List words = []; 47 | final bool isAllCaps = input.toUpperCase() == input; 48 | 49 | for (int i = 0; i < input.length; i++) { 50 | final String currChar = input[i]; 51 | final String? nextChar = i + 1 == input.length ? null : input[i + 1]; 52 | 53 | if (_symbolSet.contains(currChar)) { 54 | continue; 55 | } 56 | 57 | buffer.write(currChar); 58 | 59 | final bool isEndOfWord = nextChar == null || 60 | (!isAllCaps && _upperAlphaRegex.hasMatch(nextChar)) || 61 | _symbolSet.contains(nextChar); 62 | 63 | if (isEndOfWord) { 64 | words.add(buffer.toString()); 65 | buffer.clear(); 66 | } 67 | } 68 | 69 | return words; 70 | } 71 | } 72 | 73 | final RegExp _upperAlphaRegex = RegExp(r'[A-Z]'); 74 | final Set _symbolSet = {' ', '.', '_', '-', '/', '\\'}; 75 | -------------------------------------------------------------------------------- /lib/core/listeners/core_listener.dart: -------------------------------------------------------------------------------- 1 | import 'package:localsend_rs/core/rust/actor/model.dart'; 2 | import 'package:localsend_rs/core/rust/bridge.dart'; 3 | 4 | class CoreListener { 5 | static CoreListener? _instance; 6 | CoreListener._(); 7 | 8 | static CoreListener instance() { 9 | _instance ??= CoreListener._(); 10 | return _instance!; 11 | } 12 | 13 | Stream stateStream = listenServerState(); 14 | Stream> deviceStream = listenDevice(); 15 | } 16 | -------------------------------------------------------------------------------- /lib/core/listeners/mission_listener.dart: -------------------------------------------------------------------------------- 1 | import 'package:localsend_rs/core/rust/bridge.dart'; 2 | 3 | import '../rust/actor/mission.dart'; 4 | 5 | class MissionListener { 6 | static MissionListener? _instance; 7 | MissionListener._(); 8 | 9 | static MissionListener instance() { 10 | _instance ??= MissionListener._(); 11 | return _instance!; 12 | } 13 | 14 | Stream stream = listenMission(); 15 | } 16 | -------------------------------------------------------------------------------- /lib/core/providers/core_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:localsend_rs/core/listeners/core_listener.dart'; 2 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 3 | 4 | import '../rust/actor/model.dart'; 5 | 6 | part 'core_provider.g.dart'; 7 | 8 | class RustCoreState { 9 | final bool serverState; 10 | final List devices; 11 | 12 | RustCoreState({required this.serverState, required this.devices}); 13 | 14 | RustCoreState copyWith({bool? serverState, List? devices}) => 15 | RustCoreState( 16 | serverState: serverState ?? this.serverState, 17 | devices: devices ?? this.devices, 18 | ); 19 | } 20 | 21 | @riverpod 22 | class CoreState extends _$CoreState { 23 | @override 24 | RustCoreState build() { 25 | final subServerState = CoreListener.instance().stateStream.listen( 26 | (event) { 27 | state = state.copyWith(serverState: event); 28 | }, 29 | ); 30 | final subDevice = CoreListener.instance().deviceStream.listen( 31 | (event) { 32 | state = state.copyWith(devices: event); 33 | }, 34 | ); 35 | ref.onDispose(() { 36 | subServerState.cancel(); 37 | subDevice.cancel(); 38 | }); 39 | return RustCoreState(serverState: false, devices: []); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/core/providers/core_provider.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'core_provider.dart'; 4 | 5 | // ************************************************************************** 6 | // RiverpodGenerator 7 | // ************************************************************************** 8 | 9 | String _$coreStateHash() => r'2a21926ad86879088cb49e04efbc32590a05746f'; 10 | 11 | /// See also [CoreState]. 12 | @ProviderFor(CoreState) 13 | final coreStateProvider = 14 | AutoDisposeNotifierProvider.internal( 15 | CoreState.new, 16 | name: r'coreStateProvider', 17 | debugGetCreateSourceHash: 18 | const bool.fromEnvironment('dart.vm.product') ? null : _$coreStateHash, 19 | dependencies: null, 20 | allTransitiveDependencies: null, 21 | ); 22 | 23 | typedef _$CoreState = AutoDisposeNotifier; 24 | // ignore_for_file: type=lint 25 | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member 26 | -------------------------------------------------------------------------------- /lib/core/providers/locale_provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:freezed_annotation/freezed_annotation.dart'; 5 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 6 | import 'package:window_manager/window_manager.dart'; 7 | 8 | import '../../common/constants.dart'; 9 | import '../../common/utils.dart'; 10 | import '../../i18n/strings.g.dart'; 11 | import '../store/config_store.dart'; 12 | 13 | part 'locale_provider.g.dart'; 14 | part 'locale_provider.freezed.dart'; 15 | 16 | @freezed 17 | class LocaleConfig with _$LocaleConfig { 18 | factory LocaleConfig({ 19 | required LocaleMode mode, 20 | required Locale customLocale, 21 | }) = _LocaleConfig; 22 | } 23 | 24 | extension LocaleConfigExt on LocaleConfig { 25 | Locale getLocale() { 26 | switch (mode) { 27 | case LocaleMode.system: 28 | return stringToLocale(Platform.localeName); 29 | case LocaleMode.custom: 30 | return customLocale; 31 | } 32 | } 33 | } 34 | 35 | @riverpod 36 | class LocaleState extends _$LocaleState { 37 | @override 38 | LocaleConfig build() { 39 | String localeValue = ConfigStore().locale(); 40 | return LocaleConfig( 41 | mode: LocaleMode.system, customLocale: stringToLocale(localeValue)); 42 | } 43 | 44 | void onModeChange() { 45 | if (state.mode == LocaleMode.system) { 46 | LocaleSettings.useDeviceLocale(); 47 | } else { 48 | LocaleSettings.setLocaleRaw(state.customLocale.languageCode); 49 | } 50 | if (Platform.isWindows) { 51 | windowManager.setTitle(t.appTitle.parta + t.appTitle.partb); 52 | } 53 | } 54 | 55 | void setLocale(Locale locale) { 56 | ConfigStore().updateLocale(state.toString()); 57 | state = state.copyWith(mode: LocaleMode.custom, customLocale: locale); 58 | onModeChange(); 59 | } 60 | 61 | LocaleMode getMode() { 62 | return state.mode; 63 | } 64 | 65 | void changeMode(LocaleMode mode) { 66 | state = state.copyWith(mode: mode); 67 | onModeChange(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/core/providers/locale_provider.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'locale_provider.dart'; 4 | 5 | // ************************************************************************** 6 | // RiverpodGenerator 7 | // ************************************************************************** 8 | 9 | String _$localeStateHash() => r'e639ad5930410f9b4d38fd1d65777d2836014665'; 10 | 11 | /// See also [LocaleState]. 12 | @ProviderFor(LocaleState) 13 | final localeStateProvider = 14 | AutoDisposeNotifierProvider.internal( 15 | LocaleState.new, 16 | name: r'localeStateProvider', 17 | debugGetCreateSourceHash: 18 | const bool.fromEnvironment('dart.vm.product') ? null : _$localeStateHash, 19 | dependencies: null, 20 | allTransitiveDependencies: null, 21 | ); 22 | 23 | typedef _$LocaleState = AutoDisposeNotifier; 24 | // ignore_for_file: type=lint 25 | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member 26 | -------------------------------------------------------------------------------- /lib/core/providers/mission_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 2 | 3 | import '../listeners/mission_listener.dart'; 4 | import '../rust/actor/mission.dart'; 5 | 6 | part 'mission_provider.g.dart'; 7 | 8 | @riverpod 9 | class CoreMission extends _$CoreMission { 10 | @override 11 | MissionInfo? build() { 12 | final subMission = MissionListener.instance().stream.listen( 13 | (event) { 14 | state = event; 15 | }, 16 | ); 17 | ref.onDispose(() { 18 | subMission.cancel(); 19 | }); 20 | return null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/core/providers/mission_provider.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'mission_provider.dart'; 4 | 5 | // ************************************************************************** 6 | // RiverpodGenerator 7 | // ************************************************************************** 8 | 9 | String _$coreMissionHash() => r'11026c14485ff442423627f646ecc515cb310c43'; 10 | 11 | /// See also [CoreMission]. 12 | @ProviderFor(CoreMission) 13 | final coreMissionProvider = 14 | AutoDisposeNotifierProvider.internal( 15 | CoreMission.new, 16 | name: r'coreMissionProvider', 17 | debugGetCreateSourceHash: 18 | const bool.fromEnvironment('dart.vm.product') ? null : _$coreMissionHash, 19 | dependencies: null, 20 | allTransitiveDependencies: null, 21 | ); 22 | 23 | typedef _$CoreMission = AutoDisposeNotifier; 24 | // ignore_for_file: type=lint 25 | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member 26 | -------------------------------------------------------------------------------- /lib/core/providers/theme_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:riverpod_annotation/riverpod_annotation.dart'; 3 | 4 | import '../store/config_store.dart'; 5 | 6 | part 'theme_provider.g.dart'; 7 | 8 | @riverpod 9 | class ThemeState extends _$ThemeState { 10 | @override 11 | ThemeMode build() { 12 | return ConfigStore().themeMode(); 13 | } 14 | 15 | ThemeMode getTheme() { 16 | return state; 17 | } 18 | 19 | void setTheme(ThemeMode mode) { 20 | state = mode; 21 | ConfigStore().updateThemeMode(state); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/core/providers/theme_provider.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'theme_provider.dart'; 4 | 5 | // ************************************************************************** 6 | // RiverpodGenerator 7 | // ************************************************************************** 8 | 9 | String _$themeStateHash() => r'6f0d061bc0306df79968b8a6a40a50220428ec9d'; 10 | 11 | /// See also [ThemeState]. 12 | @ProviderFor(ThemeState) 13 | final themeStateProvider = 14 | AutoDisposeNotifierProvider.internal( 15 | ThemeState.new, 16 | name: r'themeStateProvider', 17 | debugGetCreateSourceHash: 18 | const bool.fromEnvironment('dart.vm.product') ? null : _$themeStateHash, 19 | dependencies: null, 20 | allTransitiveDependencies: null, 21 | ); 22 | 23 | typedef _$ThemeState = AutoDisposeNotifier; 24 | // ignore_for_file: type=lint 25 | // ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member 26 | -------------------------------------------------------------------------------- /lib/core/rust/actor/core.dart: -------------------------------------------------------------------------------- 1 | // This file is automatically generated, so please do not edit it. 2 | // @generated by `flutter_rust_bridge`@ 2.4.0. 3 | 4 | // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import 5 | 6 | import '../frb_generated.dart'; 7 | import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; 8 | 9 | class CoreConfig { 10 | final int port; 11 | final String interfaceAddr; 12 | final String multicastAddr; 13 | final int multicastPort; 14 | final String storePath; 15 | 16 | const CoreConfig({ 17 | required this.port, 18 | required this.interfaceAddr, 19 | required this.multicastAddr, 20 | required this.multicastPort, 21 | required this.storePath, 22 | }); 23 | 24 | @override 25 | int get hashCode => 26 | port.hashCode ^ 27 | interfaceAddr.hashCode ^ 28 | multicastAddr.hashCode ^ 29 | multicastPort.hashCode ^ 30 | storePath.hashCode; 31 | 32 | @override 33 | bool operator ==(Object other) => 34 | identical(this, other) || 35 | other is CoreConfig && 36 | runtimeType == other.runtimeType && 37 | port == other.port && 38 | interfaceAddr == other.interfaceAddr && 39 | multicastAddr == other.multicastAddr && 40 | multicastPort == other.multicastPort && 41 | storePath == other.storePath; 42 | } 43 | -------------------------------------------------------------------------------- /lib/core/rust/actor/mission.dart: -------------------------------------------------------------------------------- 1 | // This file is automatically generated, so please do not edit it. 2 | // @generated by `flutter_rust_bridge`@ 2.4.0. 3 | 4 | // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import 5 | 6 | import '../api/model.dart'; 7 | import '../frb_generated.dart'; 8 | import 'model.dart'; 9 | import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; 10 | import 'package:freezed_annotation/freezed_annotation.dart' hide protected; 11 | part 'mission.freezed.dart'; 12 | 13 | @freezed 14 | sealed class FileState with _$FileState { 15 | const FileState._(); 16 | 17 | const factory FileState.pending() = FileState_Pending; 18 | const factory FileState.transfer() = FileState_Transfer; 19 | const factory FileState.finish() = FileState_Finish; 20 | const factory FileState.skip() = FileState_Skip; 21 | const factory FileState.fail({ 22 | required String msg, 23 | }) = FileState_Fail; 24 | } 25 | 26 | class MissionFileInfo { 27 | final FileInfo info; 28 | final FileState state; 29 | 30 | const MissionFileInfo({ 31 | required this.info, 32 | required this.state, 33 | }); 34 | 35 | @override 36 | int get hashCode => info.hashCode ^ state.hashCode; 37 | 38 | @override 39 | bool operator ==(Object other) => 40 | identical(this, other) || 41 | other is MissionFileInfo && 42 | runtimeType == other.runtimeType && 43 | info == other.info && 44 | state == other.state; 45 | } 46 | 47 | class MissionInfo { 48 | final String id; 49 | final NodeDevice sender; 50 | final List files; 51 | final MissionState state; 52 | 53 | const MissionInfo({ 54 | required this.id, 55 | required this.sender, 56 | required this.files, 57 | required this.state, 58 | }); 59 | 60 | @override 61 | int get hashCode => 62 | id.hashCode ^ sender.hashCode ^ files.hashCode ^ state.hashCode; 63 | 64 | @override 65 | bool operator ==(Object other) => 66 | identical(this, other) || 67 | other is MissionInfo && 68 | runtimeType == other.runtimeType && 69 | id == other.id && 70 | sender == other.sender && 71 | files == other.files && 72 | state == other.state; 73 | } 74 | -------------------------------------------------------------------------------- /lib/core/rust/actor/model.dart: -------------------------------------------------------------------------------- 1 | // This file is automatically generated, so please do not edit it. 2 | // @generated by `flutter_rust_bridge`@ 2.4.0. 3 | 4 | // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import 5 | 6 | import '../frb_generated.dart'; 7 | import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; 8 | 9 | enum MissionState { 10 | idle, 11 | pending, 12 | transfering, 13 | finished, 14 | failed, 15 | canceled, 16 | busy, 17 | ; 18 | } 19 | 20 | class NodeDevice { 21 | final String alias; 22 | final String version; 23 | final String deviceModel; 24 | final String deviceType; 25 | final String fingerprint; 26 | final String address; 27 | final int port; 28 | final String protocol; 29 | final bool download; 30 | final bool announcement; 31 | final bool announce; 32 | 33 | const NodeDevice({ 34 | required this.alias, 35 | required this.version, 36 | required this.deviceModel, 37 | required this.deviceType, 38 | required this.fingerprint, 39 | required this.address, 40 | required this.port, 41 | required this.protocol, 42 | required this.download, 43 | required this.announcement, 44 | required this.announce, 45 | }); 46 | 47 | @override 48 | int get hashCode => 49 | alias.hashCode ^ 50 | version.hashCode ^ 51 | deviceModel.hashCode ^ 52 | deviceType.hashCode ^ 53 | fingerprint.hashCode ^ 54 | address.hashCode ^ 55 | port.hashCode ^ 56 | protocol.hashCode ^ 57 | download.hashCode ^ 58 | announcement.hashCode ^ 59 | announce.hashCode; 60 | 61 | @override 62 | bool operator ==(Object other) => 63 | identical(this, other) || 64 | other is NodeDevice && 65 | runtimeType == other.runtimeType && 66 | alias == other.alias && 67 | version == other.version && 68 | deviceModel == other.deviceModel && 69 | deviceType == other.deviceType && 70 | fingerprint == other.fingerprint && 71 | address == other.address && 72 | port == other.port && 73 | protocol == other.protocol && 74 | download == other.download && 75 | announcement == other.announcement && 76 | announce == other.announce; 77 | } 78 | -------------------------------------------------------------------------------- /lib/core/rust/api/model.dart: -------------------------------------------------------------------------------- 1 | // This file is automatically generated, so please do not edit it. 2 | // @generated by `flutter_rust_bridge`@ 2.4.0. 3 | 4 | // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import 5 | 6 | import '../frb_generated.dart'; 7 | import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; 8 | 9 | class FileInfo { 10 | final String id; 11 | final String fileName; 12 | final PlatformInt64 size; 13 | final String fileType; 14 | final String? sha256; 15 | final Uint8List? preview; 16 | 17 | const FileInfo({ 18 | required this.id, 19 | required this.fileName, 20 | required this.size, 21 | required this.fileType, 22 | this.sha256, 23 | this.preview, 24 | }); 25 | 26 | @override 27 | int get hashCode => 28 | id.hashCode ^ 29 | fileName.hashCode ^ 30 | size.hashCode ^ 31 | fileType.hashCode ^ 32 | sha256.hashCode ^ 33 | preview.hashCode; 34 | 35 | @override 36 | bool operator ==(Object other) => 37 | identical(this, other) || 38 | other is FileInfo && 39 | runtimeType == other.runtimeType && 40 | id == other.id && 41 | fileName == other.fileName && 42 | size == other.size && 43 | fileType == other.fileType && 44 | sha256 == other.sha256 && 45 | preview == other.preview; 46 | } 47 | -------------------------------------------------------------------------------- /lib/core/rust/bridge.dart: -------------------------------------------------------------------------------- 1 | // This file is automatically generated, so please do not edit it. 2 | // @generated by `flutter_rust_bridge`@ 2.4.0. 3 | 4 | // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import 5 | 6 | import 'actor/core.dart'; 7 | import 'actor/mission.dart'; 8 | import 'actor/model.dart'; 9 | import 'api/model.dart'; 10 | import 'frb_generated.dart'; 11 | import 'logger.dart'; 12 | import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; 13 | 14 | // These functions are ignored because they are not marked as `pub`: `_get_core` 15 | // These types are ignored because they are not used by any `pub` functions: `CORE` 16 | // These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `deref`, `initialize` 17 | 18 | Future setup({required NodeDevice device, required CoreConfig config}) => 19 | RustLib.instance.api.crateBridgeSetup(device: device, config: config); 20 | 21 | Stream listenServerState() => 22 | RustLib.instance.api.crateBridgeListenServerState(); 23 | 24 | Future startServer() => RustLib.instance.api.crateBridgeStartServer(); 25 | 26 | Future shutdownServer() => 27 | RustLib.instance.api.crateBridgeShutdownServer(); 28 | 29 | Future restartServer() => RustLib.instance.api.crateBridgeRestartServer(); 30 | 31 | Future changePath({required String path}) => 32 | RustLib.instance.api.crateBridgeChangePath(path: path); 33 | 34 | Future changeConfig({required CoreConfig config}) => 35 | RustLib.instance.api.crateBridgeChangeConfig(config: config); 36 | 37 | Stream> listenDevice() => 38 | RustLib.instance.api.crateBridgeListenDevice(); 39 | 40 | Stream listenMission() => 41 | RustLib.instance.api.crateBridgeListenMission(); 42 | 43 | Stream listenTaskProgress() => 44 | RustLib.instance.api.crateBridgeListenTaskProgress(); 45 | 46 | Future clearMission() => RustLib.instance.api.crateBridgeClearMission(); 47 | 48 | Future cancelPending({required String id}) => 49 | RustLib.instance.api.crateBridgeCancelPending(id: id); 50 | 51 | Future acceptPending({required String id}) => 52 | RustLib.instance.api.crateBridgeAcceptPending(id: id); 53 | 54 | Stream createLogStream() => 55 | RustLib.instance.api.crateBridgeCreateLogStream(); 56 | 57 | Future announce() => RustLib.instance.api.crateBridgeAnnounce(); 58 | -------------------------------------------------------------------------------- /lib/core/rust/logger.dart: -------------------------------------------------------------------------------- 1 | // This file is automatically generated, so please do not edit it. 2 | // @generated by `flutter_rust_bridge`@ 2.4.0. 3 | 4 | // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import 5 | 6 | import 'frb_generated.dart'; 7 | import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; 8 | 9 | class LogEntry { 10 | final PlatformInt64 timeMillis; 11 | final int level; 12 | final String tag; 13 | final String msg; 14 | 15 | const LogEntry({ 16 | required this.timeMillis, 17 | required this.level, 18 | required this.tag, 19 | required this.msg, 20 | }); 21 | 22 | @override 23 | int get hashCode => 24 | timeMillis.hashCode ^ level.hashCode ^ tag.hashCode ^ msg.hashCode; 25 | 26 | @override 27 | bool operator ==(Object other) => 28 | identical(this, other) || 29 | other is LogEntry && 30 | runtimeType == other.runtimeType && 31 | timeMillis == other.timeMillis && 32 | level == other.level && 33 | tag == other.tag && 34 | msg == other.msg; 35 | } 36 | -------------------------------------------------------------------------------- /lib/core/store/config_store.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:shared_preferences/shared_preferences.dart'; 5 | import 'package:uuid/uuid.dart'; 6 | 7 | import '../../common/constants.dart'; 8 | 9 | class ConfigStore { 10 | static ConfigStore? _instance; 11 | static SharedPreferences? _prefs; 12 | 13 | factory ConfigStore() { 14 | if (_instance == null) { 15 | throw Exception('AppSharedPrefs is not initialized. ' 16 | 'Please call AppSharedPrefs.ensureInitialized before.'); 17 | } 18 | return _instance!; 19 | } 20 | 21 | const ConfigStore._(); 22 | 23 | static ensureInitialized() async { 24 | _prefs ??= await SharedPreferences.getInstance(); 25 | _instance ??= const ConfigStore._(); 26 | } 27 | 28 | static const _deviceIdKey = 'deviceId'; 29 | 30 | String deviceId() { 31 | final deviceId = _prefs!.getString(_deviceIdKey); 32 | if (deviceId == null) { 33 | final newId = const Uuid().v4(); 34 | _prefs!.setString(_deviceIdKey, newId); 35 | return newId; 36 | } else { 37 | return deviceId; 38 | } 39 | } 40 | 41 | static const _themeModeKey = 'themeMode'; 42 | 43 | ThemeMode themeMode() { 44 | final themeValue = _prefs!.getInt(_themeModeKey); 45 | if (themeValue == null) return ThemeMode.system; 46 | 47 | return ThemeMode.values[themeValue]; 48 | } 49 | 50 | Future updateThemeMode(ThemeMode theme) async { 51 | await _prefs!.setInt(_themeModeKey, theme.index); 52 | } 53 | 54 | static const _localeKey = 'locale'; 55 | 56 | String locale() { 57 | final localeValue = _prefs!.getString(_localeKey) ?? Platform.localeName; 58 | return localeValue; 59 | } 60 | 61 | Future updateLocale(String locale) async { 62 | await _prefs!.setString(_localeKey, locale); 63 | } 64 | 65 | static const _localeModeKey = 'localeMode'; 66 | 67 | LocaleMode localeMode() { 68 | final index = _prefs!.getInt(_localeModeKey) ?? 0; 69 | final localeMode = LocaleMode.values[index]; 70 | return localeMode; 71 | } 72 | 73 | Future updateLocaleMode(LocaleMode mode) async { 74 | await _prefs!.setInt(_localeKey, mode.index); 75 | } 76 | 77 | static const _storePathKey = 'storePath'; 78 | 79 | bool storePathSet() { 80 | return _prefs!.containsKey(_storePathKey); 81 | } 82 | 83 | String storePath() { 84 | final value = _prefs!.getString(_storePathKey) ?? "./"; 85 | return value; 86 | } 87 | 88 | Future setStorePath(String path) async { 89 | await _prefs!.setString(_storePathKey, path); 90 | } 91 | 92 | static const _quickSaveKey = 'quickSave'; 93 | 94 | bool quickSave() { 95 | final value = _prefs!.getBool(_quickSaveKey) ?? false; 96 | return value; 97 | } 98 | 99 | Future setQuickSave(bool value) async { 100 | await _prefs!.setBool(_quickSaveKey, value); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:chinese_font_library/chinese_font_library.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_localizations/flutter_localizations.dart'; 7 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 8 | import 'package:window_manager/window_manager.dart'; 9 | 10 | import 'common/device_info_utils.dart'; 11 | import 'common/utils.dart'; 12 | import 'core/providers/locale_provider.dart'; 13 | import 'core/providers/theme_provider.dart'; 14 | import 'core/rust/bridge.dart'; 15 | import 'core/rust/frb_generated.dart'; 16 | import 'core/store/config_store.dart'; 17 | import 'i18n/strings.g.dart'; 18 | import 'view/pages/frame_page.dart'; 19 | 20 | Future main() async { 21 | WidgetsFlutterBinding.ensureInitialized(); 22 | await RustLib.init(); 23 | initLogger(); 24 | 25 | await ConfigStore.ensureInitialized(); 26 | 27 | final device = await getDevice(); 28 | final config = await getConfig(device.port); 29 | await setup(device: device, config: config); 30 | 31 | if (Platform.isWindows) { 32 | // Must add this line. 33 | await windowManager.ensureInitialized(); 34 | 35 | WindowOptions windowOptions = const WindowOptions( 36 | size: Size(1080, 960), 37 | center: true, 38 | ); 39 | windowManager.waitUntilReadyToShow(windowOptions, () async { 40 | await windowManager.show(); 41 | await windowManager.focus(); 42 | }); 43 | } 44 | 45 | initLocale(); 46 | 47 | runApp(ProviderScope(child: TranslationProvider(child: const MyApp()))); 48 | } 49 | 50 | class MyApp extends ConsumerWidget { 51 | const MyApp({super.key}); 52 | 53 | ThemeData _buildTheme(Brightness brightness, Locale locale) { 54 | var baseTheme = ThemeData( 55 | useMaterial3: true, 56 | colorSchemeSeed: const Color(0xfff74c00), 57 | brightness: brightness); 58 | 59 | if (locale.languageCode == "zh") { 60 | return baseTheme.useSystemChineseFont(brightness); 61 | } 62 | return baseTheme; 63 | } 64 | 65 | @override 66 | Widget build(BuildContext context, WidgetRef ref) { 67 | final themeMode = ref.watch(themeStateProvider); 68 | final localeConfig = ref.watch(localeStateProvider); 69 | final locale = localeConfig.getLocale(); 70 | return MaterialApp( 71 | title: t.appTitle.parta + t.appTitle.partb, 72 | locale: locale, 73 | // use provider 74 | supportedLocales: AppLocaleUtils.supportedLocales, 75 | localizationsDelegates: GlobalMaterialLocalizations.delegates, 76 | theme: _buildTheme( 77 | Brightness.light, 78 | locale, 79 | ), 80 | darkTheme: _buildTheme( 81 | Brightness.dark, 82 | locale, 83 | ), 84 | themeMode: themeMode, 85 | home: const FramePage(), 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/view/pages/setting_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../i18n/strings.g.dart'; 4 | import '../widget/common_widget.dart'; 5 | import '../widget/setting_widgets.dart'; 6 | 7 | class SettingPage extends StatelessWidget { 8 | const SettingPage({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Scaffold( 13 | body: CustomScrollView( 14 | slivers: [ 15 | SliverToBoxAdapter( 16 | child: StaticAppbar(title: context.t.setting.title), 17 | ), 18 | SliverPadding( 19 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 20 | sliver: SliverList( 21 | delegate: SliverChildListDelegate( 22 | [ 23 | SettingTileGroup( 24 | title: t.setting.common, 25 | children: const [ 26 | ThemeTile(), // color tile 27 | // language tile 28 | LocaleTile(), 29 | ], 30 | ), 31 | const SizedBox( 32 | height: 16, 33 | ), 34 | SettingTileGroup( 35 | title: t.setting.receive.title, 36 | children: const [ 37 | QuickSaveWidget(), 38 | StorePathWIdget() 39 | ], 40 | ), 41 | const SizedBox( 42 | height: 16, 43 | ), 44 | SettingTileGroup( 45 | title: t.setting.core.title, 46 | children: const [ 47 | // core status 48 | ServerTile(), // core log 49 | ], 50 | ), 51 | const SizedBox( 52 | height: 16, 53 | ), 54 | SizedBox( 55 | height: 200, 56 | child: Image.asset("assets/icon/logo_512.png"), 57 | ), 58 | const Column( 59 | children: [ 60 | AppTitle(), 61 | SizedBox( 62 | height: 8, 63 | ), 64 | Text("Version: 1.0.0"), 65 | Text("by tom8zds @ github") 66 | ], 67 | ), 68 | const SizedBox( 69 | height: 32, 70 | ), 71 | ], 72 | ), 73 | ), 74 | ) 75 | ], 76 | ), 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/view/widget/common_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../i18n/strings.g.dart'; 4 | 5 | class AppTitle extends StatelessWidget { 6 | const AppTitle({super.key}); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return Text.rich( 11 | TextSpan( 12 | text: t.appTitle.parta, 13 | children: [ 14 | TextSpan( 15 | text: t.appTitle.partb, 16 | style: Theme.of(context).textTheme.displaySmall!.copyWith( 17 | color: const Color(0xfff74c00), 18 | fontWeight: FontWeight.bold, 19 | ), 20 | ), 21 | ], 22 | ), 23 | style: Theme.of(context).textTheme.displaySmall, 24 | ); 25 | } 26 | } 27 | 28 | class Tag extends StatelessWidget { 29 | final String title; 30 | 31 | const Tag({super.key, required this.title}); 32 | @override 33 | Widget build(BuildContext context) { 34 | return Padding( 35 | padding: const EdgeInsets.only(right: 8.0), 36 | child: Container( 37 | padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), 38 | decoration: BoxDecoration( 39 | borderRadius: BorderRadius.circular(6), 40 | color: Theme.of(context).colorScheme.primaryContainer, 41 | ), 42 | child: Text( 43 | title, 44 | style: TextStyle( 45 | color: Theme.of(context).colorScheme.primary, 46 | ), 47 | ), 48 | ), 49 | ); 50 | } 51 | } 52 | 53 | class StaticAppbar extends StatelessWidget { 54 | final String title; 55 | 56 | const StaticAppbar({super.key, required this.title}); 57 | @override 58 | Widget build(BuildContext context) { 59 | return SizedBox( 60 | height: kToolbarHeight, 61 | child: Center( 62 | child: Text( 63 | title, 64 | style: Theme.of(context).textTheme.titleLarge, 65 | ), 66 | ), 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/view/widget/discover_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_riverpod/flutter_riverpod.dart'; 3 | import 'package:localsend_rs/view/widget/device_widget.dart'; 4 | 5 | import '../../core/providers/core_provider.dart'; 6 | 7 | class DiscoverWidget extends ConsumerWidget { 8 | const DiscoverWidget({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context, WidgetRef ref) { 12 | final core = ref.watch(coreStateProvider); 13 | return Container( 14 | decoration: BoxDecoration( 15 | borderRadius: BorderRadius.circular(12), 16 | color: Theme.of(context).colorScheme.surface, 17 | ), 18 | child: Builder( 19 | builder: (context) { 20 | final data = core.devices; 21 | if (data.isEmpty) { 22 | return const Center( 23 | child: Text("empty"), 24 | ); 25 | } 26 | 27 | return ListView.builder( 28 | itemBuilder: (context, index) { 29 | final item = data.elementAt(index); 30 | return DeviceWidget(device: item); 31 | }, 32 | itemCount: data.length, 33 | ); 34 | }, 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/view/widget/network_widget.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class NetworkWidget extends StatefulWidget { 6 | final void Function(String addr) onPressed; 7 | 8 | const NetworkWidget({super.key, required this.onPressed}); 9 | 10 | @override 11 | State createState() => _NetworkWidgetState(); 12 | } 13 | 14 | class _NetworkWidgetState extends State { 15 | List addressList = []; 16 | 17 | Future getInterface() async { 18 | List addressList = []; 19 | List interfaces = await NetworkInterface.list(); 20 | for (var interface in interfaces) { 21 | for (var addr in interface.addresses) { 22 | if (addr.isLinkLocal || 23 | addr.isLoopback || 24 | addr.isMulticast || 25 | addr.type != InternetAddressType.IPv4) { 26 | continue; 27 | } 28 | addressList.add(addr.address); 29 | } 30 | } 31 | addressList.sort(); 32 | setState(() { 33 | this.addressList = addressList; 34 | }); 35 | } 36 | 37 | @override 38 | void initState() { 39 | getInterface(); 40 | super.initState(); 41 | } 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | return Container( 46 | child: Column( 47 | children: [ 48 | TextButton.icon( 49 | onPressed: getInterface, 50 | label: const Text("refresh"), 51 | icon: const Icon(Icons.refresh), 52 | ), 53 | for (final addr in addressList) 54 | TextButton( 55 | child: Text(addr), 56 | onPressed: () { 57 | widget.onPressed(addr); 58 | }) 59 | ], 60 | ), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.10) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | 12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 13 | # which isn't available in 3.10. 14 | function(list_prepend LIST_NAME PREFIX) 15 | set(NEW_LIST "") 16 | foreach(element ${${LIST_NAME}}) 17 | list(APPEND NEW_LIST "${PREFIX}${element}") 18 | endforeach(element) 19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 20 | endfunction() 21 | 22 | # === Flutter Library === 23 | # System-level dependencies. 24 | find_package(PkgConfig REQUIRED) 25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 28 | 29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 30 | 31 | # Published to parent scope for install step. 32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 36 | 37 | list(APPEND FLUTTER_LIBRARY_HEADERS 38 | "fl_basic_message_channel.h" 39 | "fl_binary_codec.h" 40 | "fl_binary_messenger.h" 41 | "fl_dart_project.h" 42 | "fl_engine.h" 43 | "fl_json_message_codec.h" 44 | "fl_json_method_codec.h" 45 | "fl_message_codec.h" 46 | "fl_method_call.h" 47 | "fl_method_channel.h" 48 | "fl_method_codec.h" 49 | "fl_method_response.h" 50 | "fl_plugin_registrar.h" 51 | "fl_plugin_registry.h" 52 | "fl_standard_message_codec.h" 53 | "fl_standard_method_codec.h" 54 | "fl_string_codec.h" 55 | "fl_value.h" 56 | "fl_view.h" 57 | "flutter_linux.h" 58 | ) 59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 60 | add_library(flutter INTERFACE) 61 | target_include_directories(flutter INTERFACE 62 | "${EPHEMERAL_DIR}" 63 | ) 64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 65 | target_link_libraries(flutter INTERFACE 66 | PkgConfig::GTK 67 | PkgConfig::GLIB 68 | PkgConfig::GIO 69 | ) 70 | add_dependencies(flutter flutter_assemble) 71 | 72 | # === Flutter tool backend === 73 | # _phony_ is a non-existent file to force this command to run every time, 74 | # since currently there's no way to get a full input/output list from the 75 | # flutter tool. 76 | add_custom_command( 77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 79 | COMMAND ${CMAKE_COMMAND} -E env 80 | ${FLUTTER_TOOL_ENVIRONMENT} 81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 83 | VERBATIM 84 | ) 85 | add_custom_target(flutter_assemble DEPENDS 86 | "${FLUTTER_LIBRARY}" 87 | ${FLUTTER_LIBRARY_HEADERS} 88 | ) 89 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | void fl_register_plugins(FlPluginRegistry* registry) { 14 | g_autoptr(FlPluginRegistrar) desktop_drop_registrar = 15 | fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin"); 16 | desktop_drop_plugin_register_with_registrar(desktop_drop_registrar); 17 | g_autoptr(FlPluginRegistrar) screen_retriever_registrar = 18 | fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin"); 19 | screen_retriever_plugin_register_with_registrar(screen_retriever_registrar); 20 | g_autoptr(FlPluginRegistrar) window_manager_registrar = 21 | fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin"); 22 | window_manager_plugin_register_with_registrar(window_manager_registrar); 23 | } 24 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | desktop_drop 7 | screen_retriever 8 | window_manager 9 | ) 10 | 11 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 12 | rust_builder 13 | ) 14 | 15 | set(PLUGIN_BUNDLED_LIBRARIES) 16 | 17 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 18 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 19 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 22 | endforeach(plugin) 23 | 24 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 25 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 27 | endforeach(ffi_plugin) 28 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import desktop_drop 9 | import device_info_plus 10 | import path_provider_foundation 11 | import screen_retriever 12 | import shared_preferences_foundation 13 | import window_manager 14 | 15 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 16 | DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin")) 17 | DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) 18 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 19 | ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) 20 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 21 | WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) 22 | } 23 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = localsend_rs 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = github.tom8zds.localsendRs 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2023 github.tom8zds. All rights reserved. 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import FlutterMacOS 2 | import Cocoa 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: 'localsend_rs' 2 | version: '1.0.0+1' 3 | publish_to: none 4 | environment: 5 | sdk: '>=3.2.0 <4.0.0' 6 | description: A Rust Implementation of LocalSend protocl v2 for better performance 7 | dependencies: 8 | animations: ^2.0.11 9 | cupertino_icons: '^1.0.2' 10 | chinese_font_library: ^1.2.0 11 | device_info_plus: '^10.1.0' 12 | dio: '^5.4.0' 13 | equatable: '^2.0.5' 14 | file_picker: ^8.0.6 15 | filesize: ^2.0.1 16 | flutter: 17 | sdk: flutter 18 | flutter_localizations: 19 | sdk: flutter 20 | flutter_riverpod: '^2.4.9' 21 | flutter_rust_bridge: ^2.4.0 22 | freezed_annotation: '^2.4.2' 23 | logger: '^2.3.0' 24 | path_provider: '^2.1.1' 25 | riverpod_annotation: '^2.3.3' 26 | rust_builder: 27 | path: 'rust_builder' 28 | shared_preferences: '^2.2.2' 29 | slang: '^3.31.0' 30 | slang_flutter: '^3.31.0' 31 | uuid: '^4.4.0' 32 | window_manager: ^0.4.2 33 | simple_icons: ^10.1.3 34 | desktop_drop: ^0.5.0 35 | dev_dependencies: 36 | build_runner: '^2.4.8' 37 | custom_lint: '^0.6.4' 38 | ffigen: ^14.0.1 39 | flutter_launcher_icons: ^0.14.0 40 | flutter_lints: '^4.0.0' 41 | flutter_test: 42 | sdk: flutter 43 | freezed: '^2.4.6' 44 | innosetup: '^0.1.3' 45 | integration_test: 46 | sdk: flutter 47 | pubspec_dependency_sorter: '^1.0.5' 48 | riverpod_generator: '^2.3.9' 49 | riverpod_lint: '^2.3.7' 50 | slang_build_runner: '^3.31.0' 51 | version: any 52 | flutter: 53 | uses-material-design: true 54 | assets: 55 | - assets/icon/ 56 | -------------------------------------------------------------------------------- /rust/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb -------------------------------------------------------------------------------- /rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust_lib" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [lib] 7 | crate-type = ["cdylib", "staticlib"] 8 | 9 | [dependencies] 10 | flutter_rust_bridge = "=2.4.0" 11 | axum = { version = "0.7.6" } 12 | rand = "0.8.5" 13 | serde = "1.0.210" 14 | serde_derive = "1.0.210" 15 | serde_json = "1.0.128" 16 | tokio = { version = "1.40.0", features = ["full"] } 17 | tokio-util = { version = "0.7.12", features = ["io"] } 18 | futures = "0.3.30" 19 | log = "0.4.22" 20 | lazy_static = "1.5.0" 21 | simplelog = "0.12.2" 22 | pin-project-lite = "0.2.14" 23 | uuid = { version = "1.10.0", features = ["v4"] } 24 | ureq = "2.10.1" 25 | socket2 = "0.5.7" 26 | parking_lot = "0.12.3" 27 | once_cell = "1.19.0" 28 | -------------------------------------------------------------------------------- /rust/build.cmd: -------------------------------------------------------------------------------- 1 | cd rust 2 | cargo ndk -o ../android/app/src/main/jniLibs -t arm64-v8a -t armeabi-v7a --manifest-path ./Cargo.toml build --release -------------------------------------------------------------------------------- /rust/src/actor/http.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use axum::Router; 4 | use log::info; 5 | 6 | use tokio::sync::{mpsc, watch}; 7 | 8 | use crate::api::v2; 9 | 10 | use super::{core::CoreActorHandle, discovery::DiscoverHandle}; 11 | 12 | enum ServerMessage { 13 | Shutdown, 14 | } 15 | 16 | struct HttpServerActor { 17 | core: CoreActorHandle, 18 | receiver: mpsc::Receiver, 19 | } 20 | 21 | async fn shutdown_signal(mut actor: HttpServerActor) { 22 | while let Some(msg) = actor.receiver.recv().await { 23 | if matches!(msg, ServerMessage::Shutdown) { 24 | break; 25 | }; 26 | actor.handle_message(msg).await; 27 | } 28 | } 29 | 30 | async fn run_http_actor(actor: HttpServerActor, shutdown_callback: watch::Sender) { 31 | let config = actor.core.get_config().await; 32 | let n_port = config.port; 33 | 34 | let discover_handle = DiscoverHandle::new(actor.core.clone()); 35 | 36 | let addr = SocketAddr::from(([0, 0, 0, 0], n_port)); 37 | let listener = tokio::net::TcpListener::bind(addr) 38 | .await 39 | .expect("failed to listen to port"); 40 | 41 | let app = Router::new().nest("/api/localsend/", v2::app(actor.core.clone())); 42 | 43 | info!("http service {} started", n_port); 44 | 45 | axum::serve(listener, app) 46 | .with_graceful_shutdown(shutdown_signal(actor)) 47 | .await 48 | .unwrap(); 49 | 50 | info!("http service {} shutdown", n_port); 51 | 52 | discover_handle.shutdown().await; 53 | 54 | let _ = shutdown_callback.send(true); 55 | } 56 | 57 | impl HttpServerActor { 58 | pub fn new(receiver: mpsc::Receiver, core: CoreActorHandle) -> Self { 59 | HttpServerActor { receiver, core } 60 | } 61 | async fn handle_message(&mut self, msg: ServerMessage) { 62 | match msg { 63 | ServerMessage::Shutdown => todo!(), 64 | _ => {} 65 | } 66 | } 67 | } 68 | 69 | pub struct HttpServerHandle { 70 | sender: mpsc::Sender, 71 | shutdown_receiver: watch::Receiver, 72 | } 73 | 74 | impl HttpServerHandle { 75 | pub fn new(core: CoreActorHandle) -> Self { 76 | let (sender, receiver) = mpsc::channel(8); 77 | let (s_sender, s_receiver) = watch::channel(true); 78 | 79 | let actor = HttpServerActor::new(receiver, core); 80 | 81 | tokio::spawn(run_http_actor(actor, s_sender)); 82 | 83 | Self { 84 | sender, 85 | shutdown_receiver: s_receiver, 86 | } 87 | } 88 | 89 | pub async fn shutdown(mut self) { 90 | let msg = ServerMessage::Shutdown; 91 | 92 | // Ignore send errors. If this send fails, so does the 93 | // recv.await below. There's no reason to check the 94 | // failure twice. 95 | let _ = self.sender.send(msg).await; 96 | self.shutdown_receiver 97 | .changed() 98 | .await 99 | .expect("Actor task has been killed") 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /rust/src/actor/mission.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | 3 | use crate::api::model::FileInfo; 4 | 5 | use super::model::{MissionState, NodeDevice}; 6 | 7 | pub mod notify; 8 | pub mod pending; 9 | pub mod transfer; 10 | 11 | lazy_static! { 12 | pub static ref MISSION_NOTIFY: notify::Handle = notify::Handle::new(); 13 | } 14 | 15 | #[derive(Clone)] 16 | pub struct MissionInfo { 17 | pub id: String, 18 | pub sender: NodeDevice, 19 | pub files: Vec, 20 | pub state: MissionState, 21 | } 22 | 23 | #[derive(Debug, Clone)] 24 | pub struct MissionFileInfo { 25 | pub info: FileInfo, 26 | pub state: FileState, 27 | } 28 | 29 | #[derive(Debug, Clone)] 30 | pub enum FileState { 31 | Pending, 32 | Transfer, 33 | Finish, 34 | Skip, 35 | Fail { msg: String }, 36 | } 37 | 38 | #[derive(Clone)] 39 | pub struct MissionHandle { 40 | pub pending: pending::Handle, 41 | pub transfer: transfer::Handle, 42 | } 43 | 44 | impl MissionHandle { 45 | pub fn new() -> Self { 46 | let transfer = transfer::Handle::new(); 47 | let pending = pending::Handle::new(transfer.clone()); 48 | 49 | Self { pending, transfer } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /rust/src/actor/mission/notify.rs: -------------------------------------------------------------------------------- 1 | use log::{debug, trace}; 2 | use tokio::sync::{mpsc, oneshot, watch}; 3 | 4 | use crate::{ 5 | actor::{ 6 | mission::MISSION_NOTIFY, 7 | model::{Mission, MissionState, NodeDevice}, 8 | }, 9 | api::model::FileInfo, 10 | }; 11 | 12 | use super::MissionInfo; 13 | 14 | enum Message { 15 | Notify { 16 | mission: Option, 17 | }, 18 | Listen { 19 | respond_to: oneshot::Sender>>, 20 | }, 21 | Clear, 22 | } 23 | 24 | struct Actor { 25 | receiver: mpsc::Receiver, 26 | notify: watch::Sender>, 27 | listener: watch::Receiver>, 28 | } 29 | 30 | impl Actor { 31 | fn new(receiver: mpsc::Receiver) -> Self { 32 | let (tx, rx) = watch::channel(None); 33 | 34 | Actor { 35 | receiver, 36 | notify: tx, 37 | listener: rx, 38 | } 39 | } 40 | async fn handle_message(&mut self, msg: Message) { 41 | match msg { 42 | Message::Notify { mission } => { 43 | trace!("mission changed"); 44 | let _ = self.notify.send(mission); 45 | } 46 | Message::Listen { respond_to } => { 47 | let rx = self.listener.clone(); 48 | let _ = respond_to.send(rx); 49 | } 50 | Message::Clear => { 51 | let _ = self.notify.send(None); 52 | } 53 | } 54 | } 55 | } 56 | 57 | async fn run_notify_actor(mut actor: Actor) { 58 | while let Some(msg) = actor.receiver.recv().await { 59 | actor.handle_message(msg).await; 60 | } 61 | } 62 | 63 | #[derive(Clone)] 64 | pub struct Handle { 65 | sender: mpsc::Sender, 66 | } 67 | 68 | impl Handle { 69 | pub fn new() -> Self { 70 | let (sender, receiver) = mpsc::channel(8); 71 | let actor = Actor::new(receiver); 72 | tokio::spawn(run_notify_actor(actor)); 73 | 74 | Self { sender } 75 | } 76 | 77 | pub async fn listen(&self) -> watch::Receiver> { 78 | let (send, recv) = oneshot::channel(); 79 | let msg = Message::Listen { respond_to: send }; 80 | 81 | let _ = self.sender.send(msg).await; 82 | recv.await.expect("Actor task has been killed") 83 | } 84 | 85 | pub async fn notify(&self, mission: Option) { 86 | let msg = Message::Notify { mission }; 87 | let _ = self.sender.send(msg).await; 88 | } 89 | pub async fn clear(&self) { 90 | let (send, recv) = oneshot::channel(); 91 | let msg = Message::Clear; 92 | 93 | let _ = self.sender.send(msg).await; 94 | recv.await.expect("Actor task has been killed") 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /rust/src/actor/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod core; 2 | pub mod device; 3 | pub mod discovery; 4 | pub mod http; 5 | pub mod mission; 6 | pub mod model; 7 | -------------------------------------------------------------------------------- /rust/src/actor/model.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde_derive::{Deserialize, Serialize}; 4 | 5 | use crate::api::model::FileInfo; 6 | 7 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 8 | #[serde(rename_all = "camelCase")] 9 | pub struct NodeDevice { 10 | pub alias: String, 11 | pub version: String, 12 | pub device_model: String, 13 | pub device_type: String, 14 | pub fingerprint: String, 15 | pub address: String, 16 | pub port: u16, 17 | pub protocol: String, 18 | pub download: bool, 19 | pub announcement: bool, 20 | pub announce: bool, 21 | } 22 | 23 | #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] 24 | #[serde(rename_all = "camelCase")] 25 | pub struct NodeAnnounce { 26 | pub alias: String, 27 | pub version: String, 28 | pub device_model: String, 29 | pub device_type: String, 30 | pub fingerprint: String, 31 | pub port: u16, 32 | pub protocol: String, 33 | pub download: bool, 34 | pub announcement: bool, 35 | pub announce: bool, 36 | } 37 | 38 | impl NodeDevice { 39 | pub fn from_announce(announce: &NodeAnnounce, address: &str) -> NodeDevice { 40 | NodeDevice { 41 | alias: announce.alias.clone(), 42 | version: announce.version.clone(), 43 | device_model: announce.device_model.clone(), 44 | device_type: announce.device_type.clone(), 45 | fingerprint: announce.fingerprint.clone(), 46 | address: address.to_string(), 47 | port: announce.port, 48 | protocol: announce.protocol.clone(), 49 | download: announce.download, 50 | announcement: announce.announcement, 51 | announce: announce.announce, 52 | } 53 | } 54 | 55 | pub fn to_announce(&self) -> NodeAnnounce { 56 | NodeAnnounce { 57 | alias: self.alias.clone(), 58 | version: self.version.clone(), 59 | device_model: self.device_model.clone(), 60 | device_type: self.device_type.clone(), 61 | fingerprint: self.fingerprint.clone(), 62 | port: self.port, 63 | protocol: self.protocol.clone(), 64 | download: self.download, 65 | announcement: self.announcement, 66 | announce: self.announce, 67 | } 68 | } 69 | } 70 | 71 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 72 | pub struct Mission { 73 | pub id: String, 74 | pub sender: NodeDevice, 75 | pub id_token_map: HashMap, 76 | pub token_id_map: HashMap, 77 | pub info_map: HashMap, 78 | } 79 | 80 | impl Mission { 81 | pub fn new(info_map: HashMap, sender: NodeDevice) -> Self { 82 | let id = uuid::Uuid::new_v4().to_string(); 83 | let mut id_token_map = HashMap::new(); 84 | let mut token_id_map = HashMap::new(); 85 | info_map.iter().for_each(|(id, _value)| { 86 | let token = uuid::Uuid::new_v4().to_string(); 87 | id_token_map.insert(id.clone(), token.clone()); 88 | token_id_map.insert(token.clone(), id.clone()); 89 | }); 90 | 91 | Mission { 92 | id: id.clone(), 93 | sender, 94 | id_token_map, 95 | token_id_map, 96 | info_map: info_map.clone(), 97 | } 98 | } 99 | } 100 | 101 | #[derive(Debug, Clone, Copy)] 102 | pub enum MissionState { 103 | Idle, 104 | Pending, 105 | Transfering, 106 | Finished, 107 | Failed, 108 | Canceled, 109 | Busy, 110 | } 111 | 112 | #[derive(Debug, Clone, Copy)] 113 | pub enum TaskState { 114 | Transfering, 115 | Finished, 116 | } 117 | -------------------------------------------------------------------------------- /rust/src/api/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod model; 2 | pub mod v2; 3 | -------------------------------------------------------------------------------- /rust/src/api/model.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use serde_derive::{Deserialize, Serialize}; 4 | 5 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 6 | #[serde(rename_all = "camelCase")] 7 | pub struct SenderInfo { 8 | pub alias: String, 9 | pub version: String, 10 | pub device_model: String, 11 | pub device_type: String, 12 | pub fingerprint: String, 13 | pub port: i64, 14 | pub protocol: String, 15 | pub download: bool, 16 | } 17 | 18 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 19 | pub struct FileRequest { 20 | pub info: SenderInfo, 21 | pub files: HashMap, 22 | } 23 | 24 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 25 | #[serde(rename_all = "camelCase")] 26 | pub struct FileInfo { 27 | pub id: String, 28 | pub file_name: String, 29 | pub size: i64, 30 | pub file_type: String, 31 | pub sha256: Option, 32 | pub preview: Option>, 33 | } 34 | 35 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 36 | #[serde(rename_all = "camelCase")] 37 | pub struct FileResponse { 38 | pub session_id: String, 39 | pub files: HashMap, 40 | } 41 | 42 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 43 | #[serde(rename_all = "camelCase")] 44 | pub struct UploadTask { 45 | pub session_id: String, 46 | pub file_id: String, 47 | pub token: String, 48 | } 49 | -------------------------------------------------------------------------------- /rust/src/frb_generated.io.rs: -------------------------------------------------------------------------------- 1 | // This file is automatically generated, so please do not edit it. 2 | // Generated by `flutter_rust_bridge`@ 2.0.0. 3 | 4 | // Section: imports 5 | 6 | use super::*; 7 | use crate::actor::core::*; 8 | use flutter_rust_bridge::for_generated::byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt}; 9 | use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable}; 10 | use flutter_rust_bridge::{Handler, IntoIntoDart}; 11 | 12 | // Section: boilerplate 13 | 14 | flutter_rust_bridge::frb_generated_boilerplate_io!(); 15 | 16 | #[no_mangle] 17 | pub extern "C" fn frbgen_localsend_rs_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( 18 | ptr: *const std::ffi::c_void, 19 | ) { 20 | MoiArc::>::increment_strong_count(ptr as _); 21 | } 22 | 23 | #[no_mangle] 24 | pub extern "C" fn frbgen_localsend_rs_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( 25 | ptr: *const std::ffi::c_void, 26 | ) { 27 | MoiArc::>::decrement_strong_count(ptr as _); 28 | } 29 | -------------------------------------------------------------------------------- /rust/src/frb_generated.web.rs: -------------------------------------------------------------------------------- 1 | // This file is automatically generated, so please do not edit it. 2 | // Generated by `flutter_rust_bridge`@ 2.0.0. 3 | 4 | // Section: imports 5 | 6 | use super::*; 7 | use crate::actor::core::*; 8 | use flutter_rust_bridge::for_generated::byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt}; 9 | use flutter_rust_bridge::for_generated::wasm_bindgen; 10 | use flutter_rust_bridge::for_generated::wasm_bindgen::prelude::*; 11 | use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable}; 12 | use flutter_rust_bridge::{Handler, IntoIntoDart}; 13 | 14 | // Section: boilerplate 15 | 16 | flutter_rust_bridge::frb_generated_boilerplate_web!(); 17 | 18 | #[wasm_bindgen] 19 | pub fn rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( 20 | ptr: *const std::ffi::c_void, 21 | ) { 22 | MoiArc::>::increment_strong_count(ptr as _); 23 | } 24 | 25 | #[wasm_bindgen] 26 | pub fn rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCoreConfig( 27 | ptr: *const std::ffi::c_void, 28 | ) { 29 | MoiArc::>::decrement_strong_count(ptr as _); 30 | } 31 | -------------------------------------------------------------------------------- /rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod actor; 2 | mod api; 3 | pub mod bridge; 4 | mod frb_generated; 5 | mod logger; 6 | mod util; /* AUTO INJECTED BY flutter_rust_bridge. This line may not be accurate, and you can change it according to your needs. */ 7 | -------------------------------------------------------------------------------- /rust/src/util.rs: -------------------------------------------------------------------------------- 1 | use std::io::Result; 2 | use std::pin::Pin; 3 | use std::task::{Context, Poll}; 4 | use std::time::Duration; 5 | 6 | use pin_project_lite::pin_project; 7 | use tokio::io::AsyncWrite; 8 | use tokio::sync::watch::Sender; 9 | use tokio::time::{interval, Interval}; 10 | 11 | pin_project! { 12 | pub struct ProgressWriteAdapter { 13 | #[pin] 14 | inner: R, 15 | interval: Interval, 16 | interval_bytes: usize, 17 | tx: Sender 18 | } 19 | } 20 | 21 | impl ProgressWriteAdapter { 22 | pub fn new(inner: R, tx: Sender) -> Self { 23 | Self { 24 | inner, 25 | interval: interval(Duration::from_millis(100)), 26 | interval_bytes: 0, 27 | tx, 28 | } 29 | } 30 | } 31 | 32 | impl AsyncWrite for ProgressWriteAdapter { 33 | fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { 34 | let this = self.project(); 35 | 36 | let result = this.inner.poll_write(cx, buf); 37 | match result { 38 | Poll::Ready(ref res) => { 39 | if res.is_ok() { 40 | *this.interval_bytes = *this.interval_bytes + res.as_ref().clone().unwrap(); 41 | } 42 | } 43 | _ => {} 44 | } 45 | 46 | match this.interval.poll_tick(cx) { 47 | Poll::Pending => {} 48 | Poll::Ready(_) => { 49 | let _ = this.tx.send(*this.interval_bytes); 50 | } 51 | }; 52 | 53 | result 54 | } 55 | 56 | fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 57 | self.project().inner.poll_flush(cx) 58 | } 59 | 60 | fn poll_shutdown( 61 | self: Pin<&mut Self>, 62 | cx: &mut Context<'_>, 63 | ) -> Poll> { 64 | self.project().inner.poll_shutdown(cx) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /rust_builder/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | **/doc/api/ 28 | .dart_tool/ 29 | build/ 30 | -------------------------------------------------------------------------------- /rust_builder/README.md: -------------------------------------------------------------------------------- 1 | Please ignore this folder, which is just glue to build Rust with Flutter. -------------------------------------------------------------------------------- /rust_builder/android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .cxx 10 | -------------------------------------------------------------------------------- /rust_builder/android/build.gradle: -------------------------------------------------------------------------------- 1 | // The Android Gradle Plugin builds the native code with the Android NDK. 2 | 3 | group 'com.flutter_rust_bridge.rust_builder' 4 | version '1.0' 5 | 6 | buildscript { 7 | repositories { 8 | google() 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | // The Android Gradle Plugin knows how to build native code with the NDK. 14 | classpath 'com.android.tools.build:gradle:7.3.0' 15 | } 16 | } 17 | 18 | rootProject.allprojects { 19 | repositories { 20 | google() 21 | mavenCentral() 22 | } 23 | } 24 | 25 | apply plugin: 'com.android.library' 26 | 27 | android { 28 | if (project.android.hasProperty("namespace")) { 29 | namespace 'com.flutter_rust_bridge.rust_builder' 30 | } 31 | 32 | // Bumping the plugin compileSdkVersion requires all clients of this plugin 33 | // to bump the version in their app. 34 | compileSdkVersion 33 35 | 36 | // Use the NDK version 37 | // declared in /android/app/build.gradle file of the Flutter project. 38 | // Replace it with a version number if this plugin requires a specfic NDK version. 39 | // (e.g. ndkVersion "23.1.7779620") 40 | ndkVersion android.ndkVersion 41 | 42 | compileOptions { 43 | sourceCompatibility JavaVersion.VERSION_1_8 44 | targetCompatibility JavaVersion.VERSION_1_8 45 | } 46 | 47 | defaultConfig { 48 | minSdkVersion 19 49 | } 50 | } 51 | 52 | apply from: "../cargokit/gradle/plugin.gradle" 53 | cargokit { 54 | manifestDir = "../../rust" 55 | libname = "rust_lib" 56 | } 57 | -------------------------------------------------------------------------------- /rust_builder/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'rust_builder' 2 | -------------------------------------------------------------------------------- /rust_builder/android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /rust_builder/cargokit/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .dart_tool 3 | *.iml 4 | !pubspec.lock 5 | -------------------------------------------------------------------------------- /rust_builder/cargokit/LICENSE: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | Copyright 2022 Matej Knopp 5 | 6 | ================================================================================ 7 | 8 | MIT LICENSE 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 14 | of the Software, and to permit persons to whom the Software is furnished to do 15 | so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 22 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 23 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 25 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 | 27 | ================================================================================ 28 | 29 | APACHE LICENSE, VERSION 2.0 30 | 31 | Licensed under the Apache License, Version 2.0 (the "License"); 32 | you may not use this file except in compliance with the License. 33 | You may obtain a copy of the License at 34 | 35 | http://www.apache.org/licenses/LICENSE-2.0 36 | 37 | Unless required by applicable law or agreed to in writing, software 38 | distributed under the License is distributed on an "AS IS" BASIS, 39 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 40 | See the License for the specific language governing permissions and 41 | limitations under the License. 42 | 43 | -------------------------------------------------------------------------------- /rust_builder/cargokit/README: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | Experimental repository to provide glue for seamlessly integrating cargo build 5 | with flutter plugins and packages. 6 | 7 | See https://matejknopp.com/post/flutter_plugin_in_rust_with_no_prebuilt_binaries/ 8 | for a tutorial on how to use Cargokit. 9 | 10 | Example plugin available at https://github.com/irondash/hello_rust_ffi_plugin. 11 | 12 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_pod.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | BASEDIR=$(dirname "$0") 5 | 6 | # Workaround for https://github.com/dart-lang/pub/issues/4010 7 | BASEDIR=$(cd "$BASEDIR" ; pwd -P) 8 | 9 | # Remove XCode SDK from path. Otherwise this breaks tool compilation when building iOS project 10 | NEW_PATH=`echo $PATH | tr ":" "\n" | grep -v "Contents/Developer/" | tr "\n" ":"` 11 | 12 | export PATH=${NEW_PATH%?} # remove trailing : 13 | 14 | env 15 | 16 | # Platform name (macosx, iphoneos, iphonesimulator) 17 | export CARGOKIT_DARWIN_PLATFORM_NAME=$PLATFORM_NAME 18 | 19 | # Arctive architectures (arm64, armv7, x86_64), space separated. 20 | export CARGOKIT_DARWIN_ARCHS=$ARCHS 21 | 22 | # Current build configuration (Debug, Release) 23 | export CARGOKIT_CONFIGURATION=$CONFIGURATION 24 | 25 | # Path to directory containing Cargo.toml. 26 | export CARGOKIT_MANIFEST_DIR=$PODS_TARGET_SRCROOT/$1 27 | 28 | # Temporary directory for build artifacts. 29 | export CARGOKIT_TARGET_TEMP_DIR=$TARGET_TEMP_DIR 30 | 31 | # Output directory for final artifacts. 32 | export CARGOKIT_OUTPUT_DIR=$PODS_CONFIGURATION_BUILD_DIR/$PRODUCT_NAME 33 | 34 | # Directory to store built tool artifacts. 35 | export CARGOKIT_TOOL_TEMP_DIR=$TARGET_TEMP_DIR/build_tool 36 | 37 | # Directory inside root project. Not necessarily the top level directory of root project. 38 | export CARGOKIT_ROOT_PROJECT_DIR=$SRCROOT 39 | 40 | FLUTTER_EXPORT_BUILD_ENVIRONMENT=( 41 | "$PODS_ROOT/../Flutter/ephemeral/flutter_export_environment.sh" # macOS 42 | "$PODS_ROOT/../Flutter/flutter_export_environment.sh" # iOS 43 | ) 44 | 45 | for path in "${FLUTTER_EXPORT_BUILD_ENVIRONMENT[@]}" 46 | do 47 | if [[ -f "$path" ]]; then 48 | source "$path" 49 | fi 50 | done 51 | 52 | "$BASEDIR/run_build_tool.sh" build-pod "$@" 53 | 54 | # Make a symlink from built framework to phony file, which will be used as input to 55 | # build script. This should force rebuild (podspec currently doesn't support alwaysOutOfDate 56 | # attribute on custom build phase) 57 | ln -fs "$OBJROOT/XCBuildData/build.db" "${BUILT_PRODUCTS_DIR}/cargokit_phony" 58 | ln -fs "${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}" "${BUILT_PRODUCTS_DIR}/cargokit_phony_out" 59 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/README.md: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | A sample command-line application with an entrypoint in `bin/`, library code 5 | in `lib/`, and example unit test in `test/`. 6 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This is copied from Cargokit (which is the official way to use it currently) 2 | # Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | # This file configures the static analysis results for your project (errors, 5 | # warnings, and lints). 6 | # 7 | # This enables the 'recommended' set of lints from `package:lints`. 8 | # This set helps identify many issues that may lead to problems when running 9 | # or consuming Dart code, and enforces writing Dart using a single, idiomatic 10 | # style and format. 11 | # 12 | # If you want a smaller set of lints you can change this to specify 13 | # 'package:lints/core.yaml'. These are just the most critical lints 14 | # (the recommended set includes the core lints). 15 | # The core lints are also what is used by pub.dev for scoring packages. 16 | 17 | include: package:lints/recommended.yaml 18 | 19 | # Uncomment the following section to specify additional rules. 20 | 21 | linter: 22 | rules: 23 | - prefer_relative_imports 24 | - directives_ordering 25 | 26 | # analyzer: 27 | # exclude: 28 | # - path/to/excluded/files/** 29 | 30 | # For more information about the core and recommended set of lints, see 31 | # https://dart.dev/go/core-lints 32 | 33 | # For additional information about configuring this file, see 34 | # https://dart.dev/guides/language/analysis-options 35 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/bin/build_tool.dart: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | import 'package:build_tool/build_tool.dart' as build_tool; 5 | 6 | void main(List arguments) { 7 | build_tool.runMain(arguments); 8 | } 9 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/lib/build_tool.dart: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | import 'src/build_tool.dart' as build_tool; 5 | 6 | Future runMain(List args) async { 7 | return build_tool.runMain(args); 8 | } 9 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/lib/src/build_cmake.dart: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | import 'dart:io'; 5 | 6 | import 'package:path/path.dart' as path; 7 | 8 | import 'artifacts_provider.dart'; 9 | import 'builder.dart'; 10 | import 'environment.dart'; 11 | import 'options.dart'; 12 | import 'target.dart'; 13 | 14 | class BuildCMake { 15 | final CargokitUserOptions userOptions; 16 | 17 | BuildCMake({required this.userOptions}); 18 | 19 | Future build() async { 20 | final targetPlatform = Environment.targetPlatform; 21 | final target = Target.forFlutterName(Environment.targetPlatform); 22 | if (target == null) { 23 | throw Exception("Unknown target platform: $targetPlatform"); 24 | } 25 | 26 | final environment = BuildEnvironment.fromEnvironment(isAndroid: false); 27 | final provider = 28 | ArtifactProvider(environment: environment, userOptions: userOptions); 29 | final artifacts = await provider.getArtifacts([target]); 30 | 31 | final libs = artifacts[target]!; 32 | 33 | for (final lib in libs) { 34 | if (lib.type == AritifactType.dylib) { 35 | File(lib.path) 36 | .copySync(path.join(Environment.outputDir, lib.finalFileName)); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/lib/src/build_gradle.dart: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | import 'dart:io'; 5 | 6 | import 'package:logging/logging.dart'; 7 | import 'package:path/path.dart' as path; 8 | 9 | import 'artifacts_provider.dart'; 10 | import 'builder.dart'; 11 | import 'environment.dart'; 12 | import 'options.dart'; 13 | import 'target.dart'; 14 | 15 | final log = Logger('build_gradle'); 16 | 17 | class BuildGradle { 18 | BuildGradle({required this.userOptions}); 19 | 20 | final CargokitUserOptions userOptions; 21 | 22 | Future build() async { 23 | final targets = Environment.targetPlatforms.map((arch) { 24 | final target = Target.forFlutterName(arch); 25 | if (target == null) { 26 | throw Exception( 27 | "Unknown darwin target or platform: $arch, ${Environment.darwinPlatformName}"); 28 | } 29 | return target; 30 | }).toList(); 31 | 32 | final environment = BuildEnvironment.fromEnvironment(isAndroid: true); 33 | final provider = 34 | ArtifactProvider(environment: environment, userOptions: userOptions); 35 | final artifacts = await provider.getArtifacts(targets); 36 | 37 | for (final target in targets) { 38 | final libs = artifacts[target]!; 39 | final outputDir = path.join(Environment.outputDir, target.android!); 40 | Directory(outputDir).createSync(recursive: true); 41 | 42 | for (final lib in libs) { 43 | if (lib.type == AritifactType.dylib) { 44 | File(lib.path).copySync(path.join(outputDir, lib.finalFileName)); 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/lib/src/build_pod.dart: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | import 'dart:io'; 5 | 6 | import 'package:path/path.dart' as path; 7 | 8 | import 'artifacts_provider.dart'; 9 | import 'builder.dart'; 10 | import 'environment.dart'; 11 | import 'options.dart'; 12 | import 'target.dart'; 13 | import 'util.dart'; 14 | 15 | class BuildPod { 16 | BuildPod({required this.userOptions}); 17 | 18 | final CargokitUserOptions userOptions; 19 | 20 | Future build() async { 21 | final targets = Environment.darwinArchs.map((arch) { 22 | final target = Target.forDarwin( 23 | platformName: Environment.darwinPlatformName, darwinAarch: arch); 24 | if (target == null) { 25 | throw Exception( 26 | "Unknown darwin target or platform: $arch, ${Environment.darwinPlatformName}"); 27 | } 28 | return target; 29 | }).toList(); 30 | 31 | final environment = BuildEnvironment.fromEnvironment(isAndroid: false); 32 | final provider = 33 | ArtifactProvider(environment: environment, userOptions: userOptions); 34 | final artifacts = await provider.getArtifacts(targets); 35 | 36 | void performLipo(String targetFile, Iterable sourceFiles) { 37 | runCommand("lipo", [ 38 | '-create', 39 | ...sourceFiles, 40 | '-output', 41 | targetFile, 42 | ]); 43 | } 44 | 45 | final outputDir = Environment.outputDir; 46 | 47 | Directory(outputDir).createSync(recursive: true); 48 | 49 | final staticLibs = artifacts.values 50 | .expand((element) => element) 51 | .where((element) => element.type == AritifactType.staticlib) 52 | .toList(); 53 | final dynamicLibs = artifacts.values 54 | .expand((element) => element) 55 | .where((element) => element.type == AritifactType.dylib) 56 | .toList(); 57 | 58 | final libName = environment.crateInfo.packageName; 59 | 60 | // If there is static lib, use it and link it with pod 61 | if (staticLibs.isNotEmpty) { 62 | final finalTargetFile = path.join(outputDir, "lib$libName.a"); 63 | performLipo(finalTargetFile, staticLibs.map((e) => e.path)); 64 | } else { 65 | // Otherwise try to replace bundle dylib with our dylib 66 | final bundlePaths = [ 67 | '$libName.framework/Versions/A/$libName', 68 | '$libName.framework/$libName', 69 | ]; 70 | 71 | for (final bundlePath in bundlePaths) { 72 | final targetFile = path.join(outputDir, bundlePath); 73 | if (File(targetFile).existsSync()) { 74 | performLipo(targetFile, dynamicLibs.map((e) => e.path)); 75 | 76 | // Replace absolute id with @rpath one so that it works properly 77 | // when moved to Frameworks. 78 | runCommand("install_name_tool", [ 79 | '-id', 80 | '@rpath/$bundlePath', 81 | targetFile, 82 | ]); 83 | return; 84 | } 85 | } 86 | throw Exception('Unable to find bundle for dynamic library'); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/lib/src/cargo.dart: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | import 'dart:io'; 5 | 6 | import 'package:path/path.dart' as path; 7 | import 'package:toml/toml.dart'; 8 | 9 | class ManifestException { 10 | ManifestException(this.message, {required this.fileName}); 11 | 12 | final String? fileName; 13 | final String message; 14 | 15 | @override 16 | String toString() { 17 | if (fileName != null) { 18 | return 'Failed to parse package manifest at $fileName: $message'; 19 | } else { 20 | return 'Failed to parse package manifest: $message'; 21 | } 22 | } 23 | } 24 | 25 | class CrateInfo { 26 | CrateInfo({required this.packageName}); 27 | 28 | final String packageName; 29 | 30 | static CrateInfo parseManifest(String manifest, {final String? fileName}) { 31 | final toml = TomlDocument.parse(manifest); 32 | final package = toml.toMap()['package']; 33 | if (package == null) { 34 | throw ManifestException('Missing package section', fileName: fileName); 35 | } 36 | final name = package['name']; 37 | if (name == null) { 38 | throw ManifestException('Missing package name', fileName: fileName); 39 | } 40 | return CrateInfo(packageName: name); 41 | } 42 | 43 | static CrateInfo load(String manifestDir) { 44 | final manifestFile = File(path.join(manifestDir, 'Cargo.toml')); 45 | final manifest = manifestFile.readAsStringSync(); 46 | return parseManifest(manifest, fileName: manifestFile.path); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/lib/src/environment.dart: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | import 'dart:io'; 5 | 6 | extension on String { 7 | String resolveSymlink() => File(this).resolveSymbolicLinksSync(); 8 | } 9 | 10 | class Environment { 11 | /// Current build configuration (debug or release). 12 | static String get configuration => 13 | _getEnv("CARGOKIT_CONFIGURATION").toLowerCase(); 14 | 15 | static bool get isDebug => configuration == 'debug'; 16 | static bool get isRelease => configuration == 'release'; 17 | 18 | /// Temporary directory where Rust build artifacts are placed. 19 | static String get targetTempDir => _getEnv("CARGOKIT_TARGET_TEMP_DIR"); 20 | 21 | /// Final output directory where the build artifacts are placed. 22 | static String get outputDir => _getEnvPath('CARGOKIT_OUTPUT_DIR'); 23 | 24 | /// Path to the crate manifest (containing Cargo.toml). 25 | static String get manifestDir => _getEnvPath('CARGOKIT_MANIFEST_DIR'); 26 | 27 | /// Directory inside root project. Not necessarily root folder. Symlinks are 28 | /// not resolved on purpose. 29 | static String get rootProjectDir => _getEnv('CARGOKIT_ROOT_PROJECT_DIR'); 30 | 31 | // Pod 32 | 33 | /// Platform name (macosx, iphoneos, iphonesimulator). 34 | static String get darwinPlatformName => 35 | _getEnv("CARGOKIT_DARWIN_PLATFORM_NAME"); 36 | 37 | /// List of architectures to build for (arm64, armv7, x86_64). 38 | static List get darwinArchs => 39 | _getEnv("CARGOKIT_DARWIN_ARCHS").split(' '); 40 | 41 | // Gradle 42 | static String get minSdkVersion => _getEnv("CARGOKIT_MIN_SDK_VERSION"); 43 | static String get ndkVersion => _getEnv("CARGOKIT_NDK_VERSION"); 44 | static String get sdkPath => _getEnvPath("CARGOKIT_SDK_DIR"); 45 | static String get javaHome => _getEnvPath("CARGOKIT_JAVA_HOME"); 46 | static List get targetPlatforms => 47 | _getEnv("CARGOKIT_TARGET_PLATFORMS").split(','); 48 | 49 | // CMAKE 50 | static String get targetPlatform => _getEnv("CARGOKIT_TARGET_PLATFORM"); 51 | 52 | static String _getEnv(String key) { 53 | final res = Platform.environment[key]; 54 | if (res == null) { 55 | throw Exception("Missing environment variable $key"); 56 | } 57 | return res; 58 | } 59 | 60 | static String _getEnvPath(String key) { 61 | final res = _getEnv(key); 62 | if (Directory(res).existsSync()) { 63 | return res.resolveSymlink(); 64 | } else { 65 | return res; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/lib/src/logging.dart: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | import 'dart:io'; 5 | 6 | import 'package:logging/logging.dart'; 7 | 8 | const String kSeparator = "--"; 9 | const String kDoubleSeparator = "=="; 10 | 11 | bool _lastMessageWasSeparator = false; 12 | 13 | void _log(LogRecord rec) { 14 | final prefix = '${rec.level.name}: '; 15 | final out = rec.level == Level.SEVERE ? stderr : stdout; 16 | if (rec.message == kSeparator) { 17 | if (!_lastMessageWasSeparator) { 18 | out.write(prefix); 19 | out.writeln('-' * 80); 20 | _lastMessageWasSeparator = true; 21 | } 22 | return; 23 | } else if (rec.message == kDoubleSeparator) { 24 | out.write(prefix); 25 | out.writeln('=' * 80); 26 | _lastMessageWasSeparator = true; 27 | return; 28 | } 29 | out.write(prefix); 30 | out.writeln(rec.message); 31 | _lastMessageWasSeparator = false; 32 | } 33 | 34 | void initLogging() { 35 | Logger.root.level = Level.INFO; 36 | Logger.root.onRecord.listen((LogRecord rec) { 37 | final lines = rec.message.split('\n'); 38 | for (final line in lines) { 39 | if (line.isNotEmpty || lines.length == 1 || line != lines.last) { 40 | _log(LogRecord( 41 | rec.level, 42 | line, 43 | rec.loggerName, 44 | )); 45 | } 46 | } 47 | }); 48 | } 49 | 50 | void enableVerboseLogging() { 51 | Logger.root.level = Level.ALL; 52 | } 53 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/lib/src/rustup.dart: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | import 'dart:io'; 5 | 6 | import 'package:collection/collection.dart'; 7 | import 'package:path/path.dart' as path; 8 | 9 | import 'util.dart'; 10 | 11 | class _Toolchain { 12 | _Toolchain( 13 | this.name, 14 | this.targets, 15 | ); 16 | 17 | final String name; 18 | final List targets; 19 | } 20 | 21 | class Rustup { 22 | List? installedTargets(String toolchain) { 23 | final targets = _installedTargets(toolchain); 24 | return targets != null ? List.unmodifiable(targets) : null; 25 | } 26 | 27 | void installToolchain(String toolchain) { 28 | log.info("Installing Rust toolchain: $toolchain"); 29 | runCommand("rustup", ['toolchain', 'install', toolchain]); 30 | _installedToolchains 31 | .add(_Toolchain(toolchain, _getInstalledTargets(toolchain))); 32 | } 33 | 34 | void installTarget( 35 | String target, { 36 | required String toolchain, 37 | }) { 38 | log.info("Installing Rust target: $target"); 39 | runCommand("rustup", [ 40 | 'target', 41 | 'add', 42 | '--toolchain', 43 | toolchain, 44 | target, 45 | ]); 46 | _installedTargets(toolchain)?.add(target); 47 | } 48 | 49 | final List<_Toolchain> _installedToolchains; 50 | 51 | Rustup() : _installedToolchains = _getInstalledToolchains(); 52 | 53 | List? _installedTargets(String toolchain) => _installedToolchains 54 | .firstWhereOrNull( 55 | (e) => e.name == toolchain || e.name.startsWith('$toolchain-')) 56 | ?.targets; 57 | 58 | static List<_Toolchain> _getInstalledToolchains() { 59 | String extractToolchainName(String line) { 60 | // ignore (default) after toolchain name 61 | final parts = line.split(' '); 62 | return parts[0]; 63 | } 64 | 65 | final res = runCommand("rustup", ['toolchain', 'list']); 66 | final lines = res.stdout 67 | .toString() 68 | .split('\n') 69 | .where((e) => e.isNotEmpty) 70 | .map(extractToolchainName) 71 | .toList(growable: true); 72 | 73 | return lines 74 | .map( 75 | (name) => _Toolchain( 76 | name, 77 | _getInstalledTargets(name), 78 | ), 79 | ) 80 | .toList(growable: true); 81 | } 82 | 83 | static List _getInstalledTargets(String toolchain) { 84 | final res = runCommand("rustup", [ 85 | 'target', 86 | 'list', 87 | '--toolchain', 88 | toolchain, 89 | '--installed', 90 | ]); 91 | final lines = res.stdout 92 | .toString() 93 | .split('\n') 94 | .where((e) => e.isNotEmpty) 95 | .toList(growable: true); 96 | return lines; 97 | } 98 | 99 | bool _didInstallRustSrcForNightly = false; 100 | 101 | void installRustSrcForNightly() { 102 | if (_didInstallRustSrcForNightly) { 103 | return; 104 | } 105 | // Useful for -Z build-std 106 | runCommand( 107 | "rustup", 108 | ['component', 'add', 'rust-src', '--toolchain', 'nightly'], 109 | ); 110 | _didInstallRustSrcForNightly = true; 111 | } 112 | 113 | static String? executablePath() { 114 | final envPath = Platform.environment['PATH']; 115 | final envPathSeparator = Platform.isWindows ? ';' : ':'; 116 | final home = Platform.isWindows 117 | ? Platform.environment['USERPROFILE'] 118 | : Platform.environment['HOME']; 119 | final paths = [ 120 | if (home != null) path.join(home, '.cargo', 'bin'), 121 | if (envPath != null) ...envPath.split(envPathSeparator), 122 | ]; 123 | for (final p in paths) { 124 | final rustup = Platform.isWindows ? 'rustup.exe' : 'rustup'; 125 | final rustupPath = path.join(p, rustup); 126 | if (File(rustupPath).existsSync()) { 127 | return rustupPath; 128 | } 129 | } 130 | return null; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/lib/src/util.dart: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | import 'dart:convert'; 5 | import 'dart:io'; 6 | 7 | import 'package:logging/logging.dart'; 8 | import 'package:path/path.dart' as path; 9 | 10 | import 'logging.dart'; 11 | import 'rustup.dart'; 12 | 13 | final log = Logger("process"); 14 | 15 | class CommandFailedException implements Exception { 16 | final String executable; 17 | final List arguments; 18 | final ProcessResult result; 19 | 20 | CommandFailedException({ 21 | required this.executable, 22 | required this.arguments, 23 | required this.result, 24 | }); 25 | 26 | @override 27 | String toString() { 28 | final stdout = result.stdout.toString().trim(); 29 | final stderr = result.stderr.toString().trim(); 30 | return [ 31 | "External Command: $executable ${arguments.map((e) => '"$e"').join(' ')}", 32 | "Returned Exit Code: ${result.exitCode}", 33 | kSeparator, 34 | "STDOUT:", 35 | if (stdout.isNotEmpty) stdout, 36 | kSeparator, 37 | "STDERR:", 38 | if (stderr.isNotEmpty) stderr, 39 | ].join('\n'); 40 | } 41 | } 42 | 43 | ProcessResult runCommand( 44 | String executable, 45 | List arguments, { 46 | String? workingDirectory, 47 | Map? environment, 48 | bool includeParentEnvironment = true, 49 | bool runInShell = false, 50 | Encoding? stdoutEncoding = systemEncoding, 51 | Encoding? stderrEncoding = systemEncoding, 52 | }) { 53 | log.finer('Running command $executable ${arguments.join(' ')}'); 54 | final res = Process.runSync( 55 | _resolveExecutable(executable), 56 | arguments, 57 | workingDirectory: workingDirectory, 58 | environment: environment, 59 | includeParentEnvironment: includeParentEnvironment, 60 | runInShell: runInShell, 61 | stderrEncoding: stderrEncoding, 62 | stdoutEncoding: stdoutEncoding, 63 | ); 64 | if (res.exitCode != 0) { 65 | throw CommandFailedException( 66 | executable: executable, 67 | arguments: arguments, 68 | result: res, 69 | ); 70 | } else { 71 | return res; 72 | } 73 | } 74 | 75 | class RustupNotFoundException implements Exception { 76 | @override 77 | String toString() { 78 | return [ 79 | ' ', 80 | 'rustup not found in PATH.', 81 | ' ', 82 | 'Maybe you need to install Rust? It only takes a minute:', 83 | ' ', 84 | if (Platform.isWindows) 'https://www.rust-lang.org/tools/install', 85 | if (hasHomebrewRustInPath()) ...[ 86 | '\$ brew unlink rust # Unlink homebrew Rust from PATH', 87 | ], 88 | if (!Platform.isWindows) 89 | "\$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh", 90 | ' ', 91 | ].join('\n'); 92 | } 93 | 94 | static bool hasHomebrewRustInPath() { 95 | if (!Platform.isMacOS) { 96 | return false; 97 | } 98 | final envPath = Platform.environment['PATH'] ?? ''; 99 | final paths = envPath.split(':'); 100 | return paths.any((p) { 101 | return p.contains('homebrew') && File(path.join(p, 'rustc')).existsSync(); 102 | }); 103 | } 104 | } 105 | 106 | String _resolveExecutable(String executable) { 107 | if (executable == 'rustup') { 108 | final resolved = Rustup.executablePath(); 109 | if (resolved != null) { 110 | return resolved; 111 | } 112 | throw RustupNotFoundException(); 113 | } else { 114 | return executable; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/lib/src/verify_binaries.dart: -------------------------------------------------------------------------------- 1 | /// This is copied from Cargokit (which is the official way to use it currently) 2 | /// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | import 'dart:io'; 5 | 6 | import 'package:ed25519_edwards/ed25519_edwards.dart'; 7 | import 'package:http/http.dart'; 8 | 9 | import 'artifacts_provider.dart'; 10 | import 'cargo.dart'; 11 | import 'crate_hash.dart'; 12 | import 'options.dart'; 13 | import 'precompile_binaries.dart'; 14 | import 'target.dart'; 15 | 16 | class VerifyBinaries { 17 | VerifyBinaries({ 18 | required this.manifestDir, 19 | }); 20 | 21 | final String manifestDir; 22 | 23 | Future run() async { 24 | final crateInfo = CrateInfo.load(manifestDir); 25 | 26 | final config = CargokitCrateOptions.load(manifestDir: manifestDir); 27 | final precompiledBinaries = config.precompiledBinaries; 28 | if (precompiledBinaries == null) { 29 | stdout.writeln('Crate does not support precompiled binaries.'); 30 | } else { 31 | final crateHash = CrateHash.compute(manifestDir); 32 | stdout.writeln('Crate hash: $crateHash'); 33 | 34 | for (final target in Target.all) { 35 | final message = 'Checking ${target.rust}...'; 36 | stdout.write(message.padRight(40)); 37 | stdout.flush(); 38 | 39 | final artifacts = getArtifactNames( 40 | target: target, 41 | libraryName: crateInfo.packageName, 42 | remote: true, 43 | ); 44 | 45 | final prefix = precompiledBinaries.uriPrefix; 46 | 47 | bool ok = true; 48 | 49 | for (final artifact in artifacts) { 50 | final fileName = PrecompileBinaries.fileName(target, artifact); 51 | final signatureFileName = 52 | PrecompileBinaries.signatureFileName(target, artifact); 53 | 54 | final url = Uri.parse('$prefix$crateHash/$fileName'); 55 | final signatureUrl = 56 | Uri.parse('$prefix$crateHash/$signatureFileName'); 57 | 58 | final signature = await get(signatureUrl); 59 | if (signature.statusCode != 200) { 60 | stdout.writeln('MISSING'); 61 | ok = false; 62 | break; 63 | } 64 | final asset = await get(url); 65 | if (asset.statusCode != 200) { 66 | stdout.writeln('MISSING'); 67 | ok = false; 68 | break; 69 | } 70 | 71 | if (!verify(precompiledBinaries.publicKey, asset.bodyBytes, 72 | signature.bodyBytes)) { 73 | stdout.writeln('INVALID SIGNATURE'); 74 | ok = false; 75 | } 76 | } 77 | 78 | if (ok) { 79 | stdout.writeln('OK'); 80 | } 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /rust_builder/cargokit/build_tool/pubspec.yaml: -------------------------------------------------------------------------------- 1 | # This is copied from Cargokit (which is the official way to use it currently) 2 | # Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin 3 | 4 | name: build_tool 5 | description: Cargokit build_tool. Facilitates the build of Rust crate during Flutter application build. 6 | publish_to: none 7 | version: 1.0.0 8 | 9 | environment: 10 | sdk: ">=3.0.0 <4.0.0" 11 | 12 | # Add regular dependencies here. 13 | dependencies: 14 | # these are pinned on purpose because the bundle_tool_runner doesn't have 15 | # pubspec.lock. See run_build_tool.sh 16 | logging: 1.2.0 17 | path: 1.8.0 18 | version: 3.0.0 19 | collection: 1.18.0 20 | ed25519_edwards: 0.3.1 21 | hex: 0.2.0 22 | yaml: 3.1.2 23 | source_span: 1.10.0 24 | github: 9.17.0 25 | args: 2.4.2 26 | crypto: 3.0.3 27 | convert: 3.1.1 28 | http: 1.1.0 29 | toml: 0.14.0 30 | 31 | dev_dependencies: 32 | lints: ^2.1.0 33 | test: ^1.24.0 34 | -------------------------------------------------------------------------------- /rust_builder/cargokit/cmake/resolve_symlinks.ps1: -------------------------------------------------------------------------------- 1 | function Resolve-Symlinks { 2 | [CmdletBinding()] 3 | [OutputType([string])] 4 | param( 5 | [Parameter(Position = 0, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] 6 | [string] $Path 7 | ) 8 | 9 | [string] $separator = '/' 10 | [string[]] $parts = $Path.Split($separator) 11 | 12 | [string] $realPath = '' 13 | foreach ($part in $parts) { 14 | if ($realPath -and !$realPath.EndsWith($separator)) { 15 | $realPath += $separator 16 | } 17 | $realPath += $part 18 | $item = Get-Item $realPath 19 | if ($item.Target) { 20 | $realPath = $item.Target.Replace('\', '/') 21 | } 22 | } 23 | $realPath 24 | } 25 | 26 | $path=Resolve-Symlinks -Path $args[0] 27 | Write-Host $path 28 | -------------------------------------------------------------------------------- /rust_builder/cargokit/run_build_tool.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | 4 | setlocal ENABLEDELAYEDEXPANSION 5 | 6 | SET BASEDIR=%~dp0 7 | 8 | if not exist "%CARGOKIT_TOOL_TEMP_DIR%" ( 9 | mkdir "%CARGOKIT_TOOL_TEMP_DIR%" 10 | ) 11 | cd "%CARGOKIT_TOOL_TEMP_DIR%" 12 | 13 | SET BUILD_TOOL_PKG_DIR=%BASEDIR%build_tool 14 | SET DART=%FLUTTER_ROOT%\bin\cache\dart-sdk\bin\dart 15 | 16 | set BUILD_TOOL_PKG_DIR_POSIX=%BUILD_TOOL_PKG_DIR:\=/% 17 | 18 | ( 19 | echo name: build_tool_runner 20 | echo version: 1.0.0 21 | echo publish_to: none 22 | echo. 23 | echo environment: 24 | echo sdk: '^>=3.0.0 ^<4.0.0' 25 | echo. 26 | echo dependencies: 27 | echo build_tool: 28 | echo path: %BUILD_TOOL_PKG_DIR_POSIX% 29 | ) >pubspec.yaml 30 | 31 | if not exist bin ( 32 | mkdir bin 33 | ) 34 | 35 | ( 36 | echo import 'package:build_tool/build_tool.dart' as build_tool; 37 | echo void main^(List^ args^) ^{ 38 | echo build_tool.runMain^(args^); 39 | echo ^} 40 | ) >bin\build_tool_runner.dart 41 | 42 | SET PRECOMPILED=bin\build_tool_runner.dill 43 | 44 | REM To detect changes in package we compare output of DIR /s (recursive) 45 | set PREV_PACKAGE_INFO=.dart_tool\package_info.prev 46 | set CUR_PACKAGE_INFO=.dart_tool\package_info.cur 47 | 48 | DIR "%BUILD_TOOL_PKG_DIR%" /s > "%CUR_PACKAGE_INFO%_orig" 49 | 50 | REM Last line in dir output is free space on harddrive. That is bound to 51 | REM change between invocation so we need to remove it 52 | ( 53 | Set "Line=" 54 | For /F "UseBackQ Delims=" %%A In ("%CUR_PACKAGE_INFO%_orig") Do ( 55 | SetLocal EnableDelayedExpansion 56 | If Defined Line Echo !Line! 57 | EndLocal 58 | Set "Line=%%A") 59 | ) >"%CUR_PACKAGE_INFO%" 60 | DEL "%CUR_PACKAGE_INFO%_orig" 61 | 62 | REM Compare current directory listing with previous 63 | FC /B "%CUR_PACKAGE_INFO%" "%PREV_PACKAGE_INFO%" > nul 2>&1 64 | 65 | If %ERRORLEVEL% neq 0 ( 66 | REM Changed - copy current to previous and remove precompiled kernel 67 | if exist "%PREV_PACKAGE_INFO%" ( 68 | DEL "%PREV_PACKAGE_INFO%" 69 | ) 70 | MOVE /Y "%CUR_PACKAGE_INFO%" "%PREV_PACKAGE_INFO%" 71 | if exist "%PRECOMPILED%" ( 72 | DEL "%PRECOMPILED%" 73 | ) 74 | ) 75 | 76 | REM There is no CUR_PACKAGE_INFO it was renamed in previous step to %PREV_PACKAGE_INFO% 77 | REM which means we need to do pub get and precompile 78 | if not exist "%PRECOMPILED%" ( 79 | "%DART%" pub get --no-precompile 80 | "%DART%" compile kernel bin/build_tool_runner.dart 81 | ) 82 | 83 | "%DART%" "%PRECOMPILED%" %* 84 | 85 | REM 253 means invalid snapshot version. 86 | If %ERRORLEVEL% equ 253 ( 87 | "%DART%" pub get --no-precompile 88 | "%DART%" compile kernel bin/build_tool_runner.dart 89 | "%DART%" "%PRECOMPILED%" %* 90 | ) 91 | -------------------------------------------------------------------------------- /rust_builder/cargokit/run_build_tool.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | BASEDIR=$(dirname "$0") 6 | 7 | mkdir -p "$CARGOKIT_TOOL_TEMP_DIR" 8 | 9 | cd "$CARGOKIT_TOOL_TEMP_DIR" 10 | 11 | # Write a very simple bin package in temp folder that depends on build_tool package 12 | # from Cargokit. This is done to ensure that we don't pollute Cargokit folder 13 | # with .dart_tool contents. 14 | 15 | BUILD_TOOL_PKG_DIR="$BASEDIR/build_tool" 16 | 17 | if [[ -z $FLUTTER_ROOT ]]; then # not defined 18 | DART=dart 19 | else 20 | DART="$FLUTTER_ROOT/bin/cache/dart-sdk/bin/dart" 21 | fi 22 | 23 | cat << EOF > "pubspec.yaml" 24 | name: build_tool_runner 25 | version: 1.0.0 26 | publish_to: none 27 | 28 | environment: 29 | sdk: '>=3.0.0 <4.0.0' 30 | 31 | dependencies: 32 | build_tool: 33 | path: "$BUILD_TOOL_PKG_DIR" 34 | EOF 35 | 36 | mkdir -p "bin" 37 | 38 | cat << EOF > "bin/build_tool_runner.dart" 39 | import 'package:build_tool/build_tool.dart' as build_tool; 40 | void main(List args) { 41 | build_tool.runMain(args); 42 | } 43 | EOF 44 | 45 | # Dart run will not cache any package that has a path dependency, which 46 | # is the case for our build_tool_runner. So instead we precompile the package 47 | # ourselves. 48 | # To invalidate the cached kernel we use the hash of ls -LR of the build_tool 49 | # package directory. This should be good enough, as the build_tool package 50 | # itself is not meant to have any path dependencies. 51 | 52 | if [[ "$OSTYPE" == "darwin"* ]]; then 53 | PACKAGE_HASH=$(ls -lTR "$BUILD_TOOL_PKG_DIR" | shasum) 54 | else 55 | PACKAGE_HASH=$(ls -lR --full-time "$BUILD_TOOL_PKG_DIR" | shasum) 56 | fi 57 | 58 | PACKAGE_HASH_FILE=".package_hash" 59 | 60 | if [ -f "$PACKAGE_HASH_FILE" ]; then 61 | EXISTING_HASH=$(cat "$PACKAGE_HASH_FILE") 62 | if [ "$PACKAGE_HASH" != "$EXISTING_HASH" ]; then 63 | rm "$PACKAGE_HASH_FILE" 64 | fi 65 | fi 66 | 67 | # Run pub get if needed. 68 | if [ ! -f "$PACKAGE_HASH_FILE" ]; then 69 | "$DART" pub get --no-precompile 70 | "$DART" compile kernel bin/build_tool_runner.dart 71 | echo "$PACKAGE_HASH" > "$PACKAGE_HASH_FILE" 72 | fi 73 | 74 | set +e 75 | 76 | "$DART" bin/build_tool_runner.dill "$@" 77 | 78 | exit_code=$? 79 | 80 | # 253 means invalid snapshot version. 81 | if [ $exit_code == 253 ]; then 82 | "$DART" pub get --no-precompile 83 | "$DART" compile kernel bin/build_tool_runner.dart 84 | "$DART" bin/build_tool_runner.dill "$@" 85 | exit_code=$? 86 | fi 87 | 88 | exit $exit_code 89 | -------------------------------------------------------------------------------- /rust_builder/ios/Classes/dummy_file.c: -------------------------------------------------------------------------------- 1 | // This is an empty file to force CocoaPods to create a framework. 2 | -------------------------------------------------------------------------------- /rust_builder/ios/rust_builder.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint rust_builder.podspec` to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'rust_builder' 7 | s.version = '0.0.1' 8 | s.summary = 'A new Flutter FFI plugin project.' 9 | s.description = <<-DESC 10 | A new Flutter FFI plugin project. 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Your Company' => 'email@example.com' } 15 | 16 | # This will ensure the source files in Classes/ are included in the native 17 | # builds of apps using this FFI plugin. Podspec does not support relative 18 | # paths, so Classes contains a forwarder C file that relatively imports 19 | # `../src/*` so that the C sources can be shared among all target platforms. 20 | s.source = { :path => '.' } 21 | s.source_files = 'Classes/**/*' 22 | s.dependency 'Flutter' 23 | s.platform = :ios, '11.0' 24 | 25 | # Flutter.framework does not contain a i386 slice. 26 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } 27 | s.swift_version = '5.0' 28 | 29 | s.script_phase = { 30 | :name => 'Build Rust library', 31 | # First argument is relative path to the `rust` folder, second is name of rust library 32 | :script => 'sh "$PODS_TARGET_SRCROOT/../cargokit/build_pod.sh" ../../rust rust_lib', 33 | :execution_position => :before_compile, 34 | :input_files => ['${BUILT_PRODUCTS_DIR}/cargokit_phony'], 35 | # Let XCode know that the static library referenced in -force_load below is 36 | # created by this build step. 37 | :output_files => ["${BUILT_PRODUCTS_DIR}/librust_lib.a"], 38 | } 39 | s.pod_target_xcconfig = { 40 | 'DEFINES_MODULE' => 'YES', 41 | # Flutter.framework does not contain a i386 slice. 42 | 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', 43 | 'OTHER_LDFLAGS' => '-force_load ${BUILT_PRODUCTS_DIR}/librust_lib.a', 44 | } 45 | end 46 | -------------------------------------------------------------------------------- /rust_builder/linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The Flutter tooling requires that developers have CMake 3.10 or later 2 | # installed. You should not increase this version, as doing so will cause 3 | # the plugin to fail to compile for some customers of the plugin. 4 | cmake_minimum_required(VERSION 3.10) 5 | 6 | # Project-level configuration. 7 | set(PROJECT_NAME "rust_builder") 8 | project(${PROJECT_NAME} LANGUAGES CXX) 9 | 10 | include("../cargokit/cmake/cargokit.cmake") 11 | apply_cargokit(${PROJECT_NAME} ../../rust rust_lib "") 12 | 13 | # List of absolute paths to libraries that should be bundled with the plugin. 14 | # This list could contain prebuilt libraries, or libraries created by an 15 | # external build triggered from this build file. 16 | set(rust_builder_bundled_libraries 17 | "${${PROJECT_NAME}_cargokit_lib}" 18 | PARENT_SCOPE 19 | ) 20 | -------------------------------------------------------------------------------- /rust_builder/macos/Classes/dummy_file.c: -------------------------------------------------------------------------------- 1 | // This is an empty file to force CocoaPods to create a framework. 2 | -------------------------------------------------------------------------------- /rust_builder/macos/rust_builder.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint rust_builder.podspec` to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'rust_builder' 7 | s.version = '0.0.1' 8 | s.summary = 'A new Flutter FFI plugin project.' 9 | s.description = <<-DESC 10 | A new Flutter FFI plugin project. 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Your Company' => 'email@example.com' } 15 | 16 | # This will ensure the source files in Classes/ are included in the native 17 | # builds of apps using this FFI plugin. Podspec does not support relative 18 | # paths, so Classes contains a forwarder C file that relatively imports 19 | # `../src/*` so that the C sources can be shared among all target platforms. 20 | s.source = { :path => '.' } 21 | s.source_files = 'Classes/**/*' 22 | s.dependency 'FlutterMacOS' 23 | 24 | s.platform = :osx, '10.11' 25 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } 26 | s.swift_version = '5.0' 27 | 28 | s.script_phase = { 29 | :name => 'Build Rust library', 30 | # First argument is relative path to the `rust` folder, second is name of rust library 31 | :script => 'sh "$PODS_TARGET_SRCROOT/../cargokit/build_pod.sh" ../../rust rust_lib', 32 | :execution_position => :before_compile, 33 | :input_files => ['${BUILT_PRODUCTS_DIR}/cargokit_phony'], 34 | # Let XCode know that the static library referenced in -force_load below is 35 | # created by this build step. 36 | :output_files => ["${BUILT_PRODUCTS_DIR}/librust_lib.a"], 37 | } 38 | s.pod_target_xcconfig = { 39 | 'DEFINES_MODULE' => 'YES', 40 | # Flutter.framework does not contain a i386 slice. 41 | 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', 42 | 'OTHER_LDFLAGS' => '-force_load ${BUILT_PRODUCTS_DIR}/librust_lib.a', 43 | } 44 | end 45 | -------------------------------------------------------------------------------- /rust_builder/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: rust_builder 2 | description: "Utility to build Rust code" 3 | version: 0.0.1 4 | publish_to: none 5 | 6 | environment: 7 | sdk: '>=3.2.0 <4.0.0' 8 | flutter: '>=3.3.0' 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | plugin_platform_interface: ^2.0.2 14 | 15 | dev_dependencies: 16 | ffi: ^2.0.2 17 | ffigen: ^9.0.0 18 | flutter_test: 19 | sdk: flutter 20 | flutter_lints: ^2.0.0 21 | 22 | flutter: 23 | plugin: 24 | platforms: 25 | android: 26 | ffiPlugin: true 27 | ios: 28 | ffiPlugin: true 29 | linux: 30 | ffiPlugin: true 31 | macos: 32 | ffiPlugin: true 33 | windows: 34 | ffiPlugin: true 35 | -------------------------------------------------------------------------------- /rust_builder/windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /rust_builder/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The Flutter tooling requires that developers have a version of Visual Studio 2 | # installed that includes CMake 3.14 or later. You should not increase this 3 | # version, as doing so will cause the plugin to fail to compile for some 4 | # customers of the plugin. 5 | cmake_minimum_required(VERSION 3.14) 6 | 7 | # Project-level configuration. 8 | set(PROJECT_NAME "rust_builder") 9 | project(${PROJECT_NAME} LANGUAGES CXX) 10 | 11 | include("../cargokit/cmake/cargokit.cmake") 12 | apply_cargokit(${PROJECT_NAME} ../../../../../../rust rust_lib "") 13 | 14 | # List of absolute paths to libraries that should be bundled with the plugin. 15 | # This list could contain prebuilt libraries, or libraries created by an 16 | # external build triggered from this build file. 17 | set(rust_builder_bundled_libraries 18 | "${${PROJECT_NAME}_cargokit_lib}" 19 | PARENT_SCOPE 20 | ) 21 | -------------------------------------------------------------------------------- /screenshots/desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/screenshots/desktop.png -------------------------------------------------------------------------------- /setup.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:innosetup/innosetup.dart'; 4 | import 'package:version/version.dart'; 5 | 6 | void main() { 7 | InnoSetup( 8 | app: InnoSetupApp( 9 | name: 'localsend_rs', 10 | version: Version.parse('0.1.0'), 11 | publisher: 'tomzds9@gihub', 12 | urls: InnoSetupAppUrls( 13 | homeUrl: Uri.parse('https://github.com/tom8zds/localsend_rs'), 14 | ), 15 | ), 16 | files: InnoSetupFiles( 17 | executable: File('build/windows/x64/runner/Release/localsend_rs.exe'), 18 | location: Directory('build/windows/x64/runner/Release/'), 19 | ), 20 | name: const InnoSetupName('localsend_rs-setup'), 21 | location: InnoSetupInstallerDirectory( 22 | Directory('build/windows'), 23 | ), 24 | icon: InnoSetupIcon( 25 | File('assets/icon/logo.ico'), 26 | ), 27 | ).make(); 28 | } 29 | -------------------------------------------------------------------------------- /test_driver/integration_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:integration_test/integration_test_driver.dart'; 2 | 3 | Future main() => integrationDriver(); 4 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | void RegisterPlugins(flutter::PluginRegistry* registry) { 14 | DesktopDropPluginRegisterWithRegistrar( 15 | registry->GetRegistrarForPlugin("DesktopDropPlugin")); 16 | ScreenRetrieverPluginRegisterWithRegistrar( 17 | registry->GetRegistrarForPlugin("ScreenRetrieverPlugin")); 18 | WindowManagerPluginRegisterWithRegistrar( 19 | registry->GetRegistrarForPlugin("WindowManagerPlugin")); 20 | } 21 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | desktop_drop 7 | screen_retriever 8 | window_manager 9 | ) 10 | 11 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 12 | rust_builder 13 | ) 14 | 15 | set(PLUGIN_BUNDLED_LIBRARIES) 16 | 17 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 18 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 19 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 22 | endforeach(plugin) 23 | 24 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 25 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 27 | endforeach(ffi_plugin) 28 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) 64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0,0 67 | #endif 68 | 69 | #if defined(FLUTTER_VERSION) 70 | #define VERSION_AS_STRING FLUTTER_VERSION 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "github.tom8zds" "\0" 93 | VALUE "FileDescription", "localsend_rs" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "localsend_rs" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2023 github.tom8zds. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "localsend_rs.exe" "\0" 98 | VALUE "ProductName", "localsend_rs" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | this->Show(); 32 | }); 33 | 34 | // Flutter can complete the first frame before the "show window" callback is 35 | // registered. The following call ensures a frame is pending to ensure the 36 | // window is shown. It is a no-op if the first frame hasn't completed yet. 37 | flutter_controller_->ForceRedraw(); 38 | 39 | return true; 40 | } 41 | 42 | void FlutterWindow::OnDestroy() { 43 | if (flutter_controller_) { 44 | flutter_controller_ = nullptr; 45 | } 46 | 47 | Win32Window::OnDestroy(); 48 | } 49 | 50 | LRESULT 51 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 52 | WPARAM const wparam, 53 | LPARAM const lparam) noexcept { 54 | // Give Flutter, including plugins, an opportunity to handle window messages. 55 | if (flutter_controller_) { 56 | std::optional result = 57 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 58 | lparam); 59 | if (result) { 60 | return *result; 61 | } 62 | } 63 | 64 | switch (message) { 65 | case WM_FONTCHANGE: 66 | flutter_controller_->engine()->ReloadSystemFonts(); 67 | break; 68 | } 69 | 70 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 71 | } 72 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.Create(L"localsend_rs", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tom8zds/localsend_rs/09ab9c6596acfb34a692d3dffbdba62e3aaf6afb/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr) 51 | -1; // remove the trailing null character 52 | int input_length = (int)wcslen(utf16_string); 53 | std::string utf8_string; 54 | if (target_length <= 0 || target_length > utf8_string.max_size()) { 55 | return utf8_string; 56 | } 57 | utf8_string.resize(target_length); 58 | int converted_length = ::WideCharToMultiByte( 59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 60 | input_length, utf8_string.data(), target_length, nullptr, nullptr); 61 | if (converted_length == 0) { 62 | return std::string(); 63 | } 64 | return utf8_string; 65 | } 66 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates a win32 window with |title| that is positioned and sized using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size this function will scale the inputted width and height as 35 | // as appropriate for the default monitor. The window is invisible until 36 | // |Show| is called. Returns true if the window was created successfully. 37 | bool Create(const std::wstring& title, const Point& origin, const Size& size); 38 | 39 | // Show the current window. Returns true if the window was successfully shown. 40 | bool Show(); 41 | 42 | // Release OS resources associated with window. 43 | void Destroy(); 44 | 45 | // Inserts |content| into the window tree. 46 | void SetChildContent(HWND content); 47 | 48 | // Returns the backing Window handle to enable clients to set icon and other 49 | // window properties. Returns nullptr if the window has been destroyed. 50 | HWND GetHandle(); 51 | 52 | // If true, closing this window will quit the application. 53 | void SetQuitOnClose(bool quit_on_close); 54 | 55 | // Return a RECT representing the bounds of the current client area. 56 | RECT GetClientArea(); 57 | 58 | protected: 59 | // Processes and route salient window messages for mouse handling, 60 | // size change and DPI. Delegates handling of these to member overloads that 61 | // inheriting classes can handle. 62 | virtual LRESULT MessageHandler(HWND window, 63 | UINT const message, 64 | WPARAM const wparam, 65 | LPARAM const lparam) noexcept; 66 | 67 | // Called when CreateAndShow is called, allowing subclass window-related 68 | // setup. Subclasses should return false if setup fails. 69 | virtual bool OnCreate(); 70 | 71 | // Called when Destroy is called. 72 | virtual void OnDestroy(); 73 | 74 | private: 75 | friend class WindowClassRegistrar; 76 | 77 | // OS callback called by message pump. Handles the WM_NCCREATE message which 78 | // is passed when the non-client area is being created and enables automatic 79 | // non-client DPI scaling so that the non-client area automatically 80 | // responds to changes in DPI. All other messages are handled by 81 | // MessageHandler. 82 | static LRESULT CALLBACK WndProc(HWND const window, 83 | UINT const message, 84 | WPARAM const wparam, 85 | LPARAM const lparam) noexcept; 86 | 87 | // Retrieves a class instance pointer for |window| 88 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 89 | 90 | // Update the window frame's theme to match the system theme. 91 | static void UpdateTheme(HWND const window); 92 | 93 | bool quit_on_close_ = false; 94 | 95 | // window handle for top level window. 96 | HWND window_handle_ = nullptr; 97 | 98 | // window handle for hosted content. 99 | HWND child_content_ = nullptr; 100 | }; 101 | 102 | #endif // RUNNER_WIN32_WINDOW_H_ 103 | --------------------------------------------------------------------------------