Fix Tauri build args, split Android into matrix, track flutter/gui, update docs and contacts

This commit is contained in:
ospab 2026-05-28 15:01:41 +03:00
parent 6d9b7d8a26
commit 33145febbb
39 changed files with 9404 additions and 26 deletions

View File

@ -289,7 +289,7 @@ jobs:
run: |
npm install
cargo build -p ostp-tun-helper --release --target ${{ matrix.target }}
npm run build -- --no-bundle --target ${{ matrix.target }}
npx tauri build --no-bundle --target ${{ matrix.target }}
- name: Package Portable ZIP
shell: pwsh
@ -312,8 +312,17 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
build-android:
name: Build Android Client (Flutter)
name: Build Android Client (Flutter) - ${{ matrix.arch }}
runs-on: ubuntu-latest
strategy:
matrix:
include:
- arch: arm64-v8a
flutter_target: android-arm64
tun2socks_arch: linux-arm64
- arch: armeabi-v7a
flutter_target: android-arm
tun2socks_arch: linux-armv7
steps:
- uses: actions/checkout@v4
@ -347,42 +356,31 @@ jobs:
working-directory: ostp-flutter
run: |
# 1. Compile JNI
mkdir -p android/app/src/main/jniLibs/arm64-v8a
mkdir -p android/app/src/main/jniLibs/armeabi-v7a
mkdir -p android/app/src/main/jniLibs/${{ matrix.arch }}
cd ../ostp-jni
cargo ndk -t arm64-v8a -t armeabi-v7a -o "../ostp-flutter/android/app/src/main/jniLibs" build --release
cargo ndk -t ${{ matrix.arch }} -o "../ostp-flutter/android/app/src/main/jniLibs" build --release
cd ../ostp-flutter
# 2. Download tun2socks
if [ ! -f "android/app/src/main/jniLibs/arm64-v8a/libtun2socks.so" ]; then
curl -fsSL "https://github.com/xjasonlyu/tun2socks/releases/download/v2.6.0/tun2socks-linux-arm64.zip" -o "t2s64.zip"
unzip -o t2s64.zip -d t2s64_tmp
cp t2s64_tmp/tun2socks-linux-arm64 android/app/src/main/jniLibs/arm64-v8a/libtun2socks.so
rm -rf t2s64.zip t2s64_tmp
fi
if [ ! -f "android/app/src/main/jniLibs/armeabi-v7a/libtun2socks.so" ]; then
curl -fsSL "https://github.com/xjasonlyu/tun2socks/releases/download/v2.6.0/tun2socks-linux-armv7.zip" -o "t2s32.zip"
unzip -o t2s32.zip -d t2s32_tmp
cp t2s32_tmp/tun2socks-linux-armv7 android/app/src/main/jniLibs/armeabi-v7a/libtun2socks.so
rm -rf t2s32.zip t2s32_tmp
if [ ! -f "android/app/src/main/jniLibs/${{ matrix.arch }}/libtun2socks.so" ]; then
curl -fsSL "https://github.com/xjasonlyu/tun2socks/releases/download/v2.6.0/tun2socks-${{ matrix.tun2socks_arch }}.zip" -o "t2s.zip"
unzip -o t2s.zip -d t2s_tmp
cp t2s_tmp/tun2socks-${{ matrix.tun2socks_arch }} android/app/src/main/jniLibs/${{ matrix.arch }}/libtun2socks.so
rm -rf t2s.zip t2s_tmp
fi
# 3. Build Flutter APK
flutter build apk --release --split-per-abi
flutter build apk --release --target-platform ${{ matrix.flutter_target }}
# 4. Copy to output
cp build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk ostp-android-armv7.apk
cp build/app/outputs/flutter-apk/app-arm64-v8a-release.apk ostp-android-arm64.apk
cp build/app/outputs/flutter-apk/app-release.apk ostp-android-${{ matrix.arch }}.apk
- name: Upload to GitHub Release
if: ${{ startsWith(github.ref, 'refs/tags/') }}
uses: softprops/action-gh-release@v2
with:
files: |
ostp-flutter/ostp-android-armv7.apk
ostp-flutter/ostp-android-arm64.apk
files: ostp-flutter/ostp-android-${{ matrix.arch }}.apk
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

3
.gitignore vendored
View File

@ -1,7 +1,6 @@
/target/
/target_build/
/target_linux/
/ostp-gui/
/dist/
**/*.rs.bk
.idea/
@ -32,4 +31,4 @@ wintun.dll
# Dev notes (not for repo)
.ai-rules.md
turn-harvesting-idea.md
ostp-flutter/

View File

@ -139,8 +139,9 @@ Download pre-built binaries for your platform from [GitHub Releases](https://git
### 4. Connect via share link (one-liner)
```bash
./ostp ostp://ACCESS_KEY@server.com:50000
./ostp "ostp://ACCESS_KEY@server.com:50000?..."
```
> **Note**: Always wrap the `ostp://...` link in quotes (`"`) so your terminal doesn't misinterpret special characters like `&` or `?`.
---
@ -230,3 +231,10 @@ cargo test -p ostp-core -p ostp-server
Business Source License 1.1. Free for personal and non-commercial use.
Converts to MIT License on May 14, 2030.
---
## Contact
- **Telegram**: [@ospab0](https://t.me/ospab0)
- **Email**: gvoprgrg@gmail.com

45
ostp-flutter/.gitignore vendored Normal file
View File

@ -0,0 +1,45 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
/coverage/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

45
ostp-flutter/.metadata Normal file
View File

@ -0,0 +1,45 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "db50e20168db8fee486b9abf32fc912de3bc5b6a"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
base_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
- platform: android
create_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
base_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
- platform: ios
create_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
base_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
- platform: linux
create_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
base_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
- platform: macos
create_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
base_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
- platform: web
create_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
base_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
- platform: windows
create_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
base_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

17
ostp-flutter/README.md Normal file
View File

@ -0,0 +1,17 @@
# ostp_client
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter)
- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

14
ostp-flutter/android/.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View File

@ -0,0 +1,48 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.ospab.ostp_client"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.ospab.ostp_client"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = maxOf(flutter.minSdkVersion, 24)
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}
dependencies {
implementation("androidx.core:core-ktx:1.13.1")
}

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,74 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application
android:label="ostp_client"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:extractNativeLibs="true">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<service
android:name=".OstpVpnService"
android:permission="android.permission.BIND_VPN_SERVICE"
android:exported="false">
<intent-filter>
<action android:name="android.net.VpnService"/>
</intent-filter>
</service>
<!-- Quick Settings Tile -->
<service
android:name=".OstpTileService"
android:icon="@mipmap/ic_launcher"
android:label="OSTP VPN"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
android:exported="true">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE"/>
</intent-filter>
</service>
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View File

@ -0,0 +1,138 @@
package com.ospab.ostp_client
import android.content.Intent
import android.net.VpnService
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Canvas
import android.util.Base64
import java.io.ByteArrayOutputStream
class MainActivity : FlutterActivity() {
private val CHANNEL = "com.ospab.ostp/vpn"
private val VPN_REQUEST_CODE = 0x0F
private var pendingConfigJson: String? = null
private fun getAppIconBase64(pm: PackageManager, appInfo: ApplicationInfo): String? {
try {
val drawable = pm.getApplicationIcon(appInfo)
val width = 96
val height = 96
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, width, height)
drawable.draw(canvas)
val outputStream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 90, outputStream)
val byteArray = outputStream.toByteArray()
return Base64.encodeToString(byteArray, Base64.NO_WRAP)
} catch (e: Throwable) {
return null
}
}
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"saveConfig" -> {
val configJson = call.argument<String>("configJson")
val prefs = getSharedPreferences("OstpPrefs", android.content.Context.MODE_PRIVATE)
prefs.edit().putString("latest_config_json", configJson).apply()
result.success(true)
}
"startTunnel" -> {
pendingConfigJson = call.argument<String>("configJson")
val intent = VpnService.prepare(this)
if (intent != null) {
startActivityForResult(intent, VPN_REQUEST_CODE)
result.success(true)
} else {
startVpnService()
result.success(true)
}
}
"stopTunnel" -> {
try {
val intent = Intent(this, OstpVpnService::class.java)
intent.action = "STOP"
startService(intent)
result.success(true)
} catch (e: Throwable) {
result.error("ERROR", e.message, null)
}
}
"getLogs" -> {
try {
val logs = net.ostp.client.OstpClientSdk.getLogs()
result.success(logs ?: "[]")
} catch (e: Throwable) {
result.error("ERROR", e.message ?: "Unknown JNI Error", null)
}
}
"clearLogs" -> {
try {
net.ostp.client.OstpClientSdk.getLogs() // Drain
result.success(true)
} catch (e: Throwable) {
result.error("ERROR", e.message, null)
}
}
"isRunning" -> {
result.success(OstpVpnService.isRunning)
}
"getMetrics" -> {
try {
val metrics = net.ostp.client.OstpClientSdk.getMetrics()
result.success(metrics ?: "{}")
} catch (e: Throwable) {
result.error("ERROR", e.message, null)
}
}
"getInstalledApps" -> {
try {
val pm = packageManager
val apps = pm.getInstalledApplications(PackageManager.GET_META_DATA)
val list = apps.map { app ->
val isSystem = ((app.flags and ApplicationInfo.FLAG_SYSTEM) != 0) &&
(pm.getLaunchIntentForPackage(app.packageName) == null)
val iconBase64 = getAppIconBase64(pm, app)
mapOf(
"name" to pm.getApplicationLabel(app).toString(),
"package" to app.packageName,
"isSystem" to isSystem,
"icon" to (iconBase64 ?: "")
)
}
result.success(list)
} catch (e: Exception) {
result.error("ERROR", e.message, null)
}
}
else -> result.notImplemented()
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == VPN_REQUEST_CODE && resultCode == RESULT_OK) {
startVpnService()
}
super.onActivityResult(requestCode, resultCode, data)
}
private fun startVpnService() {
val intent = Intent(this, OstpVpnService::class.java)
intent.action = "START"
if (pendingConfigJson != null) {
intent.putExtra("configJson", pendingConfigJson)
}
startService(intent)
}
}

View File

@ -0,0 +1,93 @@
package com.ospab.ostp_client
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Build
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import androidx.annotation.Keep
import androidx.annotation.RequiresApi
@Keep
@RequiresApi(Build.VERSION_CODES.N)
class OstpTileService : TileService() {
override fun onStartListening() {
super.onStartListening()
updateTile()
}
override fun onClick() {
super.onClick()
if (OstpVpnService.isRunning) {
// Отключить VPN
val stopIntent = Intent(this, OstpVpnService::class.java).apply { action = "STOP" }
startService(stopIntent)
// Обновим плитку сразу
qsTile?.state = Tile.STATE_INACTIVE
qsTile?.label = "OSTP VPN"
qsTile?.updateTile()
} else {
// Включить VPN напрямую
val prefs = getSharedPreferences("OstpPrefs", Context.MODE_PRIVATE)
val configJson = prefs.getString("latest_config_json", null)
if (configJson != null) {
val startIntent = Intent(this, OstpVpnService::class.java).apply {
action = "START"
putExtra("configJson", configJson)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(startIntent)
} else {
startService(startIntent)
}
qsTile?.state = Tile.STATE_ACTIVE
qsTile?.label = "OSTP VPN"
qsTile?.updateTile()
} else {
// Если конфигурация еще не сохранена, открыть приложение
val appIntent = packageManager.getLaunchIntentForPackage(packageName)?.apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
putExtra("tile_connect", true)
}
if (appIntent != null) {
startActivityAndCollapse(appIntent)
}
}
}
}
private fun updateTile() {
val tile = qsTile ?: return
if (OstpVpnService.isRunning) {
tile.label = "OSTP VPN"
tile.state = Tile.STATE_ACTIVE
} else {
tile.label = "OSTP VPN"
tile.state = Tile.STATE_INACTIVE
}
tile.updateTile()
}
companion object {
/**
* Запрашивает обновление плитки быстрых настроек.
* Вызывается из OstpVpnService при изменении состояния.
*/
@Keep
fun requestListeningState(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
try {
requestListeningState(
context,
ComponentName(context, OstpTileService::class.java)
)
} catch (e: Exception) {
// Плитка может быть не добавлена в панель — это нормально
}
}
}
}
}

View File

@ -0,0 +1,261 @@
package com.ospab.ostp_client
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Intent
import android.content.pm.ServiceInfo
import android.net.VpnService
import android.os.Build
import android.os.ParcelFileDescriptor
import android.os.PowerManager
import android.util.Log
import net.ostp.client.OstpClientSdk
import java.io.IOException
import androidx.annotation.Keep
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
@Keep
class OstpVpnService : VpnService() {
@Keep
companion object {
@Keep
var isRunning = false
@Keep
var instance: OstpVpnService? = null
private const val NOTIF_ID = 1001
private const val CHANNEL_ID = "ostp_vpn_channel"
private const val WAKE_LOCK_TAG = "ostp:vpn_wakelock"
/** Called from Kotlin OstpClientSdk to protect VPN sockets from the VPN itself. */
@Keep
@JvmStatic
fun protectSocket(fd: Int): Boolean {
return instance?.protect(fd) ?: false
}
/**
* Called by OstpClientSdk.notifyNetworkChanged() JNI thunk.
*/
@Keep
@JvmStatic
fun onNetworkChanged() {
android.util.Log.d("OstpVpnService", "onNetworkChanged() signaled to Rust bridge")
}
}
private var vpnInterface: ParcelFileDescriptor? = null
private var wakeLock: PowerManager.WakeLock? = null
override fun onCreate() {
super.onCreate()
instance = this
createNotificationChannel()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val action = intent?.action
if (action == "START") {
val configJson = intent.getStringExtra("configJson") ?: return START_NOT_STICKY
// Launch foreground immediately so Android doesn't kill us
startForeground(NOTIF_ID, buildNotification(connecting = true))
startVpn(configJson)
} else if (action == "STOP") {
stopVpn()
}
return START_STICKY
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"OSTP VPN",
NotificationManager.IMPORTANCE_LOW
).apply {
description = "OSTP VPN connection status"
setShowBadge(false)
}
val nm = getSystemService(NotificationManager::class.java)
nm.createNotificationChannel(channel)
}
}
private fun buildNotification(connecting: Boolean): Notification {
val stopIntent = PendingIntent.getService(
this,
0,
Intent(this, OstpVpnService::class.java).apply { action = "STOP" },
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val openIntent = PendingIntent.getActivity(
this,
1,
packageManager.getLaunchIntentForPackage(packageName)
?.apply { addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) },
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val (statusText, actionLabel) = if (connecting) {
Pair("Подключение...", "Отмена")
} else {
Pair("Подключено", "Отключить")
}
return NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("OSTP VPN")
.setContentText(statusText)
.setSmallIcon(android.R.drawable.ic_lock_lock)
.setOngoing(true)
.setShowWhen(false)
.setContentIntent(openIntent)
.addAction(android.R.drawable.ic_delete, actionLabel, stopIntent)
.setPriority(NotificationCompat.PRIORITY_LOW)
.build()
}
fun updateNotification(connected: Boolean) {
try {
val nm = NotificationManagerCompat.from(this)
nm.notify(NOTIF_ID, buildNotification(connecting = !connected))
} catch (e: Throwable) {
Log.e("OstpVpnService", "Failed to update notification", e)
}
// Refresh Quick Settings tile state
OstpTileService.requestListeningState(applicationContext)
}
private fun acquireWakeLock() {
if (wakeLock == null) {
val pm = getSystemService(POWER_SERVICE) as PowerManager
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG)
wakeLock?.acquire(24 * 60 * 60 * 1000L) // Max 24h
Log.d("OstpVpnService", "WakeLock acquired")
}
}
private fun releaseWakeLock() {
try {
wakeLock?.let {
if (it.isHeld) it.release()
}
wakeLock = null
Log.d("OstpVpnService", "WakeLock released")
} catch (e: Throwable) {
Log.e("OstpVpnService", "Error releasing WakeLock", e)
}
}
private fun startVpn(configJson: String) {
if (vpnInterface != null) return
acquireWakeLock()
try {
val json = org.json.JSONObject(configJson)
val dnsServer = json.optString("dns_server", "1.1.1.1")
val localProxy = json.optJSONObject("local_proxy")?.optString("bind_addr", "127.0.0.1:1088") ?: "127.0.0.1:1088"
val builder = Builder()
.setSession("OSTP Tunnel")
.addAddress("10.1.0.2", 24)
.addAddress("fd00:1:fd00:1:fd00:1:fd00:1", 128)
.addRoute("0.0.0.0", 0)
.addRoute("::", 0)
.addDnsServer(dnsServer)
.setMtu(1300)
try {
builder.allowFamily(android.system.OsConstants.AF_INET)
builder.allowFamily(android.system.OsConstants.AF_INET6)
} catch (e: Throwable) { }
val appRules = json.optJSONObject("app_rules")
val mode = appRules?.optString("mode", "bypass") ?: "bypass"
val packages = appRules?.optJSONArray("packages")
if (mode == "proxy") {
if (packages != null) {
for (i in 0 until packages.length()) {
val pkg = packages.getString(i)
try {
builder.addAllowedApplication(pkg)
} catch (e: Throwable) {
Log.e("OstpVpnService", "Failed to add allowed application $pkg: $e")
}
}
}
} else {
try {
builder.addDisallowedApplication(applicationContext.packageName)
} catch (e: Throwable) {
Log.e("OstpVpnService", "Failed to disallow our own package: $e")
}
if (packages != null) {
for (i in 0 until packages.length()) {
val pkg = packages.getString(i)
try {
builder.addDisallowedApplication(pkg)
} catch (e: Throwable) {
Log.e("OstpVpnService", "Failed to add disallowed application $pkg: $e")
}
}
}
}
vpnInterface = builder.establish()
val fd = vpnInterface?.fd ?: throw Exception("Failed to get VPN FD")
// CRITICAL: Clear O_CLOEXEC so the child process inherits the TUN file descriptor
try {
android.system.Os.fcntlInt(vpnInterface!!.fileDescriptor, android.system.OsConstants.F_SETFD, 0)
} catch (e: Throwable) {
Log.e("OstpVpnService", "Failed to clear O_CLOEXEC", e)
}
val t2sBin = applicationInfo.nativeLibraryDir + "/libtun2socks.so"
val success = OstpClientSdk.startClient(configJson, fd, t2sBin, localProxy)
if (success) {
Log.i("OstpVpnService", "OSTP Rust Core & tun2socks started successfully")
isRunning = true
updateNotification(connected = true)
} else {
Log.e("OstpVpnService", "Failed to start OSTP Rust Core & tun2socks")
stopVpn()
}
} catch (e: Throwable) {
Log.e("OstpVpnService", "Error starting VPN", e)
stopVpn()
}
}
private fun stopVpn() {
isRunning = false
releaseWakeLock()
try {
OstpClientSdk.stopClient()
vpnInterface?.close()
vpnInterface = null
} catch (e: IOException) {
Log.e("OstpVpnService", "Error closing VPN interface", e)
}
stopForeground(true)
OstpTileService.requestListeningState(applicationContext)
stopSelf()
}
override fun onDestroy() {
super.onDestroy()
instance = null
stopVpn()
}
}

View File

@ -0,0 +1,43 @@
package net.ostp.client
import androidx.annotation.Keep
@Keep
object OstpClientSdk {
init {
System.loadLibrary("ostp_jni")
}
@Keep
@JvmStatic
fun protectSocket(fd: Int): Boolean {
val service = com.ospab.ostp_client.OstpVpnService.instance
if (service != null) {
val res = service.protect(fd)
android.util.Log.i("OstpClientSdk", "VpnService.protect(socketFd=$fd) -> success=$res")
return res
}
android.util.Log.e("OstpClientSdk", "VpnService instance is null! Cannot protect socketFd=$fd")
return false
}
@Keep
@JvmStatic
external fun startClient(configJson: String, fd: Int, t2sBinPath: String, localProxy: String): Boolean
@Keep
@JvmStatic
external fun stopClient(): Boolean
@Keep
@JvmStatic
external fun getMetrics(): String
@Keep
@JvmStatic
external fun getLogs(): String
@Keep
@JvmStatic
external fun addLog(logMsg: String)
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,24 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

View File

@ -0,0 +1,2 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true

View File

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip

View File

@ -0,0 +1,26 @@
pluginManagement {
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.11.1" apply false
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
}
include(":app")

48
ostp-flutter/build.ps1 Normal file
View File

@ -0,0 +1,48 @@
$ErrorActionPreference = "Stop"
Write-Host "==============================================" -ForegroundColor Cyan
Write-Host " OSTP Android App Release Build Pipeline " -ForegroundColor Cyan
Write-Host "==============================================" -ForegroundColor Cyan
# Step 1: Run JNI build script to compile Rust core and download tun2socks
Write-Host ""
Write-Host "[1/3] Compiling Rust JNI Core & Downloading tun2socks..." -ForegroundColor Yellow
$jniScript = Join-Path $PSScriptRoot "build_android_jni.ps1"
if (Test-Path $jniScript) {
& $jniScript
} else {
Write-Error "Could not find build_android_jni.ps1 at $jniScript"
exit 1
}
# Step 2: Build Flutter APK in release mode
Write-Host ""
Write-Host "[2/3] Compiling Flutter Application in Release Mode..." -ForegroundColor Yellow
Push-Location $PSScriptRoot
try {
& flutter build apk --release --target-platform android-arm,android-arm64
} catch {
Write-Host "[ERROR] Flutter build failed! Make sure Flutter SDK is installed and configured in your PATH." -ForegroundColor Red
Pop-Location
exit 1
}
Pop-Location
# Step 3: Copy and rename the final release APK next to this script
Write-Host ""
Write-Host "[3/3] Copying and packaging release APK..." -ForegroundColor Yellow
$apkPath = Join-Path $PSScriptRoot "build\app\outputs\flutter-apk\app-release.apk"
$destPath = Join-Path $PSScriptRoot "ostp-client-release.apk"
if (Test-Path $apkPath) {
Copy-Item -Path $apkPath -Destination $destPath -Force
Write-Host ""
Write-Host "==============================================" -ForegroundColor Green
Write-Host " SUCCESS! Build completed successfully! " -ForegroundColor Green
Write-Host " Release APK copied to: " -ForegroundColor Green
Write-Host " $destPath" -ForegroundColor White
Write-Host "==============================================" -ForegroundColor Green
} else {
Write-Host "[ERROR] Release APK was not found at expected path: $apkPath" -ForegroundColor Red
exit 1
}

View File

@ -0,0 +1,35 @@
$ErrorActionPreference = "Stop"
Write-Host "Building OSTP JNI for Android (arm64-v8a and armeabi-v7a)..."
$jniLibs = "$PSScriptRoot\android\app\src\main\jniLibs"
New-Item -ItemType Directory -Force -Path "$jniLibs\arm64-v8a" | Out-Null
New-Item -ItemType Directory -Force -Path "$jniLibs\armeabi-v7a" | Out-Null
Push-Location "$PSScriptRoot\..\ostp-jni"
Write-Host "Compiling for aarch64-linux-android and armv7-linux-androideabi..."
cargo ndk -t arm64-v8a -t armeabi-v7a -o "$jniLibs" build --release
$tun2socksArm64 = "$jniLibs\arm64-v8a\libtun2socks.so"
$tun2socksArmv7 = "$jniLibs\armeabi-v7a\libtun2socks.so"
if (-not (Test-Path $tun2socksArm64)) {
Write-Host "Downloading tun2socks for arm64-v8a..."
Invoke-WebRequest -Uri "https://github.com/xjasonlyu/tun2socks/releases/download/v2.6.0/tun2socks-linux-arm64.zip" -OutFile "$jniLibs\t2s64.zip"
Expand-Archive "$jniLibs\t2s64.zip" "$jniLibs\t2s64_tmp" -Force
Copy-Item "$jniLibs\t2s64_tmp\tun2socks-linux-arm64" $tun2socksArm64 -Force
Remove-Item "$jniLibs\t2s64.zip", "$jniLibs\t2s64_tmp" -Recurse -Force
}
if (-not (Test-Path $tun2socksArmv7)) {
Write-Host "Downloading tun2socks for armeabi-v7a..."
Invoke-WebRequest -Uri "https://github.com/xjasonlyu/tun2socks/releases/download/v2.6.0/tun2socks-linux-armv7.zip" -OutFile "$jniLibs\t2s32.zip"
Expand-Archive "$jniLibs\t2s32.zip" "$jniLibs\t2s32_tmp" -Force
Copy-Item "$jniLibs\t2s32_tmp\tun2socks-linux-armv7" $tun2socksArmv7 -Force
Remove-Item "$jniLibs\t2s32.zip", "$jniLibs\t2s32_tmp" -Recurse -Force
}
Pop-Location
Write-Host "Done! The .so files have been copied to $jniLibs"

2077
ostp-flutter/lib/main.dart Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

354
ostp-flutter/pubspec.lock Normal file
View File

@ -0,0 +1,354 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
url: "https://pub.dev"
source: hosted
version: "2.13.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
characters:
dependency: transitive
description:
name: characters
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev"
source: hosted
version: "1.4.1"
clock:
dependency: transitive
description:
name: clock
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
url: "https://pub.dev"
source: hosted
version: "1.0.9"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
url: "https://pub.dev"
source: hosted
version: "6.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
lints:
dependency: transitive
description:
name: lints
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
url: "https://pub.dev"
source: hosted
version: "6.1.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev"
source: hosted
version: "0.12.19"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev"
source: hosted
version: "0.13.0"
meta:
dependency: transitive
description:
name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.17.0"
mobile_scanner:
dependency: "direct main"
description:
name: mobile_scanner
sha256: d234581c090526676fd8fab4ada92f35c6746e3fb4f05a399665d75a399fb760
url: "https://pub.dev"
source: hosted
version: "5.2.3"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
url: "https://pub.dev"
source: hosted
version: "2.2.1"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
platform:
dependency: transitive
description:
name: platform
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf
url: "https://pub.dev"
source: hosted
version: "2.5.5"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53
url: "https://pub.dev"
source: hosted
version: "2.4.23"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
url: "https://pub.dev"
source: hosted
version: "2.5.6"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9"
url: "https://pub.dev"
source: hosted
version: "2.4.2"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
source_span:
dependency: transitive
description:
name: source_span
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
url: "https://pub.dev"
source: hosted
version: "1.10.2"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
url: "https://pub.dev"
source: hosted
version: "1.12.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev"
source: hosted
version: "0.7.10"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
url: "https://pub.dev"
source: hosted
version: "2.2.0"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
url: "https://pub.dev"
source: hosted
version: "15.2.0"
web:
dependency: transitive
description:
name: web
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
sdks:
dart: ">=3.11.4 <4.0.0"
flutter: ">=3.35.0"

91
ostp-flutter/pubspec.yaml Normal file
View File

@ -0,0 +1,91 @@
name: ostp_client
description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
environment:
sdk: ^3.11.4
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
shared_preferences: ^2.5.5
mobile_scanner: ^5.0.0
dev_dependencies:
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^6.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/to/asset-from-package
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package

View File

@ -0,0 +1,30 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:ostp_client/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}

51
ostp-gui/build_dist.js Normal file
View File

@ -0,0 +1,51 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const workspaceRoot = path.resolve(__dirname, '..');
const distDir = path.join(__dirname, 'dist');
if (!fs.existsSync(distDir)) {
fs.mkdirSync(distDir, { recursive: true });
}
const filesToCopy = [
{
src: path.join(__dirname, 'src-tauri', 'target', 'release', 'ostp-gui.exe'),
dest: path.join(distDir, 'ostp-gui.exe')
},
{
src: path.join(workspaceRoot, 'target', 'release', 'ostp-tun-helper.exe'),
dest: path.join(distDir, 'ostp-tun-helper.exe')
},
{
src: path.join(workspaceRoot, 't2s_tmp', 'tun2socks-windows-amd64.exe'),
dest: path.join(distDir, 'tun2socks.exe')
},
{
src: path.join(workspaceRoot, 'target', 'release', 'wintun.dll'),
dest: path.join(distDir, 'wintun.dll')
}
];
let success = true;
for (const file of filesToCopy) {
if (fs.existsSync(file.src)) {
fs.copyFileSync(file.src, file.dest);
console.log(`Copied ${path.basename(file.src)} -> dist/${path.basename(file.dest)}`);
} else {
console.error(`Error: Missing file ${file.src}`);
success = false;
}
}
if (success) {
console.log('\nSuccess! All files have been gathered in the ostp-gui/dist/ directory.');
} else {
console.error('\nSome files were missing. Make sure you have run the build first!');
process.exit(1);
}

5750
ostp-gui/src-tauri/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff