Compare commits

..

41 Commits

Author SHA1 Message Date
Zane Schepke e84d7e354c feat: add amnezia side-by-side 2024-05-05 00:49:31 -04:00
Zane Schepke 681b066d99 Update issue-workflow.yml 2024-05-03 22:54:35 -04:00
Zane Schepke ad53fca928 Create publish-workflow.yml 2024-05-03 22:53:42 -04:00
Zane Schepke f7e4b7e8ef Update issue-workflow.yml 2024-05-03 22:42:44 -04:00
Zane Schepke b04e8e7f60 Update issue-workflow.yml 2024-05-03 22:38:37 -04:00
Zane Schepke cbee5cfd1b Create issue-workflow.yml 2024-05-03 22:26:05 -04:00
Weblate (bot) 440fe6ceda Translations update from Hosted Weblate (#182)
Co-authored-by: Dominik Thalhammer <dominik@thalhammer.it>
2024-04-29 00:00:28 -04:00
Zane Schepke 27def018bd ci: fix spacing 2024-04-27 21:50:13 -04:00
Zane Schepke 16979dbb2b Update ic_channel.xml 2024-04-26 01:50:08 -04:00
Hosted Weblate 5d8190628d Update translation files
Updated by "Remove blank strings" hook in Weblate.

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/
2024-04-26 01:50:08 -04:00
Anonymous 6d597a235d Translated using Weblate (Spanish)
Currently translated at 100.0% (159 of 159 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/es/
2024-04-26 01:49:49 -04:00
Anonymous 61fd2f01b9 Translated using Weblate (German)
Currently translated at 49.0% (78 of 159 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/de/
2024-04-26 01:49:08 -04:00
Anonymous 914a641977 Translated using Weblate (Russian)
Currently translated at 3.1% (5 of 159 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/ru/
2024-04-26 01:49:08 -04:00
Anonymous c97a3afbc4 Translated using Weblate (Turkish)
Currently translated at 100.0% (159 of 159 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/tr/
2024-04-26 01:49:08 -04:00
gallegonovato fadc2d1562 Translated using Weblate (Spanish)
Currently translated at 100.0% (159 of 159 strings)

Translation: WG Tunnel/strings
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/strings/es/
2024-04-26 01:49:08 -04:00
Languages add-on 106ab76b82 Added translation using Weblate (Spanish) 2024-04-26 01:48:39 -04:00
Weblate (bot) 48b3f60b37 Translations update from Hosted Weblate (#177)
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Gabriel Franz <gabrielfranz31@gmail.com>
Co-authored-by: Saratoga79 <ordizi79@gmail.com>
2024-04-24 03:49:57 -04:00
WINZORT e7f5cee6dd Add fastlane tr (#173) 2024-04-22 23:21:19 -04:00
Zane Schepke 5e01fd6c85 Merge pull request #175 from zaneschepke/i18n
Translations update from Hosted Weblate
2024-04-22 23:18:46 -04:00
Dominik Thalhammer dcbd72c6f6 Translated using Weblate (German)
Currently translated at 40.9% (9 of 22 strings)

Translation: WG Tunnel/fastlane
Translate-URL: https://hosted.weblate.org/projects/wg-tunnel/fastlane/de/
2024-04-22 13:18:21 +02:00
Languages add-on f27aa1452a Added translation using Weblate (German) 2024-04-22 07:52:45 +02:00
Languages add-on 82c3521c58 Added translation using Weblate (Russian) 2024-04-21 06:19:53 +02:00
Zane Schepke c6535ffe7e chore: update chats 2024-04-20 18:36:40 -04:00
Zane Schepke be5fe94ce6 chore: cleanup unused strings 2024-04-20 17:42:14 -04:00
Zane Schepke 1e905c1cfb chore: update README.md 2024-04-20 17:09:36 -04:00
Zane Schepke a549c111aa Merge pull request #170 from yurtpage/i18n
i18n
2024-04-20 16:28:15 -04:00
Zane Schepke 5ab344044e chore: add Weblate details to README 2024-04-20 14:30:21 -04:00
Zane Schepke 7030f68548 chore: update README 2024-04-20 13:57:33 -04:00
Zane Schepke ecd51b6fe5 chore: add code of conduct 2024-04-20 13:52:28 -04:00
Yurt Page 3e4f8e0791 fastlane i18n ru
Signed-off-by: Yurt Page <yurtpage@gmail.com>
2024-04-19 18:28:58 +03:00
Zane Schepke e940a0dbc5 Merge pull request #167 from mikropsoft/main
Improve tr locales
2024-04-18 22:12:21 -04:00
WINZORT 8e233475df Update strings.xml 2024-04-17 17:41:49 +03:00
WINZORT 24789826ad Update strings.xml 2024-04-17 17:38:00 +03:00
WINZORT 787e0d1a71 Update strings.xml 2024-04-17 17:37:22 +03:00
WINZORT 914ef53ef0 Improve tr locales 2024-04-17 17:23:27 +03:00
Zane Schepke a569974beb add Turkish localization 2024-04-16 16:13:41 -04:00
Zane Schepke 87bc89b6f1 Merge branch 'main' of https://github.com/zaneschepke/wgtunnel 2024-04-16 00:50:43 -04:00
Zane Schepke c343220e96 Merge pull request #156 from mikropsoft/main
Add tr locales
2024-04-16 00:41:14 -04:00
Zane Schepke 5447ec73f7 fix: stop tunnel regression
Fixes regression where tunnel is stuck in on state after x amount of toggles. Closes #163

Adds obfuscation of potentially sensitive data from logs.
Closes #160

Adds hiding of FAB on scroll to allow users to toggle tunnels when they have many tunnel configs.
Closes #161
2024-04-16 00:14:06 -04:00
Zane Schepke a2b8eb5b0b ci: update release wording 2024-04-06 11:06:19 -04:00
WINZORT ee3fcabcf1 Add tr locales 2024-04-06 15:39:46 +03:00
118 changed files with 2053 additions and 421 deletions
+22
View File
@@ -0,0 +1,22 @@
# Contributor Code of Conduct
## Pledge
We as individuals involved in this project, pledge to participate in this
community in a respectful, constructive, and civil manner as we work towards a common goal
of delivering free, open source, and value adding software for all.
## Standard
The standard for this community is the Golden Rule.
> “Do unto others as you would have them do unto you.”
## Scope
This Code of Conduct applies to all spaces related to WG Tunnel.
## Incidents or Concerns
For any incidents or concerns, reach out to Zane at
<support@zaneschepke.com>.
+20
View File
@@ -0,0 +1,20 @@
name: Issue Updates Workflow
on:
issues:
types: [opened, closed, reopened]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Send Telegram Message
run: |
msg_text='${{ github.actor }} updated an issue:
status: ${{ github.event.issue.state }} - #${{ github.event.issue.number }} ${{ github.event.issue.title }}
${{ github.event.issue.url }}'
curl -s -X POST 'https://api.telegram.org/bot${{ secrets.TELEGRAM_TOKEN }}/sendMessage' \
-d "chat_id=${{ secrets.TELEGRAM_TO }}&text=${msg_text}&message_thread_id=${{ secrets.TELEGRAM_TOPIC }}"
+5 -4
View File
@@ -1,4 +1,3 @@
# name of the workflow
name: Android CI Tag Deployment (Pre-release)
on:
@@ -97,7 +96,7 @@ jobs:
- name: Get checksum
id: checksum
run: echo "checksum=$(apksigner verify -print-certs ${{ steps.apk-path.outputs.path }} | grep -Po "(?<=SHA-256 digest:) .*")" | awk '{$1=$1};1' >> $GITHUB_OUTPUT
run: echo "checksum=$(apksigner verify -print-certs ${{ steps.apk-path.outputs.path }} | grep -Po "(?<=SHA-256 digest:) .*" | tr -d "[:blank:]")" >> $GITHUB_OUTPUT
- name: Append checksum
id: append_checksum
@@ -105,8 +104,10 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
body: >
<br /> SHA256 Checksum: <br /> ```${{ steps.checksum.outputs.checksum }}```
body: |
SHA256 fingerprint:
```${{ steps.checksum.outputs.checksum }}```
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
draft: false
+21
View File
@@ -0,0 +1,21 @@
name: Release Updates Workflow
on:
release:
types: [published]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Send Telegram Message
run: |
msg_text='${{ github.actor }} published a new release:
Release: ${{ github.event.release.tag_name }}
${{ github.event.release.body }}
${{ github.event.release.url }}'
curl -s -X POST 'https://api.telegram.org/bot${{ secrets.TELEGRAM_TOKEN }}/sendMessage' \
-d "chat_id=${{ secrets.TELEGRAM_TO }}&text=${msg_text}&message_thread_id=${{ secrets.TELEGRAM_TOPIC }}"
+5 -3
View File
@@ -103,7 +103,7 @@ jobs:
- name: Get checksum
id: checksum
run: echo "checksum=$(apksigner verify -print-certs ${{ steps.apk-path.outputs.path }} | grep -Po "(?<=SHA-256 digest:) .*")" | awk '{$1=$1};1' >> $GITHUB_OUTPUT
run: echo "checksum=$(apksigner verify -print-certs ${{ steps.apk-path.outputs.path }} | grep -Po "(?<=SHA-256 digest:) .*" | tr -d "[:blank:]")" >> $GITHUB_OUTPUT
- name: Append checksum
id: append_checksum
@@ -111,8 +111,10 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
body: >
<br /> SHA256 Checksum: <br /> ```${{ steps.checksum.outputs.checksum }}```
body: |
SHA256 fingerprint:
```${{ steps.checksum.outputs.checksum }}```
tag_name: ${{ github.ref_name }}
name: ${{ github.ref_name }}
draft: false
+18 -3
View File
@@ -4,8 +4,9 @@ WG Tunnel
<div align="center">
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Discord Chat](https://img.shields.io/discord/1108285024631001111.svg)](https://discord.gg/rbRRNh6H7V)
[![Discord](https://img.shields.io/badge/Discord-%235865F2.svg?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/rbRRNh6H7V)
[![X Community](https://img.shields.io/badge/X-000000?style=for-the-badge&logo=x&logoColor=white)](https://twitter.com/i/communities/1780655267685736818)
[![Telegram](https://img.shields.io/badge/Telegram-2CA5E0?style=for-the-badge&logo=telegram&logoColor=white)](https://t.me/wgtunnel)
</div>
@@ -62,13 +63,27 @@ and on while on different networks. This app was created to offer a free solutio
* Battery preservation measures
* Restart tunnel on ping failure (beta)
## Docs (WIP)
## Docs
Basic documentation of the feature and behaviors of this app can be
found [here](https://zaneschepke.com/wgtunnel-docs/overview.html).
The repository for these docs can be found [here](https://github.com/zaneschepke/wgtunnel-docs).
## Contributing
Any contributions in the form of feedback, issues, code, or translations are welcome and much appreciated!
Please read the [code of conduct](https://github.com/zaneschepke/wgtunnel?tab=coc-ov-file#contributor-code-of-conduct) before contributing.
## Translation
This app is using [Weblate](https://weblate.org) to assist with translations.
Help translate WG Tunnel into your language at [Hosted Weblate](https://hosted.weblate.org/engage/wg-tunnel/).\
[![Translation status](https://hosted.weblate.org/widgets/wg-tunnel/-/multi-auto.svg)](https://hosted.weblate.org/engage/wg-tunnel/)
## Building
```
+5 -2
View File
@@ -12,6 +12,10 @@ android {
namespace = Constants.APP_ID
compileSdk = Constants.TARGET_SDK
androidResources {
generateLocaleConfig = true
}
defaultConfig {
applicationId = Constants.APP_ID
minSdk = Constants.MIN_SDK
@@ -25,8 +29,6 @@ android {
getByName("debug").assets.srcDirs(files("$projectDir/schemas")) // Room
}
resourceConfigurations.addAll(listOf("en"))
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { useSupportLibrary = true }
}
@@ -160,6 +162,7 @@ dependencies {
// get tunnel lib from github packages or mavenLocal
implementation(libs.tunnel)
implementation(libs.amneziawg.android)
coreLibraryDesugaring(libs.desugar.jdk.libs)
// logging
@@ -0,0 +1,190 @@
{
"formatVersion": 1,
"database": {
"version": 8,
"identityHash": "b4d4a7c489f6b2f0d3aa4fa6f37b4935",
"entities": [
{
"tableName": "Settings",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `is_tunnel_enabled` INTEGER NOT NULL, `is_tunnel_on_mobile_data_enabled` INTEGER NOT NULL, `trusted_network_ssids` TEXT NOT NULL, `is_always_on_vpn_enabled` INTEGER NOT NULL, `is_tunnel_on_ethernet_enabled` INTEGER NOT NULL, `is_shortcuts_enabled` INTEGER NOT NULL DEFAULT false, `is_tunnel_on_wifi_enabled` INTEGER NOT NULL DEFAULT false, `is_kernel_enabled` INTEGER NOT NULL DEFAULT false, `is_restore_on_boot_enabled` INTEGER NOT NULL DEFAULT false, `is_multi_tunnel_enabled` INTEGER NOT NULL DEFAULT false, `is_auto_tunnel_paused` INTEGER NOT NULL DEFAULT false, `is_ping_enabled` INTEGER NOT NULL DEFAULT false, `is_amnezia_enabled` INTEGER NOT NULL DEFAULT false)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isAutoTunnelEnabled",
"columnName": "is_tunnel_enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isTunnelOnMobileDataEnabled",
"columnName": "is_tunnel_on_mobile_data_enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "trustedNetworkSSIDs",
"columnName": "trusted_network_ssids",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isAlwaysOnVpnEnabled",
"columnName": "is_always_on_vpn_enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isTunnelOnEthernetEnabled",
"columnName": "is_tunnel_on_ethernet_enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isShortcutsEnabled",
"columnName": "is_shortcuts_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isTunnelOnWifiEnabled",
"columnName": "is_tunnel_on_wifi_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isKernelEnabled",
"columnName": "is_kernel_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isRestoreOnBootEnabled",
"columnName": "is_restore_on_boot_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isMultiTunnelEnabled",
"columnName": "is_multi_tunnel_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isAutoTunnelPaused",
"columnName": "is_auto_tunnel_paused",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isPingEnabled",
"columnName": "is_ping_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isAmneziaEnabled",
"columnName": "is_amnezia_enabled",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "TunnelConfig",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `wg_quick` TEXT NOT NULL, `tunnel_networks` TEXT NOT NULL DEFAULT '', `is_mobile_data_tunnel` INTEGER NOT NULL DEFAULT false, `is_primary_tunnel` INTEGER NOT NULL DEFAULT false, `am_quick` TEXT NOT NULL DEFAULT '')",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "wgQuick",
"columnName": "wg_quick",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "tunnelNetworks",
"columnName": "tunnel_networks",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
},
{
"fieldPath": "isMobileDataTunnel",
"columnName": "is_mobile_data_tunnel",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "isPrimaryTunnel",
"columnName": "is_primary_tunnel",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "false"
},
{
"fieldPath": "amQuick",
"columnName": "am_quick",
"affinity": "TEXT",
"notNull": true,
"defaultValue": "''"
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"id"
]
},
"indices": [
{
"name": "index_TunnelConfig_name",
"unique": true,
"columnNames": [
"name"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_TunnelConfig_name` ON `${TABLE_NAME}` (`name`)"
}
],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b4d4a7c489f6b2f0d3aa4fa6f37b4935')"
]
}
}
@@ -9,6 +9,8 @@ import com.zaneschepke.wireguardautotunnel.service.tile.AutoTunnelControlTile
import com.zaneschepke.wireguardautotunnel.service.tile.TunnelControlTile
import com.zaneschepke.wireguardautotunnel.util.ReleaseTree
import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import timber.log.Timber
import xyz.teamgravity.pin_lock_compose.PinManager
@@ -21,7 +23,16 @@ class WireGuardAutoTunnel : Application() {
PinManager.initialize(this)
}
override fun onLowMemory() {
super.onLowMemory()
applicationScope.cancel("onLowMemory() called by system")
applicationScope = MainScope()
}
companion object {
var applicationScope = MainScope()
lateinit var instance: WireGuardAutoTunnel
private set
@@ -6,12 +6,12 @@ import androidx.room.DeleteColumn
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.room.migration.AutoMigrationSpec
import com.zaneschepke.wireguardautotunnel.data.model.Settings
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
@Database(
entities = [Settings::class, TunnelConfig::class],
version = 7,
version = 8,
autoMigrations =
[
AutoMigration(from = 1, to = 2),
@@ -33,6 +33,7 @@ import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
to = 7,
spec = RemoveLegacySettingColumnsMigration::class,
),
AutoMigration(7, 8)
],
exportSchema = true,
)
@@ -5,7 +5,7 @@ import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.zaneschepke.wireguardautotunnel.data.model.Settings
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
import kotlinx.coroutines.flow.Flow
@Dao
@@ -5,7 +5,7 @@ import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.util.TunnelConfigs
import kotlinx.coroutines.flow.Flow
@@ -1,4 +1,4 @@
package com.zaneschepke.wireguardautotunnel.data.model
package com.zaneschepke.wireguardautotunnel.data.domain
data class GeneralState(
val locationDisclosureShown: Boolean = LOCATION_DISCLOSURE_SHOWN_DEFAULT,
@@ -1,4 +1,4 @@
package com.zaneschepke.wireguardautotunnel.data.model
package com.zaneschepke.wireguardautotunnel.data.domain
import androidx.room.ColumnInfo
import androidx.room.Entity
@@ -50,4 +50,9 @@ data class Settings(
defaultValue = "false",
)
val isPingEnabled: Boolean = false,
@ColumnInfo(
name = "is_amnezia_enabled",
defaultValue = "false",
)
val isAmneziaEnabled: Boolean = false,
)
@@ -1,4 +1,4 @@
package com.zaneschepke.wireguardautotunnel.data.model
package com.zaneschepke.wireguardautotunnel.data.domain
import androidx.room.ColumnInfo
import androidx.room.Entity
@@ -27,12 +27,24 @@ data class TunnelConfig(
defaultValue = "false",
)
val isPrimaryTunnel: Boolean = false,
@ColumnInfo(
name = "am_quick",
defaultValue = "",
)
val amQuick: String = "",
) {
companion object {
fun configFromQuick(wgQuick: String): Config {
fun configFromWgQuick(wgQuick: String): Config {
val inputStream: InputStream = wgQuick.byteInputStream()
val reader = inputStream.bufferedReader(Charsets.UTF_8)
return Config.parse(reader)
return inputStream.bufferedReader(Charsets.UTF_8).use {
Config.parse(it)
}
}
fun configFromAmQuick(amQuick: String) : org.amnezia.awg.config.Config {
val inputStream: InputStream = amQuick.byteInputStream()
return inputStream.bufferedReader(Charsets.UTF_8).use {
org.amnezia.awg.config.Config.parse(it)
}
}
}
}
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.data.repository
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
interface AppDataRepository {
suspend fun getPrimaryOrFirstTunnel(): TunnelConfig?
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.data.repository
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import javax.inject.Inject
class AppDataRoomRepository @Inject constructor(
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.data.repository
import com.zaneschepke.wireguardautotunnel.data.model.GeneralState
import com.zaneschepke.wireguardautotunnel.data.domain.GeneralState
import kotlinx.coroutines.flow.Flow
interface AppStateRepository {
@@ -1,7 +1,7 @@
package com.zaneschepke.wireguardautotunnel.data.repository
import com.zaneschepke.wireguardautotunnel.data.datastore.DataStoreManager
import com.zaneschepke.wireguardautotunnel.data.model.GeneralState
import com.zaneschepke.wireguardautotunnel.data.domain.GeneralState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import timber.log.Timber
@@ -1,7 +1,7 @@
package com.zaneschepke.wireguardautotunnel.data.repository
import com.zaneschepke.wireguardautotunnel.data.SettingsDao
import com.zaneschepke.wireguardautotunnel.data.model.Settings
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
import kotlinx.coroutines.flow.Flow
class RoomSettingsRepository(private val settingsDoa: SettingsDao) : SettingsRepository {
@@ -1,7 +1,7 @@
package com.zaneschepke.wireguardautotunnel.data.repository
import com.zaneschepke.wireguardautotunnel.data.TunnelConfigDao
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.util.TunnelConfigs
import kotlinx.coroutines.flow.Flow
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.data.repository
import com.zaneschepke.wireguardautotunnel.data.model.Settings
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
import kotlinx.coroutines.flow.Flow
interface SettingsRepository {
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.data.repository
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.util.TunnelConfigs
import kotlinx.coroutines.flow.Flow
@@ -40,14 +40,21 @@ class TunnelModule {
return WgQuickBackend(context, rootShell, ToolsInstaller(context, rootShell))
}
@Provides
@Singleton
fun provideAmneziaBackend(@ApplicationContext context: Context) : org.amnezia.awg.backend.Backend {
return org.amnezia.awg.backend.GoBackend(context)
}
@Provides
@Singleton
fun provideVpnService(
amneziaBackend: org.amnezia.awg.backend.Backend,
@Userspace userspaceBackend: Backend,
@Kernel kernelBackend: Backend,
appDataRepository: AppDataRepository
): VpnService {
return WireGuardTunnel(userspaceBackend, kernelBackend, appDataRepository)
return WireGuardTunnel(amneziaBackend,userspaceBackend, kernelBackend, appDataRepository)
}
@Provides
@@ -24,7 +24,7 @@ class NotificationActionReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) = goAsync {
try {
//TODO fix for manual start changes when enabled
serviceManager.stopVpnService(context)
serviceManager.stopVpnServiceForeground(context)
delay(Constants.TOGGLE_TUNNEL_DELAY)
serviceManager.startVpnServiceForeground(context)
} catch (e: Exception) {
@@ -3,5 +3,6 @@ package com.zaneschepke.wireguardautotunnel.service.foreground
enum class Action {
START,
START_FOREGROUND,
STOP
STOP,
STOP_FOREGROUND
}
@@ -24,7 +24,7 @@ open class ForegroundService : LifecycleService() {
when (action) {
Action.START.name,
Action.START_FOREGROUND.name -> startService(intent.extras)
Action.STOP.name, Action.STOP_FOREGROUND.name -> stopService()
Constants.ALWAYS_ON_VPN_ACTION -> {
Timber.i("Always-on VPN starting service")
startService(intent.extras)
@@ -37,16 +37,9 @@ open class ForegroundService : LifecycleService() {
"with a null intent. It has been probably restarted by the system.",
)
}
// by returning this we make sure the service is restarted if the system kills the service
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
Timber.d("The service has been destroyed")
stopService()
}
protected open fun startService(extras: Bundle?) {
if (isServiceStarted) return
Timber.d("Starting ${this.javaClass.simpleName}")
@@ -55,12 +48,8 @@ open class ForegroundService : LifecycleService() {
protected open fun stopService() {
Timber.d("Stopping ${this.javaClass.simpleName}")
try {
stopForeground(STOP_FOREGROUND_REMOVE)
stopSelf()
} catch (e: Exception) {
Timber.e(e)
}
stopForeground(STOP_FOREGROUND_REMOVE)
stopSelf()
isServiceStarted = false
}
}
@@ -23,9 +23,8 @@ class ServiceManager(private val appDataRepository: AppDataRepository) {
intent.component?.javaClass
try {
when (action) {
Action.START_FOREGROUND -> context.startForegroundService(intent)
Action.START -> context.startService(intent)
Action.STOP -> context.stopService(intent)
Action.START_FOREGROUND, Action.STOP_FOREGROUND -> context.startForegroundService(intent)
Action.START, Action.STOP -> context.startService(intent)
}
} catch (e: Exception) {
Timber.e(e.message)
@@ -46,6 +45,16 @@ class ServiceManager(private val appDataRepository: AppDataRepository) {
)
}
suspend fun stopVpnServiceForeground(context: Context, isManualStop: Boolean = false) {
if (isManualStop) onManualStop()
Timber.i("Stopping vpn service")
actionOnService(
Action.STOP_FOREGROUND,
context,
WireGuardTunnelService::class.java,
)
}
suspend fun stopVpnService(context: Context, isManualStop: Boolean = false) {
if (isManualStop) onManualStop()
Timber.i("Stopping vpn service")
@@ -1,20 +1,20 @@
package com.zaneschepke.wireguardautotunnel.service.foreground
import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.data.model.Settings
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
data class WatcherState(
val isWifiConnected: Boolean = false,
val config: TunnelConfig? = null,
val vpnStatus: Tunnel.State = Tunnel.State.DOWN,
val vpnStatus: TunnelState = TunnelState.DOWN,
val isEthernetConnected: Boolean = false,
val isMobileDataConnected: Boolean = false,
val currentNetworkSSID: String = "",
val settings: Settings = Settings()
) {
private fun isVpnConnected() = vpnStatus == Tunnel.State.UP
private fun isVpnConnected() = vpnStatus == TunnelState.UP
fun isEthernetConditionMet(): Boolean {
return (isEthernetConnected &&
settings.isTunnelOnEthernetEnabled &&
@@ -5,9 +5,8 @@ import android.os.Bundle
import android.os.PowerManager
import androidx.core.app.ServiceCompat
import androidx.lifecycle.lifecycleScope
import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.service.network.EthernetService
import com.zaneschepke.wireguardautotunnel.service.network.MobileDataService
@@ -15,9 +14,11 @@ import com.zaneschepke.wireguardautotunnel.service.network.NetworkService
import com.zaneschepke.wireguardautotunnel.service.network.NetworkStatus
import com.zaneschepke.wireguardautotunnel.service.network.WifiService
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
import com.zaneschepke.wireguardautotunnel.util.Constants
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
@@ -56,7 +57,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
private val networkEventsFlow = MutableStateFlow(WatcherState())
private lateinit var watcherJob: Job
private var watcherJob: Job? = null
private var wakeLock: PowerManager.WakeLock? = null
private val tag = this.javaClass.name
@@ -74,11 +75,6 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
}
}
override fun onDestroy() {
super.onDestroy()
stopService()
}
override fun startService(extras: Bundle?) {
super.startService(extras)
try {
@@ -139,8 +135,10 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
}
private fun cancelWatcherJob() {
if (this::watcherJob.isInitialized) {
watcherJob.cancel()
try {
watcherJob?.cancel()
} catch (e : CancellationException) {
Timber.i("Watcher job cancelled")
}
}
@@ -219,10 +217,10 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
private suspend fun watchForPingFailure() {
try {
do {
if (vpnService.vpnState.value.status == Tunnel.State.UP) {
if (vpnService.vpnState.value.status == TunnelState.UP) {
val tunnelConfig = vpnService.vpnState.value.tunnelConfig
tunnelConfig?.let {
val config = TunnelConfig.configFromQuick(it.wgQuick)
val config = TunnelConfig.configFromWgQuick(it.wgQuick)
val results = config.peers.map { peer ->
val host = if (peer.endpoint.isPresent &&
peer.endpoint.get().resolved.isPresent)
@@ -236,7 +234,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
}
if (results.contains(false)) {
Timber.i("Restarting VPN for ping failure")
serviceManager.stopVpnService(this)
serviceManager.stopVpnServiceForeground(this)
delay(Constants.VPN_RESTART_DELAY)
serviceManager.startVpnServiceForeground(this)
delay(Constants.PING_COOLDOWN)
@@ -323,12 +321,14 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
isWifiConnected = true,
)
val ssid = wifiService.getNetworkName(it.networkCapabilities)
ssid?.let {
Timber.i("Detected SSID: $ssid")
appDataRepository.appState.setCurrentSsid(ssid)
ssid?.let { name ->
if(name.contains(Constants.UNREADABLE_SSID)) {
Timber.w("SSID unreadable: missing permissions")
} else Timber.i("Detected valid SSID")
appDataRepository.appState.setCurrentSsid(name)
networkEventsFlow.value =
networkEventsFlow.value.copy(
currentNetworkSSID = ssid,
currentNetworkSSID = name,
)
} ?: Timber.w("Failed to read ssid")
}
@@ -381,7 +381,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
watcherState.isTunnelOffOnMobileDataConditionMet() -> {
Timber.i("$autoTunnel - tunnel off on mobile data met, turning vpn off")
serviceManager.stopVpnService(this)
serviceManager.stopVpnServiceForeground(this)
}
watcherState.isTunnelNotWifiNamePreferredMet(watcherState.currentNetworkSSID) -> {
@@ -407,17 +407,17 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
watcherState.isTrustedWifiConditionMet() -> {
Timber.i("$autoTunnel - tunnel off on trusted wifi condition met, turning vpn off")
serviceManager.stopVpnService(this)
serviceManager.stopVpnServiceForeground(this)
}
watcherState.isTunnelOffOnWifiConditionMet() -> {
Timber.i("$autoTunnel - tunnel off on wifi condition met, turning vpn off")
serviceManager.stopVpnService(this)
serviceManager.stopVpnServiceForeground(this)
}
watcherState.isTunnelOffOnNoConnectivityMet() -> {
Timber.i("$autoTunnel - tunnel off on no connectivity met, turning vpn off")
serviceManager.stopVpnService(this)
serviceManager.stopVpnServiceForeground(this)
}
else -> {
@@ -11,15 +11,19 @@ import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.receiver.NotificationActionReceiver
import com.zaneschepke.wireguardautotunnel.service.notification.NotificationService
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.handshakeStatus
import com.zaneschepke.wireguardautotunnel.util.mapPeerStats
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
@@ -35,7 +39,7 @@ class WireGuardTunnelService : ForegroundService() {
@Inject
lateinit var notificationService: NotificationService
private lateinit var job: Job
private var job: Job? = null
private var didShowConnected = false
@@ -49,11 +53,6 @@ class WireGuardTunnelService : ForegroundService() {
}
}
override fun onDestroy() {
super.onDestroy()
}
override fun startService(extras: Bundle?) {
super.startService(extras)
cancelJob()
@@ -61,7 +60,7 @@ class WireGuardTunnelService : ForegroundService() {
lifecycleScope.launch(Dispatchers.IO) {
launch {
val tunnelId = extras?.getInt(Constants.TUNNEL_EXTRA_KEY)
if (vpnService.getState() == Tunnel.State.UP) {
if (vpnService.getState() == TunnelState.UP) {
vpnService.stopTunnel()
}
vpnService.startTunnel(
@@ -80,7 +79,7 @@ class WireGuardTunnelService : ForegroundService() {
private suspend fun handshakeNotifications() {
var tunnelName: String? = null
vpnService.vpnState.collect { state ->
state.statistics
state.statistics
?.mapPeerStats()
?.map { it.value?.handshakeStatus() }
.let { statuses ->
@@ -105,7 +104,7 @@ class WireGuardTunnelService : ForegroundService() {
else -> {}
}
}
if (state.status == Tunnel.State.UP && state.tunnelConfig?.name != tunnelName) {
if (state.status == TunnelState.UP && state.tunnelConfig?.name != tunnelName) {
tunnelName = state.tunnelConfig?.name
launchVpnNotification(
getString(R.string.tunnel_start_title),
@@ -182,8 +181,10 @@ class WireGuardTunnelService : ForegroundService() {
}
private fun cancelJob() {
if (this::job.isInitialized) {
job.cancel()
try {
job?.cancel()
} catch (e : CancellationException) {
Timber.i("Tunnel job cancelled")
}
}
}
@@ -2,7 +2,7 @@ package com.zaneschepke.wireguardautotunnel.service.shortcut
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.lifecycle.lifecycleScope
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.service.foreground.Action
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
@@ -24,7 +24,7 @@ class ShortcutsActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch(Dispatchers.Main) {
WireGuardAutoTunnel.applicationScope.launch(Dispatchers.IO) {
val settings = appDataRepository.settings.getSettings()
if (settings.isShortcutsEnabled) {
when (intent.getStringExtra(CLASS_NAME_EXTRA_KEY)) {
@@ -40,7 +40,7 @@ class ShortcutsActivity : ComponentActivity() {
this@ShortcutsActivity, tunnelConfig?.id, isManualStart = true,
)
Action.STOP.name -> serviceManager.stopVpnService(
Action.STOP.name -> serviceManager.stopVpnServiceForeground(
this@ShortcutsActivity,
isManualStop = true,
)
@@ -4,7 +4,7 @@ import android.os.Build
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import dagger.hilt.android.AndroidEntryPoint
@@ -3,10 +3,10 @@ package com.zaneschepke.wireguardautotunnel.service.tile
import android.os.Build
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
@@ -38,12 +38,12 @@ class TunnelControlTile : TileService() {
scope.launch {
vpnService.vpnState.collect { it ->
when (it.status) {
Tunnel.State.UP -> {
TunnelState.UP -> {
setActive()
it.tunnelConfig?.name?.let { name -> setTileDescription(name) }
}
Tunnel.State.DOWN -> {
TunnelState.DOWN -> {
setInactive()
val config = appDataRepository.getStartTunnelConfig()?.also { config ->
manualStartConfig = config
@@ -79,8 +79,8 @@ class TunnelControlTile : TileService() {
unlockAndRun {
scope.launch {
try {
if (vpnService.getState() == Tunnel.State.UP) {
serviceManager.stopVpnService(
if (vpnService.getState() == TunnelState.UP) {
serviceManager.stopVpnServiceForeground(
this@TunnelControlTile,
isManualStop = true,
)
@@ -0,0 +1,42 @@
package com.zaneschepke.wireguardautotunnel.service.tunnel
import com.wireguard.android.backend.Tunnel
enum class TunnelState {
UP,
DOWN,
TOGGLE;
fun toWgState() : Tunnel.State {
return when(this) {
UP -> Tunnel.State.UP
DOWN -> Tunnel.State.DOWN
TOGGLE -> Tunnel.State.TOGGLE
}
}
fun toAmState() : org.amnezia.awg.backend.Tunnel.State {
return when(this) {
UP -> org.amnezia.awg.backend.Tunnel.State.UP
DOWN -> org.amnezia.awg.backend.Tunnel.State.DOWN
TOGGLE -> org.amnezia.awg.backend.Tunnel.State.TOGGLE
}
}
companion object {
fun from(state: Tunnel.State) : TunnelState {
return when(state) {
Tunnel.State.DOWN -> DOWN
Tunnel.State.TOGGLE -> TOGGLE
Tunnel.State.UP -> UP
}
}
fun from(state: org.amnezia.awg.backend.Tunnel.State) : TunnelState {
return when(state) {
org.amnezia.awg.backend.Tunnel.State.DOWN -> DOWN
org.amnezia.awg.backend.Tunnel.State.TOGGLE -> TOGGLE
org.amnezia.awg.backend.Tunnel.State.UP -> UP
}
}
}
}
@@ -1,15 +1,15 @@
package com.zaneschepke.wireguardautotunnel.service.tunnel
import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import kotlinx.coroutines.flow.StateFlow
interface VpnService : Tunnel {
suspend fun startTunnel(tunnelConfig: TunnelConfig? = null): Tunnel.State
interface VpnService : Tunnel, org.amnezia.awg.backend.Tunnel {
suspend fun startTunnel(tunnelConfig: TunnelConfig? = null): TunnelState
suspend fun stopTunnel()
val vpnState: StateFlow<VpnState>
fun getState(): Tunnel.State
fun getState(): TunnelState
}
@@ -1,11 +1,10 @@
package com.zaneschepke.wireguardautotunnel.service.tunnel
import com.wireguard.android.backend.Statistics
import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
data class VpnState(
val status: Tunnel.State = Tunnel.State.DOWN,
val status: TunnelState = TunnelState.DOWN,
val tunnelConfig: TunnelConfig? = null,
val statistics: Statistics? = null
val statistics: TunnelStatistics? = null
)
@@ -2,14 +2,17 @@ package com.zaneschepke.wireguardautotunnel.service.tunnel
import com.wireguard.android.backend.Backend
import com.wireguard.android.backend.BackendException
import com.wireguard.android.backend.Statistics
import com.wireguard.android.backend.Tunnel.State
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.module.Kernel
import com.zaneschepke.wireguardautotunnel.module.Userspace
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.AmneziaStatistics
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.WireGuardStatistics
import com.zaneschepke.wireguardautotunnel.util.Constants
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -18,12 +21,15 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import org.amnezia.awg.backend.Tunnel
import org.amnezia.awg.config.Config
import timber.log.Timber
import javax.inject.Inject
class WireGuardTunnel
@Inject
constructor(
private val userspaceAmneziaBackend : org.amnezia.awg.backend.Backend,
@Userspace private val userspaceBackend: Backend,
@Kernel private val kernelBackend: Backend,
private val appDataRepository: AppDataRepository,
@@ -33,50 +39,74 @@ constructor(
private val scope = CoroutineScope(Dispatchers.IO)
private lateinit var statsJob: Job
private var statsJob: Job? = null
private var backend: Backend = userspaceBackend
private var backendIsUserspace = true
private var backendIsWgUserspace = true
private var backendIsAmneziaUserspace = false
init {
scope.launch {
appDataRepository.settings.getSettingsFlow().collect {
if (it.isKernelEnabled && backendIsUserspace) {
if (it.isKernelEnabled && (backendIsWgUserspace || backendIsAmneziaUserspace)) {
Timber.d("Setting kernel backend")
backend = kernelBackend
backendIsUserspace = false
} else if (!it.isKernelEnabled && !backendIsUserspace) {
Timber.d("Setting userspace backend")
backendIsWgUserspace = false
backendIsAmneziaUserspace = false
} else if (!it.isKernelEnabled && !it.isAmneziaEnabled && !backendIsWgUserspace) {
Timber.d("Setting WireGuard userspace backend")
backend = userspaceBackend
backendIsUserspace = true
backendIsWgUserspace = true
backendIsAmneziaUserspace = false
} else if (it.isAmneziaEnabled && !backendIsAmneziaUserspace) {
Timber.d("Setting Amnezia userspace backend")
backendIsAmneziaUserspace = true
backendIsWgUserspace = false
}
}
}
}
override suspend fun startTunnel(tunnelConfig: TunnelConfig?): State {
private fun setState(tunnelConfig: TunnelConfig?, tunnelState: TunnelState) : TunnelState {
return if(backendIsAmneziaUserspace) {
Timber.i("Using Amnezia backend")
val config = tunnelConfig?.let {
if(it.amQuick != "") TunnelConfig.configFromAmQuick(it.amQuick) else {
Timber.w("Using backwards compatible wg config, amnezia specific config not found.")
TunnelConfig.configFromAmQuick(it.wgQuick)
}
}
val state = userspaceAmneziaBackend.setState(this, tunnelState.toAmState(), config)
TunnelState.from(state)
} else {
Timber.i("Using Wg backend")
val wgConfig = tunnelConfig?.let { TunnelConfig.configFromWgQuick(it.wgQuick) }
val state = backend.setState(
this,
tunnelState.toWgState(),
wgConfig,
)
TunnelState.from(state)
}
}
override suspend fun startTunnel(tunnelConfig: TunnelConfig?): TunnelState {
return try {
//TODO we need better error handling here
val config = tunnelConfig ?: appDataRepository.getPrimaryOrFirstTunnel()
if (config != null) {
emitTunnelConfig(config)
val wgConfig = TunnelConfig.configFromQuick(config.wgQuick)
val state =
backend.setState(
this,
State.UP,
wgConfig,
)
state
setState(config, TunnelState.UP)
} else throw Exception("No tunnels")
} catch (e: BackendException) {
Timber.e("Failed to start tunnel with error: ${e.message}")
State.DOWN
TunnelState.from(State.DOWN)
}
}
private fun emitTunnelState(state: State) {
private fun emitTunnelState(state : TunnelState) {
_vpnState.tryEmit(
_vpnState.value.copy(
status = state,
@@ -84,7 +114,7 @@ constructor(
)
}
private fun emitBackendStatistics(statistics: Statistics) {
private fun emitBackendStatistics(statistics: TunnelStatistics) {
_vpnState.tryEmit(
_vpnState.value.copy(
statistics = statistics,
@@ -102,41 +132,58 @@ constructor(
override suspend fun stopTunnel() {
try {
if (getState() == State.UP) {
val state = backend.setState(this, State.DOWN, null)
if (getState() == TunnelState.UP) {
val state = setState(null, TunnelState.DOWN)
emitTunnelState(state)
}
} catch (e: BackendException) {
Timber.e("Failed to stop tunnel with error: ${e.message}")
Timber.e("Failed to stop wireguard tunnel with error: ${e.message}")
} catch (e: org.amnezia.awg.backend.BackendException) {
Timber.e("Failed to stop amnezia tunnel with error: ${e.message}")
}
}
override fun getState(): State {
return backend.getState(this)
override fun getState(): TunnelState {
return if(backendIsAmneziaUserspace) TunnelState.from(userspaceAmneziaBackend.getState(this))
else TunnelState.from(backend.getState(this))
}
override fun getName(): String {
return _vpnState.value.tunnelConfig?.name ?: ""
}
override fun onStateChange(state: State) {
override fun onStateChange(newState: Tunnel.State) {
handleStateChange(TunnelState.from(newState))
}
private fun handleStateChange(state: TunnelState) {
val tunnel = this
emitTunnelState(state)
WireGuardAutoTunnel.requestTunnelTileServiceStateUpdate(WireGuardAutoTunnel.instance)
if (state == State.UP) {
if (state == TunnelState.UP) {
statsJob =
scope.launch {
while (true) {
val statistics = backend.getStatistics(tunnel)
emitBackendStatistics(statistics)
if(backendIsAmneziaUserspace) {
emitBackendStatistics(AmneziaStatistics(userspaceAmneziaBackend.getStatistics(tunnel)))
} else {
emitBackendStatistics(WireGuardStatistics(backend.getStatistics(tunnel)))
}
delay(Constants.VPN_STATISTIC_CHECK_INTERVAL)
}
}
}
if (state == State.DOWN) {
if (this::statsJob.isInitialized) {
statsJob.cancel()
if (state == TunnelState.DOWN) {
try {
statsJob?.cancel()
} catch (e : CancellationException) {
Timber.i("Stats job cancelled")
}
}
}
override fun onStateChange(state: State) {
handleStateChange(TunnelState.from(state))
}
}
@@ -0,0 +1,34 @@
package com.zaneschepke.wireguardautotunnel.service.tunnel.statistics
import org.amnezia.awg.backend.Statistics
import org.amnezia.awg.crypto.Key
class AmneziaStatistics(private val statistics: Statistics) : TunnelStatistics() {
override fun peerStats(peer: Key): PeerStats? {
val key = Key.fromBase64(peer.toBase64())
val stats = statistics.peer(key)
return stats?.let {
PeerStats(
rxBytes = stats.rxBytes,
txBytes = stats.txBytes,
latestHandshakeEpochMillis = stats.latestHandshakeEpochMillis
)
}
}
override fun isTunnelStale(): Boolean {
return statistics.isStale
}
override fun getPeers(): Array<Key> {
return statistics.peers()
}
override fun rx(): Long {
return statistics.totalRx()
}
override fun tx(): Long {
return statistics.totalTx()
}
}
@@ -0,0 +1,18 @@
package com.zaneschepke.wireguardautotunnel.service.tunnel.statistics
import org.amnezia.awg.crypto.Key
abstract class TunnelStatistics {
@JvmRecord
data class PeerStats(val rxBytes: Long, val txBytes: Long, val latestHandshakeEpochMillis: Long)
abstract fun peerStats(peer: Key): PeerStats?
abstract fun isTunnelStale() : Boolean
abstract fun getPeers(): Array<Key>
abstract fun rx() : Long
abstract fun tx() : Long
}
@@ -0,0 +1,36 @@
package com.zaneschepke.wireguardautotunnel.service.tunnel.statistics
import com.wireguard.android.backend.Statistics
import org.amnezia.awg.crypto.Key
class WireGuardStatistics(private val statistics: Statistics) : TunnelStatistics() {
override fun peerStats(peer: Key): PeerStats? {
val key = com.wireguard.crypto.Key.fromBase64(peer.toBase64())
val peerStats = statistics.peer(key)
return peerStats?.let {
PeerStats(
txBytes = peerStats.txBytes,
rxBytes = peerStats.rxBytes,
latestHandshakeEpochMillis = peerStats.latestHandshakeEpochMillis
)
}
}
override fun isTunnelStale(): Boolean {
return statistics.isStale
}
override fun getPeers(): Array<Key> {
return statistics.peers().map {
Key.fromBase64(it.toBase64())
}.toTypedArray()
}
override fun rx(): Long {
return statistics.totalRx()
}
override fun tx(): Long {
return statistics.totalTx()
}
}
@@ -2,12 +2,14 @@ package com.zaneschepke.wireguardautotunnel.ui
import android.app.Application
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.widget.Toast
import androidx.compose.runtime.mutableStateListOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.journeyapps.barcodescanner.BarcodeEncoder
import com.wireguard.android.backend.GoBackend
import com.zaneschepke.logcatter.Logcatter
import com.zaneschepke.logcatter.model.LogMessage
@@ -19,6 +21,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber
import java.time.Instant
@@ -27,9 +30,7 @@ import javax.inject.Inject
@HiltViewModel
class AppViewModel
@Inject
constructor(
private val application: Application,
) : ViewModel() {
constructor() : ViewModel() {
val vpnIntent: Intent? = GoBackend.VpnService.prepare(WireGuardAutoTunnel.instance)
@@ -49,68 +50,78 @@ constructor(
}
private fun requestPermissions() {
_appUiState.value = _appUiState.value.copy(
requestPermissions = true,
)
_appUiState.update {
it.copy(
requestPermissions = true
)
}
}
fun permissionsRequested() {
_appUiState.value = _appUiState.value.copy(
requestPermissions = false,
)
_appUiState.update {
it.copy(
requestPermissions = false
)
}
}
fun openWebPage(url: String) {
fun openWebPage(url: String, context : Context) {
try {
val webpage: Uri = Uri.parse(url)
val intent = Intent(Intent.ACTION_VIEW, webpage).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
application.startActivity(intent)
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
Timber.e(e)
showSnackbarMessage(application.getString(R.string.no_browser_detected))
showSnackbarMessage(context.getString(R.string.no_browser_detected))
}
}
fun onVpnPermissionAccepted() {
_appUiState.value = _appUiState.value.copy(
vpnPermissionAccepted = true,
)
_appUiState.update {
it.copy(
vpnPermissionAccepted = true
)
}
}
fun launchEmail() {
fun launchEmail(context: Context) {
try {
val intent =
Intent(Intent.ACTION_SENDTO).apply {
type = Constants.EMAIL_MIME_TYPE
putExtra(Intent.EXTRA_EMAIL, arrayOf(application.getString(R.string.my_email)))
putExtra(Intent.EXTRA_SUBJECT, application.getString(R.string.email_subject))
putExtra(Intent.EXTRA_EMAIL, arrayOf(context.getString(R.string.my_email)))
putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.email_subject))
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
application.startActivity(
Intent.createChooser(intent, application.getString(R.string.email_chooser)).apply {
context.startActivity(
Intent.createChooser(intent, context.getString(R.string.email_chooser)).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
},
)
} catch (e: ActivityNotFoundException) {
Timber.e(e)
showSnackbarMessage(application.getString(R.string.no_email_detected))
showSnackbarMessage(context.getString(R.string.no_email_detected))
}
}
fun showSnackbarMessage(message: String) {
_appUiState.value = _appUiState.value.copy(
snackbarMessage = message,
snackbarMessageConsumed = false,
)
_appUiState.update {
it.copy(
snackbarMessage = message,
snackbarMessageConsumed = false,
)
}
}
fun snackbarMessageConsumed() {
_appUiState.value = _appUiState.value.copy(
snackbarMessage = "",
snackbarMessageConsumed = true,
)
_appUiState.update {
it.copy(
snackbarMessage = "",
snackbarMessageConsumed = true,
)
}
}
val logs = mutableStateListOf<LogMessage>()
@@ -118,12 +129,12 @@ constructor(
fun readLogCatOutput() =
viewModelScope.launch(viewModelScope.coroutineContext + Dispatchers.IO) {
launch {
Logcatter.logs {
Logcatter.logs(callback = {
logs.add(it)
if (logs.size > Constants.LOG_BUFFER_SIZE) {
logs.removeRange(0, (logs.size - Constants.LOG_BUFFER_SIZE).toInt())
}
}
})
}
}
@@ -132,17 +143,19 @@ constructor(
Logcatter.clear()
}
fun saveLogsToFile() {
fun saveLogsToFile(context: Context) {
val fileName = "${Constants.BASE_LOG_FILE_NAME}-${Instant.now().epochSecond}.txt"
val content = logs.joinToString(separator = "\n")
FileUtils.saveFileToDownloads(application.applicationContext, content, fileName)
Toast.makeText(application, application.getString(R.string.logs_saved), Toast.LENGTH_SHORT)
FileUtils.saveFileToDownloads(context.applicationContext, content, fileName)
Toast.makeText(context, context.getString(R.string.logs_saved), Toast.LENGTH_SHORT)
.show()
}
fun setNotificationPermissionAccepted(accepted: Boolean) {
_appUiState.value = _appUiState.value.copy(
notificationPermissionAccepted = accepted,
)
_appUiState.update {
it.copy(
notificationPermissionAccepted = accepted,
)
}
}
}
@@ -138,7 +138,11 @@ class MainActivity : AppCompatActivity() {
return@LaunchedEffect notificationPermissionState.launchPermissionRequest()
}
if (!appUiState.vpnPermissionAccepted) {
return@LaunchedEffect vpnActivityResultState.launch(appViewModel.vpnIntent)
return@LaunchedEffect appViewModel.vpnIntent?.let {
vpnActivityResultState.launch(
it
)
}!!
}
}
}
@@ -4,13 +4,17 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Home
import androidx.compose.material.icons.rounded.QuestionMark
import androidx.compose.material.icons.rounded.Settings
import androidx.compose.ui.res.stringResource
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.ui.common.navigation.BottomNavItem
import com.zaneschepke.wireguardautotunnel.util.StringValue
sealed class Screen(val route: String) {
data object Main : Screen("main") {
val navItem =
BottomNavItem(
name = "Tunnels",
name = WireGuardAutoTunnel.instance.getString(R.string.tunnels),
route = route,
icon = Icons.Rounded.Home,
)
@@ -19,7 +23,7 @@ sealed class Screen(val route: String) {
data object Settings : Screen("settings") {
val navItem =
BottomNavItem(
name = "Settings",
name = WireGuardAutoTunnel.instance.getString(R.string.settings),
route = route,
icon = Icons.Rounded.Settings,
)
@@ -28,7 +32,7 @@ sealed class Screen(val route: String) {
data object Support : Screen("support") {
val navItem =
BottomNavItem(
name = "Support",
name = WireGuardAutoTunnel.instance.getString(R.string.support),
route = route,
icon = Icons.Rounded.QuestionMark,
)
@@ -17,7 +17,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.wireguard.android.backend.Statistics
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
import com.zaneschepke.wireguardautotunnel.util.toThreeDecimalPlaceString
@@ -30,7 +30,7 @@ fun RowListItem(
onClick: () -> Unit,
rowButton: @Composable () -> Unit,
expanded: Boolean,
statistics: Statistics?
statistics: TunnelStatistics?
) {
Box(
modifier =
@@ -59,7 +59,7 @@ fun RowListItem(
rowButton()
}
if (expanded) {
statistics?.peers()?.forEach {
statistics?.getPeers()?.forEach {
Row(
modifier =
Modifier
@@ -69,9 +69,9 @@ fun RowListItem(
horizontalArrangement = Arrangement.SpaceEvenly,
) {
//TODO change these to string resources
val handshakeEpoch = statistics.peer(it)!!.latestHandshakeEpochMillis
val peerTx = statistics.peer(it)!!.txBytes
val peerRx = statistics.peer(it)!!.rxBytes
val handshakeEpoch = statistics.peerStats(it)!!.latestHandshakeEpochMillis
val peerTx = statistics.peerStats(it)!!.txBytes
val peerRx = statistics.peerStats(it)!!.rxBytes
val peerId = it.toBase64().subSequence(0, 3).toString() + "***"
val handshakeSec =
NumberUtils.getSecondsBetweenTimestampAndNow(handshakeEpoch)
@@ -28,7 +28,12 @@ fun ConfigurationToggle(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(label, textAlign = TextAlign.Start)
Text(label, textAlign = TextAlign.Start, modifier = Modifier
.weight(
weight = 1.0f,
fill = false,
),
softWrap = true)
Switch(
modifier = modifier,
enabled = enabled,
@@ -1,30 +0,0 @@
package com.zaneschepke.wireguardautotunnel.ui.models
import com.wireguard.config.Interface
data class InterfaceProxy(
var privateKey: String = "",
var publicKey: String = "",
var addresses: String = "",
var dnsServers: String = "",
var listenPort: String = "",
var mtu: String = ""
) {
companion object {
fun from(i: Interface): InterfaceProxy {
return InterfaceProxy(
publicKey = i.keyPair.publicKey.toBase64().trim(),
privateKey = i.keyPair.privateKey.toBase64().trim(),
addresses = i.addresses.joinToString(", ").trim(),
dnsServers = i.dnsServers.joinToString(", ").replace("/", "").trim(),
listenPort =
if (i.listenPort.isPresent) {
i.listenPort.get().toString().trim()
} else {
""
},
mtu = if (i.mtu.isPresent) i.mtu.get().toString().trim() else "",
)
}
}
}
@@ -486,6 +486,98 @@ fun ConfigScreen(
modifier = Modifier.width(IntrinsicSize.Min),
)
}
if(uiState.isAmneziaEnabled) {
ConfigurationTextBox(
value = uiState.interfaceProxy.junkPacketCount,
onValueChange = { value -> viewModel.onJunkPacketCountChanged(value) },
keyboardActions = keyboardActions,
label = stringResource(R.string.junk_packet_count),
hint = stringResource(R.string.junk_packet_count).lowercase(),
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester),
)
ConfigurationTextBox(
value = uiState.interfaceProxy.junkPacketMinSize,
onValueChange = { value -> viewModel.onJunkPacketMinSizeChanged(value) },
keyboardActions = keyboardActions,
label = stringResource(R.string.junk_packet_minimum_size),
hint = stringResource(R.string.junk_packet_minimum_size).lowercase(),
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester),
)
ConfigurationTextBox(
value = uiState.interfaceProxy.junkPacketMaxSize,
onValueChange = { value -> viewModel.onJunkPacketMaxSizeChanged(value) },
keyboardActions = keyboardActions,
label = stringResource(R.string.junk_packet_maximum_size),
hint = stringResource(R.string.junk_packet_maximum_size).lowercase(),
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester),
)
ConfigurationTextBox(
value = uiState.interfaceProxy.initPacketJunkSize,
onValueChange = { value -> viewModel.onInitPacketJunkSizeChanged(value) },
keyboardActions = keyboardActions,
label = stringResource(R.string.init_packet_junk_size),
hint = stringResource(R.string.init_packet_junk_size).lowercase(),
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester),
)
ConfigurationTextBox(
value = uiState.interfaceProxy.responsePacketJunkSize,
onValueChange = { value -> viewModel.onResponsePacketJunkSize(value) },
keyboardActions = keyboardActions,
label = stringResource(R.string.response_packet_junk_size),
hint = stringResource(R.string.response_packet_junk_size).lowercase(),
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester),
)
ConfigurationTextBox(
value = uiState.interfaceProxy.initPacketMagicHeader,
onValueChange = { value -> viewModel.onInitPacketMagicHeader(value) },
keyboardActions = keyboardActions,
label = stringResource(R.string.init_packet_magic_header),
hint = stringResource(R.string.init_packet_magic_header).lowercase(),
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester),
)
ConfigurationTextBox(
value = uiState.interfaceProxy.responsePacketMagicHeader,
onValueChange = { value -> viewModel.onResponsePacketMagicHeader(value) },
keyboardActions = keyboardActions,
label = stringResource(R.string.response_packet_magic_header),
hint = stringResource(R.string.response_packet_magic_header).lowercase(),
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester),
)
ConfigurationTextBox(
value = uiState.interfaceProxy.transportPacketMagicHeader,
onValueChange = { value -> viewModel.onTransportPacketMagicHeader(value) },
keyboardActions = keyboardActions,
label = stringResource(R.string.transport_packet_magic_header),
hint = stringResource(R.string.transport_packet_magic_header).lowercase(),
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester),
)
ConfigurationTextBox(
value = uiState.interfaceProxy.underloadPacketMagicHeader,
onValueChange = { value -> viewModel.onUnderloadPacketMagicHeader(value) },
keyboardActions = keyboardActions,
label = stringResource(R.string.underload_packet_magic_header),
hint = stringResource(R.string.underload_packet_magic_header).lowercase(),
modifier = Modifier
.fillMaxWidth()
.focusRequester(focusRequester),
)
}
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
@@ -1,8 +1,9 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.config
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.ui.models.InterfaceProxy
import com.zaneschepke.wireguardautotunnel.ui.models.PeerProxy
import com.wireguard.config.Config
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.ui.screens.config.model.InterfaceProxy
import com.zaneschepke.wireguardautotunnel.ui.screens.config.model.PeerProxy
import com.zaneschepke.wireguardautotunnel.util.Packages
data class ConfigUiState(
@@ -14,5 +15,58 @@ data class ConfigUiState(
val isAllApplicationsEnabled: Boolean = false,
val loading: Boolean = true,
val tunnel: TunnelConfig? = null,
val tunnelName: String = ""
)
val tunnelName: String = "",
val isAmneziaEnabled: Boolean = false
) {
companion object {
fun from(config : Config) : ConfigUiState {
val proxyPeers = config.peers.map { PeerProxy.from(it) }
val proxyInterface = InterfaceProxy.from(config.`interface`)
var include = true
var isAllApplicationsEnabled = false
val checkedPackages =
if (config.`interface`.includedApplications.isNotEmpty()) {
config.`interface`.includedApplications
} else if (config.`interface`.excludedApplications.isNotEmpty()) {
include = false
config.`interface`.excludedApplications
} else {
isAllApplicationsEnabled = true
emptySet()
}
return ConfigUiState(
proxyPeers,
proxyInterface,
emptyList(),
checkedPackages.toList(),
include,
isAllApplicationsEnabled,
)
}
fun from(config: org.amnezia.awg.config.Config) : ConfigUiState {
//TODO update with new values
val proxyPeers = config.peers.map { PeerProxy.from(it) }
val proxyInterface = InterfaceProxy.from(config.`interface`)
var include = true
var isAllApplicationsEnabled = false
val checkedPackages =
if (config.`interface`.includedApplications.isNotEmpty()) {
config.`interface`.includedApplications
} else if (config.`interface`.excludedApplications.isNotEmpty()) {
include = false
config.`interface`.excludedApplications
} else {
isAllApplicationsEnabled = true
emptySet()
}
return ConfigUiState(
proxyPeers,
proxyInterface,
emptyList(),
checkedPackages.toList(),
include,
isAllApplicationsEnabled,
)
}
}
}
@@ -13,10 +13,10 @@ import com.wireguard.config.Peer
import com.wireguard.crypto.Key
import com.wireguard.crypto.KeyPair
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.ui.models.InterfaceProxy
import com.zaneschepke.wireguardautotunnel.ui.models.PeerProxy
import com.zaneschepke.wireguardautotunnel.data.repository.SettingsRepository
import com.zaneschepke.wireguardautotunnel.ui.screens.config.model.PeerProxy
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.Event
import com.zaneschepke.wireguardautotunnel.util.NumberUtils
@@ -27,6 +27,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@@ -36,6 +37,7 @@ class ConfigViewModel
@Inject
constructor(
private val application: Application,
private val settingsRepository: SettingsRepository,
private val appDataRepository: AppDataRepository
) : ViewModel() {
@@ -52,32 +54,17 @@ constructor(
val tunnelConfig =
appDataRepository.tunnels.getAll()
.firstOrNull { it.id.toString() == tunnelId }
val isAmneziaEnabled = settingsRepository.getSettings().isAmneziaEnabled
if (tunnelConfig != null) {
val config = TunnelConfig.configFromQuick(tunnelConfig.wgQuick)
val proxyPeers = config.peers.map { PeerProxy.from(it) }
val proxyInterface = InterfaceProxy.from(config.`interface`)
var include = true
var isAllApplicationsEnabled = false
val checkedPackages =
if (config.`interface`.includedApplications.isNotEmpty()) {
config.`interface`.includedApplications
} else if (config.`interface`.excludedApplications.isNotEmpty()) {
include = false
config.`interface`.excludedApplications
} else {
isAllApplicationsEnabled = true
emptySet()
}
ConfigUiState(
proxyPeers,
proxyInterface,
packages,
checkedPackages.toList(),
include,
isAllApplicationsEnabled,
false,
tunnelConfig,
tunnelConfig.name,
(if(isAmneziaEnabled) {
val amConfig = if(tunnelConfig.amQuick == "") tunnelConfig.wgQuick else tunnelConfig.amQuick
ConfigUiState.from(TunnelConfig.configFromAmQuick(amConfig))
} else ConfigUiState.from(TunnelConfig.configFromWgQuick(tunnelConfig.wgQuick))).copy(
packages = packages,
loading = false,
tunnel = tunnelConfig,
tunnelName = tunnelConfig.name,
isAmneziaEnabled = isAmneziaEnabled
)
} else {
ConfigUiState(loading = false, packages = packages)
@@ -168,6 +155,20 @@ constructor(
}
}
private fun buildAmPeerListFromProxyPeers(): List<org.amnezia.awg.config.Peer> {
return _uiState.value.proxyPeers.map {
val builder = org.amnezia.awg.config.Peer.Builder()
if (it.allowedIps.isNotEmpty()) builder.parseAllowedIPs(it.allowedIps.trim())
if (it.publicKey.isNotEmpty()) builder.parsePublicKey(it.publicKey.trim())
if (it.preSharedKey.isNotEmpty()) builder.parsePreSharedKey(it.preSharedKey.trim())
if (it.endpoint.isNotEmpty()) builder.parseEndpoint(it.endpoint.trim())
if (it.persistentKeepalive.isNotEmpty()) {
builder.parsePersistentKeepalive(it.persistentKeepalive.trim())
}
builder.build()
}
}
private fun emptyCheckedPackagesList() {
_uiState.value = _uiState.value.copy(checkedPackageNames = emptyList())
}
@@ -190,20 +191,76 @@ constructor(
return builder.build()
}
private fun buildAmInterfaceListFromProxyInterface(): org.amnezia.awg.config.Interface {
val builder = org.amnezia.awg.config.Interface.Builder()
builder.parsePrivateKey(_uiState.value.interfaceProxy.privateKey.trim())
builder.parseAddresses(_uiState.value.interfaceProxy.addresses.trim())
if (_uiState.value.interfaceProxy.dnsServers.isNotEmpty()) {
builder.parseDnsServers(_uiState.value.interfaceProxy.dnsServers.trim())
}
if (_uiState.value.interfaceProxy.mtu.isNotEmpty())
builder.parseMtu(_uiState.value.interfaceProxy.mtu.trim())
if (_uiState.value.interfaceProxy.listenPort.isNotEmpty()) {
builder.parseListenPort(_uiState.value.interfaceProxy.listenPort.trim())
}
if (isAllApplicationsEnabled()) emptyCheckedPackagesList()
if (_uiState.value.include) builder.includeApplications(_uiState.value.checkedPackageNames)
if (!_uiState.value.include) builder.excludeApplications(_uiState.value.checkedPackageNames)
if(_uiState.value.interfaceProxy.junkPacketCount.isNotEmpty()) {
builder.setJunkPacketCount(_uiState.value.interfaceProxy.junkPacketCount.trim().toInt())
}
if(_uiState.value.interfaceProxy.junkPacketMinSize.isNotEmpty()) {
builder.setJunkPacketMinSize(_uiState.value.interfaceProxy.junkPacketMinSize.trim().toInt())
}
if(_uiState.value.interfaceProxy.junkPacketMaxSize.isNotEmpty()) {
builder.setJunkPacketMaxSize(_uiState.value.interfaceProxy.junkPacketMaxSize.trim().toInt())
}
if(_uiState.value.interfaceProxy.initPacketJunkSize.isNotEmpty()) {
builder.setInitPacketJunkSize(_uiState.value.interfaceProxy.initPacketJunkSize.trim().toInt())
}
if(_uiState.value.interfaceProxy.responsePacketJunkSize.isNotEmpty()) {
builder.setResponsePacketJunkSize(_uiState.value.interfaceProxy.responsePacketJunkSize.trim().toInt())
}
if(_uiState.value.interfaceProxy.initPacketMagicHeader.isNotEmpty()) {
builder.setInitPacketMagicHeader(_uiState.value.interfaceProxy.initPacketMagicHeader.trim().toLong())
}
if(_uiState.value.interfaceProxy.responsePacketMagicHeader.isNotEmpty()) {
builder.setResponsePacketMagicHeader(_uiState.value.interfaceProxy.responsePacketMagicHeader.trim().toLong())
}
if(_uiState.value.interfaceProxy.transportPacketMagicHeader.isNotEmpty()) {
builder.setTransportPacketMagicHeader(_uiState.value.interfaceProxy.transportPacketMagicHeader.trim().toLong())
}
if(_uiState.value.interfaceProxy.underloadPacketMagicHeader.isNotEmpty()) {
builder.setUnderloadPacketMagicHeader(_uiState.value.interfaceProxy.underloadPacketMagicHeader.trim().toLong())
}
return builder.build()
}
private fun buildConfig() : Config {
val peerList = buildPeerListFromProxyPeers()
val wgInterface = buildInterfaceListFromProxyInterface()
return Config.Builder().addPeers(peerList).setInterface(wgInterface).build()
}
private fun buildAmConfig() : org.amnezia.awg.config.Config {
val peerList = buildAmPeerListFromProxyPeers()
val amInterface = buildAmInterfaceListFromProxyInterface()
return org.amnezia.awg.config.Config.Builder().addPeers(peerList).setInterface(amInterface).build()
}
fun onSaveAllChanges(): Result<Event> {
return try {
val peerList = buildPeerListFromProxyPeers()
val wgInterface = buildInterfaceListFromProxyInterface()
val config = Config.Builder().addPeers(peerList).setInterface(wgInterface).build()
val config = buildConfig()
val tunnelConfig = when (uiState.value.tunnel) {
null -> TunnelConfig(
name = _uiState.value.tunnelName,
wgQuick = config.toWgQuickString(),
)
else -> uiState.value.tunnel!!.copy(
name = _uiState.value.tunnelName,
wgQuick = config.toWgQuickString(),
amQuick = if(uiState.value.isAmneziaEnabled) buildAmConfig().toAwgQuickString()
else _uiState.value.tunnel?.amQuick ?: ""
)
}
updateTunnelConfig(tunnelConfig)
@@ -216,121 +273,138 @@ constructor(
}
fun onPeerPublicKeyChange(index: Int, value: String) {
_uiState.value =
_uiState.value.copy(
_uiState.update {
it.copy(
proxyPeers =
_uiState.value.proxyPeers.update(
index,
_uiState.value.proxyPeers[index].copy(publicKey = value),
),
)
}
}
fun onPreSharedKeyChange(index: Int, value: String) {
_uiState.value =
_uiState.value.copy(
_uiState.update {
it.copy(
proxyPeers =
_uiState.value.proxyPeers.update(
index,
_uiState.value.proxyPeers[index].copy(preSharedKey = value),
),
)
}
}
fun onEndpointChange(index: Int, value: String) {
_uiState.value =
_uiState.value.copy(
_uiState.update {
it.copy(
proxyPeers =
_uiState.value.proxyPeers.update(
index,
_uiState.value.proxyPeers[index].copy(endpoint = value),
),
)
}
}
fun onAllowedIpsChange(index: Int, value: String) {
_uiState.value =
_uiState.value.copy(
_uiState.update {
it.copy(
proxyPeers =
_uiState.value.proxyPeers.update(
index,
_uiState.value.proxyPeers[index].copy(allowedIps = value),
),
)
}
}
fun onPersistentKeepaliveChanged(index: Int, value: String) {
_uiState.value =
_uiState.value.copy(
_uiState.update {
it.copy(
proxyPeers =
_uiState.value.proxyPeers.update(
index,
_uiState.value.proxyPeers[index].copy(persistentKeepalive = value),
),
)
}
}
fun onDeletePeer(index: Int) {
_uiState.value =
_uiState.value.copy(
_uiState.update {
it.copy(
proxyPeers = _uiState.value.proxyPeers.removeAt(index),
)
}
}
fun addEmptyPeer() {
_uiState.value = _uiState.value.copy(proxyPeers = _uiState.value.proxyPeers + PeerProxy())
_uiState.update {
it.copy(proxyPeers = _uiState.value.proxyPeers + PeerProxy())
}
}
fun generateKeyPair() {
val keyPair = KeyPair()
_uiState.value =
_uiState.value.copy(
_uiState.update {
it.copy(
interfaceProxy =
_uiState.value.interfaceProxy.copy(
privateKey = keyPair.privateKey.toBase64(),
publicKey = keyPair.publicKey.toBase64(),
),
)
}
}
fun onAddressesChanged(value: String) {
_uiState.value =
_uiState.value.copy(
_uiState.update {
it.copy(
interfaceProxy = _uiState.value.interfaceProxy.copy(addresses = value),
)
}
}
fun onListenPortChanged(value: String) {
_uiState.value =
_uiState.value.copy(
_uiState.update {
it.copy(
interfaceProxy = _uiState.value.interfaceProxy.copy(listenPort = value),
)
}
}
fun onDnsServersChanged(value: String) {
_uiState.value =
_uiState.value.copy(
_uiState.update {
it.copy(
interfaceProxy = _uiState.value.interfaceProxy.copy(dnsServers = value),
)
}
}
fun onMtuChanged(value: String) {
_uiState.value =
_uiState.value.copy(interfaceProxy = _uiState.value.interfaceProxy.copy(mtu = value))
_uiState.update {
it.copy(interfaceProxy = _uiState.value.interfaceProxy.copy(mtu = value))
}
}
private fun onInterfacePublicKeyChange(value: String) {
_uiState.value =
_uiState.value.copy(
_uiState.update {
it.copy(
interfaceProxy = _uiState.value.interfaceProxy.copy(publicKey = value),
)
}
}
fun onPrivateKeyChange(value: String) {
_uiState.value =
_uiState.value.copy(
_uiState.update {
it.copy(
interfaceProxy = _uiState.value.interfaceProxy.copy(privateKey = value),
)
}
if (NumberUtils.isValidKey(value)) {
val pair = KeyPair(Key.fromBase64(value))
onInterfacePublicKeyChange(pair.publicKey.toBase64())
@@ -344,6 +418,77 @@ constructor(
getAllInternetCapablePackages().filter {
getPackageLabel(it).lowercase().contains(query.lowercase())
}
_uiState.value = _uiState.value.copy(packages = packages)
_uiState.update { it.copy(packages = packages) }
}
fun onJunkPacketCountChanged(value: String) {
_uiState.update {
it.copy(
interfaceProxy = _uiState.value.interfaceProxy.copy(junkPacketCount = value)
)
}
}
fun onJunkPacketMinSizeChanged(value: String) {
_uiState.update {
it.copy(
interfaceProxy = _uiState.value.interfaceProxy.copy(junkPacketMinSize = value)
)
}
}
fun onJunkPacketMaxSizeChanged(value: String) {
_uiState.update {
it.copy(
interfaceProxy = _uiState.value.interfaceProxy.copy(junkPacketMaxSize = value)
)
}
}
fun onInitPacketJunkSizeChanged(value: String) {
_uiState.update {
it.copy(
interfaceProxy = _uiState.value.interfaceProxy.copy(initPacketJunkSize = value)
)
}
}
fun onResponsePacketJunkSize(value: String) {
_uiState.update {
it.copy(
interfaceProxy = _uiState.value.interfaceProxy.copy(responsePacketJunkSize = value)
)
}
}
fun onInitPacketMagicHeader(value: String) {
_uiState.update {
it.copy(
interfaceProxy = _uiState.value.interfaceProxy.copy(initPacketMagicHeader = value)
)
}
}
fun onResponsePacketMagicHeader(value: String) {
_uiState.update {
it.copy(
interfaceProxy = _uiState.value.interfaceProxy.copy(responsePacketMagicHeader = value)
)
}
}
fun onTransportPacketMagicHeader(value: String) {
_uiState.update {
it.copy(
interfaceProxy = _uiState.value.interfaceProxy.copy(transportPacketMagicHeader = value)
)
}
}
fun onUnderloadPacketMagicHeader(value: String) {
_uiState.update {
it.copy(
interfaceProxy = _uiState.value.interfaceProxy.copy(underloadPacketMagicHeader = value)
)
}
}
}
@@ -0,0 +1,63 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.config.model
import com.wireguard.config.Interface
data class InterfaceProxy(
val privateKey: String = "",
val publicKey: String = "",
val addresses: String = "",
val dnsServers: String = "",
val listenPort: String = "",
val mtu: String = "",
val junkPacketCount: String = "",
val junkPacketMinSize: String = "",
val junkPacketMaxSize: String = "",
val initPacketJunkSize: String = "",
val responsePacketJunkSize: String = "",
val initPacketMagicHeader: String = "",
val responsePacketMagicHeader: String = "",
val underloadPacketMagicHeader: String = "",
val transportPacketMagicHeader: String = "",
) {
companion object {
fun from(i: Interface): InterfaceProxy {
return InterfaceProxy(
publicKey = i.keyPair.publicKey.toBase64().trim(),
privateKey = i.keyPair.privateKey.toBase64().trim(),
addresses = i.addresses.joinToString(", ").trim(),
dnsServers = i.dnsServers.joinToString(", ").replace("/", "").trim(),
listenPort =
if (i.listenPort.isPresent) {
i.listenPort.get().toString().trim()
} else {
""
},
mtu = if (i.mtu.isPresent) i.mtu.get().toString().trim() else "",
)
}
fun from(i: org.amnezia.awg.config.Interface) : InterfaceProxy {
return InterfaceProxy(
publicKey = i.keyPair.publicKey.toBase64().trim(),
privateKey = i.keyPair.privateKey.toBase64().trim(),
addresses = i.addresses.joinToString(", ").trim(),
dnsServers = i.dnsServers.joinToString(", ").replace("/", "").trim(),
listenPort =
if (i.listenPort.isPresent) {
i.listenPort.get().toString().trim()
} else {
""
},
mtu = if (i.mtu.isPresent) i.mtu.get().toString().trim() else "",
junkPacketCount = if(i.junkPacketCount.isPresent) i.junkPacketCount.get().toString() else "",
junkPacketMinSize = if(i.junkPacketMinSize.isPresent) i.junkPacketMinSize.get().toString() else "",
junkPacketMaxSize = if(i.junkPacketMaxSize.isPresent) i.junkPacketMaxSize.get().toString() else "",
initPacketJunkSize = if(i.initPacketJunkSize.isPresent) i.initPacketJunkSize.get().toString() else "",
responsePacketJunkSize = if(i.responsePacketJunkSize.isPresent) i.responsePacketJunkSize.get().toString() else "",
initPacketMagicHeader = if(i.initPacketMagicHeader.isPresent) i.initPacketMagicHeader.get().toString() else "",
responsePacketMagicHeader = if(i.responsePacketMagicHeader.isPresent) i.responsePacketMagicHeader.get().toString() else "",
underloadPacketMagicHeader = if(i.underloadPacketMagicHeader.isPresent) i.underloadPacketMagicHeader.get().toString() else "",
transportPacketMagicHeader = if(i.transportPacketMagicHeader.isPresent) i.transportPacketMagicHeader.get().toString() else "",
)
}
}
}
@@ -1,13 +1,13 @@
package com.zaneschepke.wireguardautotunnel.ui.models
package com.zaneschepke.wireguardautotunnel.ui.screens.config.model
import com.wireguard.config.Peer
data class PeerProxy(
var publicKey: String = "",
var preSharedKey: String = "",
var persistentKeepalive: String = "",
var endpoint: String = "",
var allowedIps: String = IPV4_WILDCARD.joinToString(", ").trim()
val publicKey: String = "",
val preSharedKey: String = "",
val persistentKeepalive: String = "",
val endpoint: String = "",
val allowedIps: String = IPV4_WILDCARD.joinToString(", ").trim()
) {
companion object {
fun from(peer: Peer): PeerProxy {
@@ -35,6 +35,31 @@ data class PeerProxy(
)
}
fun from(peer: org.amnezia.awg.config.Peer) : PeerProxy {
return PeerProxy(
publicKey = peer.publicKey.toBase64(),
preSharedKey =
if (peer.preSharedKey.isPresent) {
peer.preSharedKey.get().toBase64().trim()
} else {
""
},
persistentKeepalive =
if (peer.persistentKeepalive.isPresent) {
peer.persistentKeepalive.get().toString().trim()
} else {
""
},
endpoint =
if (peer.endpoint.isPresent) {
peer.endpoint.get().toString().trim()
} else {
""
},
allowedIps = peer.allowedIps.joinToString(", ").trim(),
)
}
val IPV4_PUBLIC_NETWORKS =
setOf(
"0.0.0.0/5",
@@ -29,6 +29,7 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.overscroll
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.ClickableText
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Create
import androidx.compose.material.icons.filled.FileOpen
@@ -71,25 +72,32 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import com.journeyapps.barcodescanner.ScanContract
import com.journeyapps.barcodescanner.ScanOptions
import com.wireguard.android.backend.Tunnel
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
import com.zaneschepke.wireguardautotunnel.ui.CaptureActivityPortrait
import com.zaneschepke.wireguardautotunnel.ui.Screen
@@ -106,6 +114,8 @@ import com.zaneschepke.wireguardautotunnel.util.truncateWithEllipsis
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.Timer
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@@ -124,6 +134,25 @@ fun MainScreen(
val sheetState = rememberModalBottomSheetState()
var showBottomSheet by remember { mutableStateOf(false) }
// Nested scroll for control FAB
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
// Hide FAB
if (available.y < -1) {
isVisible.value = false
}
// Show FAB
if (available.y > 1) {
isVisible.value = true
}
return Offset.Zero
}
}
}
var showDeleteTunnelAlertDialog by remember { mutableStateOf(false) }
var selectedTunnel by remember { mutableStateOf<TunnelConfig?>(null) }
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
@@ -234,11 +263,14 @@ fun MainScreen(
Scaffold(
modifier =
Modifier.pointerInput(Unit) {
detectTapGestures(
onTap = {
selectedTunnel = null
},
)
if(uiState.tunnels.isNotEmpty()) {
detectTapGestures(
onTap = {
selectedTunnel = null
},
)
}
},
floatingActionButtonPosition = FabPosition.End,
floatingActionButton = {
@@ -276,16 +308,6 @@ fun MainScreen(
}
},
) {
AnimatedVisibility(uiState.tunnels.isEmpty(), exit = fadeOut(), enter = fadeIn()) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxSize(),
) {
Text(text = stringResource(R.string.no_tunnels), fontStyle = FontStyle.Italic)
}
}
if (showBottomSheet) {
ModalBottomSheet(
onDismissRequest = { showBottomSheet = false },
@@ -377,13 +399,47 @@ fun MainScreen(
verticalArrangement = Arrangement.Top,
modifier =
Modifier
.fillMaxWidth()
.overscroll(ScrollableDefaults.overscrollEffect()),
.fillMaxSize()
.overscroll(ScrollableDefaults.overscrollEffect())
.nestedScroll(nestedScrollConnection),
state = rememberLazyListState(0, uiState.tunnels.count()),
userScrollEnabled = true,
reverseLayout = false,
flingBehavior = ScrollableDefaults.flingBehavior(),
) {
item {
val gettingStarted = buildAnnotatedString {
append(stringResource(id = R.string.see_the))
append(" ")
pushStringAnnotation(tag = "gettingStarted", annotation = stringResource(id = R.string.getting_started_url))
withStyle(style = SpanStyle(color = MaterialTheme.colorScheme.primary)) {
append(stringResource(id = R.string.getting_started_guide))
}
pop()
append(" ")
append(stringResource(R.string.unsure_how))
append(".")
}
AnimatedVisibility(
uiState.tunnels.isEmpty(), exit = fadeOut(), enter = fadeIn()) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.padding(top = 100.dp)
) {
Text(text = stringResource(R.string.no_tunnels), fontStyle = FontStyle.Italic)
ClickableText(
modifier = Modifier.padding(vertical = 10.dp, horizontal = 24.dp),
text = gettingStarted,
style = MaterialTheme.typography.bodyMedium.copy(color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.Center),
) {
gettingStarted.getStringAnnotations(tag = "gettingStarted", it, it).firstOrNull()?.let { annotation ->
appViewModel.openWebPage(annotation.item, context)
}
}
}
}
}
item {
if (uiState.settings.isAutoTunnelEnabled) {
val autoTunnelingLabel = buildAnnotatedString {
@@ -439,7 +495,7 @@ fun MainScreen(
val leadingIconColor =
(if (
uiState.vpnState.tunnelConfig?.name == tunnel.name &&
uiState.vpnState.status == Tunnel.State.UP
uiState.vpnState.status == TunnelState.UP
) {
uiState.vpnState.statistics
?.mapPeerStats()
@@ -485,7 +541,7 @@ fun MainScreen(
text = tunnel.name.truncateWithEllipsis(Constants.ALLOWED_DISPLAY_NAME_LENGTH),
onHold = {
if (
(uiState.vpnState.status == Tunnel.State.UP) &&
(uiState.vpnState.status == TunnelState.UP) &&
(tunnel.name == uiState.vpnState.tunnelConfig?.name)
) {
appViewModel.showSnackbarMessage(Event.Message.TunnelOffAction.message)
@@ -497,7 +553,7 @@ fun MainScreen(
onClick = {
if (!WireGuardAutoTunnel.isRunningOnAndroidTv()) {
if (
uiState.vpnState.status == Tunnel.State.UP &&
uiState.vpnState.status == TunnelState.UP &&
(uiState.vpnState.tunnelConfig?.name == tunnel.name)
) {
expanded.value = !expanded.value
@@ -555,7 +611,7 @@ fun MainScreen(
} else {
val checked by remember {
derivedStateOf {
(uiState.vpnState.status == Tunnel.State.UP &&
(uiState.vpnState.status == TunnelState.UP &&
tunnel.name == uiState.vpnState.tunnelConfig?.name)
}
}
@@ -597,7 +653,7 @@ fun MainScreen(
modifier = Modifier.focusRequester(focusRequester),
onClick = {
if (
uiState.vpnState.status == Tunnel.State.UP &&
uiState.vpnState.status == TunnelState.UP &&
(uiState.vpnState.tunnelConfig?.name == tunnel.name)
) {
expanded.value = !expanded.value
@@ -620,7 +676,7 @@ fun MainScreen(
IconButton(
onClick = {
if (
uiState.vpnState.status == Tunnel.State.UP &&
uiState.vpnState.status == TunnelState.UP &&
tunnel.name == uiState.vpnState.tunnelConfig?.name
) {
appViewModel.showSnackbarMessage(
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.main
import com.zaneschepke.wireguardautotunnel.data.model.Settings
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnState
import com.zaneschepke.wireguardautotunnel.util.TunnelConfigs
@@ -9,8 +9,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.wireguard.config.Config
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.data.model.Settings
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
@@ -99,7 +99,7 @@ constructor(
}
private fun validateConfigString(config: String) {
TunnelConfig.configFromQuick(config)
TunnelConfig.configFromWgQuick(config)
}
suspend fun onTunnelQrResult(result: String): Result<Unit> {
@@ -1,6 +1,6 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.options
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
data class OptionsUiState(
val id: String? = null,
@@ -4,7 +4,7 @@ import androidx.compose.ui.util.fastFirstOrNull
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.Event
@@ -15,6 +15,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@@ -43,9 +44,11 @@ constructor(
)
fun init(tunnelId: String) {
_optionState.value = _optionState.value.copy(
id = tunnelId,
)
_optionState.update {
it.copy(
id = tunnelId
)
}
}
fun onDeleteRunSSID(ssid: String) = viewModelScope.launch(Dispatchers.IO) {
@@ -74,6 +74,7 @@ import com.wireguard.android.backend.Tunnel
import com.wireguard.android.backend.WgQuickBackend
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
import com.zaneschepke.wireguardautotunnel.ui.AppViewModel
import com.zaneschepke.wireguardautotunnel.ui.Screen
import com.zaneschepke.wireguardautotunnel.ui.common.ClickableIconButton
@@ -550,31 +551,43 @@ fun SettingsScreen(
}
}
}
if (WgQuickBackend.hasKernelSupport()) {
Surface(
tonalElevation = 2.dp,
shadowElevation = 2.dp,
shape = RoundedCornerShape(12.dp),
color = MaterialTheme.colorScheme.surface,
modifier = Modifier
.fillMaxWidth(fillMaxWidth)
.padding(vertical = 10.dp),
Surface(
tonalElevation = 2.dp,
shadowElevation = 2.dp,
shape = RoundedCornerShape(12.dp),
color = MaterialTheme.colorScheme.surface,
modifier = Modifier
.fillMaxWidth(fillMaxWidth)
.padding(vertical = 10.dp),
) {
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top,
modifier = Modifier.padding(15.dp),
) {
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top,
modifier = Modifier.padding(15.dp),
) {
SectionTitle(
title = stringResource(id = R.string.kernel),
padding = screenPadding,
)
SectionTitle(
title = stringResource(id = R.string.backend),
padding = screenPadding,
)
ConfigurationToggle(
stringResource(R.string.use_amnezia),
enabled =
!(uiState.settings.isAutoTunnelEnabled ||
uiState.settings.isAlwaysOnVpnEnabled ||
(uiState.vpnState.status == TunnelState.UP) || uiState.settings.isKernelEnabled),
checked = uiState.settings.isAmneziaEnabled,
padding = screenPadding,
onCheckChanged = {
viewModel.onToggleAmnezia()
},
)
if (WgQuickBackend.hasKernelSupport()) {
ConfigurationToggle(
stringResource(R.string.use_kernel),
enabled =
!(uiState.settings.isAutoTunnelEnabled ||
uiState.settings.isAlwaysOnVpnEnabled ||
(uiState.vpnState.status == Tunnel.State.UP)),
(uiState.vpnState.status == TunnelState.UP)),
checked = uiState.settings.isKernelEnabled,
padding = screenPadding,
onCheckChanged = {
@@ -1,7 +1,7 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.settings
import com.zaneschepke.wireguardautotunnel.data.model.Settings
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnState
data class SettingsUiState(
@@ -8,7 +8,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.wireguard.android.util.RootShell
import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
import com.zaneschepke.wireguardautotunnel.data.model.Settings
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
@@ -162,12 +162,29 @@ constructor(
)
}
fun onToggleAmnezia() = viewModelScope.launch {
if(uiState.value.settings.isKernelEnabled) {
saveKernelMode(false)
}
saveAmneziaMode(!uiState.value.settings.isAmneziaEnabled)
}
private fun saveAmneziaMode(on: Boolean) {
saveSettings(
uiState.value.settings.copy(
isAmneziaEnabled = on
)
)
}
fun onToggleKernelMode(): Result<Unit> {
if (!uiState.value.settings.isKernelEnabled) {
try {
rootShell.start()
Timber.i("Root shell accepted!")
saveKernelMode(on = true)
saveAmneziaMode(false)
} catch (e: RootShell.RootShellException) {
Timber.e(e)
saveKernelMode(on = false)
@@ -37,6 +37,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
@@ -106,7 +107,7 @@ fun SupportScreen(
modifier = Modifier.padding(bottom = 20.dp),
)
TextButton(
onClick = { appViewModel.openWebPage(context.resources.getString(R.string.docs_url)) },
onClick = { appViewModel.openWebPage(context.resources.getString(R.string.docs_url), context) },
modifier = Modifier
.padding(vertical = 5.dp)
.focusRequester(focusRequester),
@@ -122,7 +123,13 @@ fun SupportScreen(
Text(
stringResource(id = R.string.docs_description),
textAlign = TextAlign.Justify,
modifier = Modifier.padding(start = 10.dp),
modifier = Modifier
.padding(start = 10.dp)
.weight(
weight = 1.0f,
fill = false,
),
softWrap = true
)
}
Icon(
@@ -136,7 +143,7 @@ fun SupportScreen(
color = MaterialTheme.colorScheme.onBackground,
)
TextButton(
onClick = { appViewModel.openWebPage(context.resources.getString(R.string.discord_url)) },
onClick = { appViewModel.openWebPage(context.resources.getString(R.string.telegram_url), context) },
modifier = Modifier.padding(vertical = 5.dp),
) {
Row(
@@ -145,7 +152,7 @@ fun SupportScreen(
modifier = Modifier.fillMaxWidth(),
) {
Row {
val icon = ImageVector.vectorResource(R.drawable.discord)
val icon = ImageVector.vectorResource(R.drawable.telegram)
Icon(
icon,
icon.name,
@@ -168,7 +175,7 @@ fun SupportScreen(
color = MaterialTheme.colorScheme.onBackground,
)
TextButton(
onClick = { appViewModel.openWebPage(context.resources.getString(R.string.github_url)) },
onClick = { appViewModel.openWebPage(context.resources.getString(R.string.github_url), context) },
modifier = Modifier.padding(vertical = 5.dp),
) {
Row(
@@ -200,7 +207,7 @@ fun SupportScreen(
color = MaterialTheme.colorScheme.onBackground,
)
TextButton(
onClick = { appViewModel.launchEmail() },
onClick = { appViewModel.launchEmail(context) },
modifier = Modifier.padding(vertical = 5.dp),
) {
Row(
@@ -262,7 +269,7 @@ fun SupportScreen(
fontSize = 16.sp,
modifier =
Modifier.clickable {
appViewModel.openWebPage(context.resources.getString(R.string.privacy_policy_url))
appViewModel.openWebPage(context.resources.getString(R.string.privacy_policy_url), context)
},
)
Row(
@@ -270,8 +277,21 @@ fun SupportScreen(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(25.dp),
) {
Text("Version: ${BuildConfig.VERSION_NAME}", modifier = Modifier.focusable())
Text("Mode: ${if (uiState.settings.isKernelEnabled) "Kernel" else "Userspace"}")
val version = buildAnnotatedString {
append(stringResource(id = R.string.version))
append(": ")
append(BuildConfig.VERSION_NAME)
}
val mode = buildAnnotatedString {
append(stringResource(R.string.mode))
append(": ")
when(uiState.settings.isKernelEnabled){
true -> append(stringResource(id = R.string.kernel))
false -> append(stringResource(id = R.string.userspace))
}
}
Text(version.text, modifier = Modifier.focusable())
Text(mode.text)
}
}
}
@@ -1,5 +1,5 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.support
import com.zaneschepke.wireguardautotunnel.data.model.Settings
import com.zaneschepke.wireguardautotunnel.data.domain.Settings
data class SupportUiState(val settings: Settings = Settings())
@@ -27,6 +27,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.ClipboardManager
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
@@ -43,6 +44,8 @@ fun LogsScreen(appViewModel: AppViewModel) {
appViewModel.logs
}
val context = LocalContext.current
val lazyColumnListState = rememberLazyListState()
val clipboardManager: ClipboardManager = LocalClipboardManager.current
val scope = rememberCoroutineScope()
@@ -57,7 +60,7 @@ fun LogsScreen(appViewModel: AppViewModel) {
floatingActionButton = {
FloatingActionButton(
onClick = {
appViewModel.saveLogsToFile()
appViewModel.saveLogsToFile(context)
},
shape = RoundedCornerShape(16.dp),
containerColor = MaterialTheme.colorScheme.primary,
@@ -2,7 +2,7 @@ package com.zaneschepke.wireguardautotunnel.util
object Constants {
const val BASE_LOG_FILE_NAME = "wgtunnel-logs"
const val BASE_LOG_FILE_NAME = "wg_tunnel_logs"
const val LOG_BUFFER_SIZE = 3_000L
const val MANUAL_TUNNEL_CONFIG_ID = "0"
@@ -35,4 +35,6 @@ object Constants {
const val TUNNEL_EXTRA_KEY = "tunnelId"
const val UNREADABLE_SSID = "<unknown ssid>"
}
@@ -2,11 +2,9 @@ package com.zaneschepke.wireguardautotunnel.util
import android.content.BroadcastReceiver
import android.content.pm.PackageInfo
import com.wireguard.android.backend.Statistics
import com.wireguard.android.backend.Statistics.PeerStats
import com.wireguard.crypto.Key
import com.zaneschepke.wireguardautotunnel.data.model.TunnelConfig
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope
@@ -50,15 +48,15 @@ typealias TunnelConfigs = List<TunnelConfig>
typealias Packages = List<PackageInfo>
fun Statistics.mapPeerStats(): Map<Key, PeerStats?> {
return this.peers().associateWith { key -> (this.peer(key)) }
fun TunnelStatistics.mapPeerStats(): Map<org.amnezia.awg.crypto.Key, TunnelStatistics.PeerStats?> {
return this.getPeers().associateWith { key -> (this.peerStats(key)) }
}
fun PeerStats.latestHandshakeSeconds(): Long? {
fun TunnelStatistics.PeerStats.latestHandshakeSeconds(): Long? {
return NumberUtils.getSecondsBetweenTimestampAndNow(this.latestHandshakeEpochMillis)
}
fun PeerStats.handshakeStatus(): HandshakeStatus {
fun TunnelStatistics.PeerStats.handshakeStatus(): HandshakeStatus {
// TODO add never connected status after duration
return this.latestHandshakeSeconds().let {
when {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

+9
View File
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="50dp"
android:height="50dp"
android:viewportWidth="50"
android:viewportHeight="50">
<path
android:fillColor="#FF000000"
android:pathData="M25,2c12.703,0 23,10.297 23,23S37.703,48 25,48S2,37.703 2,25S12.297,2 25,2zM32.934,34.375c0.423,-1.298 2.405,-14.234 2.65,-16.783c0.074,-0.772 -0.17,-1.285 -0.648,-1.514c-0.578,-0.278 -1.434,-0.139 -2.427,0.219c-1.362,0.491 -18.774,7.884 -19.78,8.312c-0.954,0.405 -1.856,0.847 -1.856,1.487c0,0.45 0.267,0.703 1.003,0.966c0.766,0.273 2.695,0.858 3.834,1.172c1.097,0.303 2.346,0.04 3.046,-0.395c0.742,-0.461 9.305,-6.191 9.92,-6.693c0.614,-0.502 1.104,0.141 0.602,0.644c-0.502,0.502 -6.38,6.207 -7.155,6.997c-0.941,0.959 -0.273,1.953 0.358,2.351c0.721,0.454 5.906,3.932 6.687,4.49c0.781,0.558 1.573,0.811 2.298,0.811C32.191,36.439 32.573,35.484 32.934,34.375z"/>
</vector>
@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_channel_background" />
<foreground android:drawable="@mipmap/ic_channel_foreground" />
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

+1
View File
@@ -0,0 +1 @@
unqualifiedResLocale=en-US
+157
View File
@@ -0,0 +1,157 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">WG Tunnel</string>
<string name="error_file_extension">Dies ist keine .conf oder .zip Datei</string>
<string name="no_tunnels">Noch keine Tunnel hinzugefügt!</string>
<string name="watcher_notification_text_active">Überwachung der Netzwerkänderungen: aktiv</string>
<string name="tunnels">Tunnel</string>
<string name="enable_auto_tunnel">Starte automatisches Verbinden</string>
<string name="tunnel_mobile_data">Verbinden im Mobilnetzwerk</string>
<string name="one_tunnel_required">Mindestens eine Verbindung wird für diese Funktion benötigt</string>
<string name="privacy_policy">Siehe Privacy Policy</string>
<string name="disable_auto_tunnel">Stoppe automatisches Verbinden</string>
<string name="okay">Okay</string>
<string name="tunnel_on_ethernet">Verbindung über Ethernet</string>
<string name="search_icon">Suchsymbol</string>
<string name="attempt_connection">Versuche zu verbinden..</string>
<string name="auto_tunneling">Automatisches verbinden</string>
<string name="default_vpn_off">Primärer VPN aus</string>
<string name="turn_on_tunnel">Für diese Altion muss eine Verbindung bestehen</string>
<string name="watcher_notification_text_paused">Überwachung der Netzwerkänderungen: pausiert</string>
<string name="tunnel_start_title">VPN verbunden</string>
<string name="tunnel_start_text">Mit Tunnel verbunden</string>
<string name="notification_permission_required">Berechtigung für die Benachrichtigung benötigt.</string>
<string name="add_trusted_ssid">Vertrauenswürdiger WiFi-Name hinzugügen</string>
<string name="prominent_background_location_message">Diese Funktion benötigt Standortberechtigung im Hintergrund um die WiFi SSIDs auch wenn die Applikation geschlossen ist zu überwachen. Für mehr Infos, siehe Privacy Policy auf der Hilfeseite.</string>
<string name="prominent_background_location_title">Vereinbarung der Standortberechtigung im Hintergrund</string>
<string name="thank_you">Danke fürs benutzen von WG Tunnel!</string>
<string name="trusted_ssid_empty_description">SSID eingeben</string>
<string name="trusted_ssid_value_description">SSID bestätigen</string>
<string name="add_tunnels_text">Von Datei oder ZIP hinzufügen</string>
<string name="open_file">Datei geöffnet</string>
<string name="add_from_qr">Von QR-Code hinzufügen</string>
<string name="qr_scan">Scanne QR</string>
<string name="tunnel_name">Verbindungsname</string>
<string name="add_tunnel">Verbindung hinzufügen</string>
<string name="exclude">Ausgenommen</string>
<string name="include">Eingeschlossen</string>
<string name="tunnel_all">Verbinde alle Apps</string>
<string name="config_changes_saved">Änderungen der Konfiguration gespeichert.</string>
<string name="save_changes">Speichern</string>
<string name="icon">Symbol</string>
<string name="no_thanks">Nein danke</string>
<string name="turn_on">Einschalten</string>
<string name="map">Karte</string>
<string name="public_key">Öffentlicher Schlüssel</string>
<string name="addresses">Adressen</string>
<string name="dns_servers">DNS-Server</string>
<string name="mtu">MTU</string>
<string name="peer">Peer</string>
<string name="allowed_ips">Erlaubte IPs</string>
<string name="endpoint">Endpunkt</string>
<string name="name">Name</string>
<string name="restart">Verbindung neustarten</string>
<string name="vpn_connection_failed">Verbindung fehlgeschlagen</string>
<string name="always_on_vpn_support">Erlaube Always-On VPN</string>
<string name="location_services_not_detected">Standortdienste Nicht Erkannt</string>
<string name="hint_search_packages">Suche packete</string>
<string name="clear_icon">Lösche Symbol</string>
<string name="vpn_starting">VPN startet</string>
<string name="db_name">wg-tunnel-db</string>
<string name="scanning_qr">Scanne nach QR</string>
<string name="none">Keine vertrauenswürdigen WiFi Namen</string>
<string name="other">Andere</string>
<string name="vpn_on">VPN an</string>
<string name="vpn_off">VPN aus</string>
<string name="default_vpn_on">Primärer VPN an</string>
<string name="create_import">Starte von Grund auf neu</string>
<string name="turn_off_auto">Für diese Aktion muss automatisches Verbinden ausgeschaltet oder pausiert sein</string>
<string name="add_peer">Peer hinzufügen</string>
<string name="done">Erledigt</string>
<string name="rotate_keys">Schlüssel ändern</string>
<string name="private_key">Privater Schlüssel</string>
<string name="copy_public_key">Öffentlicher Schlüssel kopieren</string>
<string name="base64_key">base64-Schlüssel</string>
<string name="comma_separated_list">Kommaseparierte Liste</string>
<string name="delete_tunnel">Tunnel löschen</string>
<string name="persistent_keepalive">Dauerhaftes Keepalive</string>
<string name="background_location_required">Hintergrund Standortdienste erforderlich</string>
<string name="enable_app_lock">App Sperre aktiviert</string>
<string name="discord_description">Tritt der Community bei</string>
<string name="interface_">Schnittstelle</string>
<string name="listen_port">Eingehender Port</string>
<string name="random">(zufällig)</string>
<string name="optional">(nicht erforderlich)</string>
<string name="optional_no_recommend">(nicht erforderlich, aber empfohlen)</string>
<string name="seconds">Sekunden</string>
<string name="cancel">Abbrechen</string>
<string name="preshared_key">Geteilter Schlüssel</string>
<string name="enabled_app_shortcuts">Aktiviere App Verknüpfungen</string>
<string name="exported_configs_message">Konfigurationen in den Download Ordner exportiert</string>
<string name="tunnel_on_wifi">Tunnel bei nicht vertrauenswürdigem Wifi</string>
<string name="email_subject">WG Tunnel Unterstützung</string>
<string name="docs_description">Lese die Dokumentation</string>
<string name="email_description">Sende mir eine E-Mail</string>
<string name="support_help_text">Bei Fehlern oder Verbesserungsvorschlägen stehen folgende Ressourcen zur Verfügung:</string>
<string name="error_root_denied">Root Shell verboten</string>
<string name="error_no_file_explorer">Kein Datei-Explorer installiert</string>
<string name="location_services_missing_message">Die App konnte keine aktivierten Standortdienste auf deinem Gerät erkennen. Dies kann auf manchen Geräten ein Auslesen des aktuellen WIFI-Names für die \"Nicht vertrauenswürdiges WIFI\" Funktion verhindern. Möchtest du trotzdem fortfahren ?</string>
<string name="auto_tunnel_title">Auto-Tunnel Service</string>
<string name="delete_tunnel_message">Bist du sicher, dass du den Tunnel löschen möchtest?</string>
<string name="yes">Ja</string>
<string name="resume">Fortsetzen</string>
<string name="pause">Pausieren</string>
<string name="paused">Pausiert</string>
<string name="active">Aktiv</string>
<string name="go">Gehe</string>
<string name="excluded">Ausgeschlossen</string>
<string name="all">Alle</string>
<string name="always_on_disabled">Always-on VPN wollte eine Tunnel starten, aber dieses Feature ist in den Einstellungen deaktiviert.</string>
<string name="no_browser_detected">Kein Browser erkannt</string>
<string name="open_issue">Öffne ein Issue</string>
<string name="read_logs">Lese die Logs</string>
<string name="auto">(automatisch)</string>
<string name="config_parse_error">Fehler beim lesen der Konfiguration</string>
<string name="incorrect_pin">PIN is nicht korrekt</string>
<string name="pin_created">PIN erfolgreich angelegt</string>
<string name="enter_pin">Gib deine PIN ein</string>
<string name="auto_off">Auto-Tunnel pausieren</string>
<string name="auto_tun_on">Auto-Tunnel fortsetzen</string>
<string name="auto_tun_off">Auto-Tunnel pausieren</string>
<string name="version">Version</string>
<string name="mode">Modus</string>
<string name="userspace">Userspace</string>
<string name="settings">Einstellungen</string>
<string name="support">Unterstützung</string>
<string name="watcher_channel_id">Beobachter Kanal</string>
<string name="error_authentication_failed">Anmeldung fehlgeschlagen</string>
<string name="export_configs">Exportiere Konfigurationen</string>
<string name="unknown_error">Ein unbekannter Fehler ist aufgetreten</string>
<string name="email_chooser">Sende eine E-Mail…</string>
<string name="error_authorization_failed">Fehler bei der Authorisierung</string>
<string name="location_services_required">Standortdienste erforderlich</string>
<string name="precise_location_required">Genauer Standort erforderlich</string>
<string name="error_invalid_code">Fehlerhafter QR code</string>
<string name="error_none">Kein Fehler</string>
<string name="tunneling_apps">Tunnel Anwendungen</string>
<string name="included">Hinzugefügt</string>
<string name="no_email_detected">Keine E-Mail Anwendung erkannt</string>
<string name="logs_saved">Logs im Download Ordner gespeichert</string>
<string name="create_pin">Erstelle eine PIN</string>
<string name="use_tunnel_on_wifi_name">Tunnel in Wifi-Namen verwenden</string>
<string name="no_wifi_names_configured">Keine Wifi-Namen für diesen Tunnel konfiguriert</string>
<string name="disabled">Deaktiviert</string>
<string name="mobile_data_tunnel">Als Tunnel für Mobile Daten setzen</string>
<string name="general">Allgemein</string>
<string name="restart_on_ping">Neustart bei PING Fehler (Beta)</string>
<string name="edit_tunnel">Tunnel bearbeiten</string>
<string name="set_primary_tunnel">Als Primären Tunnel setzen</string>
<string name="auto_on">Auto-Tunnel fortsetzen</string>
<string name="vpn_channel_id">VPN Kanal</string>
<string name="vpn_channel_name">VPN Benachrichtigungskanal</string>
<string name="watcher_channel_name">Beobachter Benachrichtigungskanal</string>
<string name="turn_off_tunnel">Aktion erfordert deaktivierten Tunnel</string>
<string name="kernel">Kernel</string>
<string name="use_kernel">Verwende das Kernel Modul</string>
<string name="error_ssid_exists">SSID existiert bereits</string>
</resources>
+157
View File
@@ -0,0 +1,157 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="map">Mapa</string>
<string name="allowed_ips">Allowed IPs</string>
<string name="email_chooser">Enviar un email…</string>
<string name="go">ir</string>
<string name="discord_description">Únete a la comunidad</string>
<string name="kernel">Kernel</string>
<string name="restart">Reiniciar túnel</string>
<string name="vpn_connection_failed">Error de conexión</string>
<string name="error_none">Sin error</string>
<string name="other">Otros</string>
<string name="add_peer">Añadir peer</string>
<string name="done">Hecho</string>
<string name="copy_public_key">Copiar public key</string>
<string name="base64_key">clave base64</string>
<string name="exported_configs_message">Configuración exportada a Descargas</string>
<string name="tunnel_on_wifi">Túnel en Wi-Fi no de confianza</string>
<string name="location_services_missing_message">La app no detecta activado el servicio de ubicación en tu dispositivo. Dependiendo del dispositivo, esto podría hacer que la característica de Wi-Fi no de confianza falle al leer el nombre Wi-Fi. ¿Quieres continuar de todas formas?</string>
<string name="excluded">excluida(s)</string>
<string name="all">todas</string>
<string name="mobile_data_tunnel">Establecer como túnel en datos móviles</string>
<string name="use_tunnel_on_wifi_name">Usar tunnel en nombre Wi-Fi</string>
<string name="disabled">desactivado</string>
<string name="version">Versión</string>
<string name="userspace">Espacio del usuario</string>
<string name="mode">Modo</string>
<string name="support">Ayuda</string>
<string name="private_key">Clave privada</string>
<string name="trusted_ssid_value_description">Enviar SSID</string>
<string name="trusted_ssid_empty_description">Introducir SSID</string>
<string name="add_tunnels_text">Añadir desde archivo o zip</string>
<string name="open_file">Abrir archivo</string>
<string name="add_from_qr">Añadir mediante código QR</string>
<string name="qr_scan">Escanear QR</string>
<string name="tunnel_name">Nombre de túnel</string>
<string name="add_tunnel">Añadir túnel</string>
<string name="exclude">Excluir</string>
<string name="include">Incluir</string>
<string name="tunnel_all">Todas las apps por el túnel</string>
<string name="config_changes_saved">Cambios de configuración guardados.</string>
<string name="save_changes">Guardar</string>
<string name="icon">Icono</string>
<string name="no_thanks">No gracias</string>
<string name="turn_on">Activar</string>
<string name="mtu">MTU</string>
<string name="dns_servers">DNS servers</string>
<string name="addresses">Addresses</string>
<string name="public_key">Public key</string>
<string name="error_file_extension">No es un archivo .conf o .zip</string>
<string name="turn_off_tunnel">Desactiva antes el túnel</string>
<string name="no_tunnels">¡Ningún túnel añadido aún!</string>
<string name="watcher_notification_text_paused">Monitoreando cambios de red: En pausa</string>
<string name="watcher_notification_text_active">Monitoreando cambios de red: Activado</string>
<string name="tunnel_start_title">VPN conectada</string>
<string name="tunnel_start_text">Conectado al túnel</string>
<string name="notification_permission_required">Necesita permiso de notificaciones.</string>
<string name="add_trusted_ssid">Añadir nombres Wi-Fi de confianza</string>
<string name="tunnels">Túneles</string>
<string name="enable_auto_tunnel">Iniciar túnel-automático</string>
<string name="disable_auto_tunnel">Parar túnel-automático</string>
<string name="tunnel_mobile_data">Activar túnel en datos móviles</string>
<string name="one_tunnel_required">Esta característica necesita ser usada en almenos por un túnel</string>
<string name="privacy_policy">Ver Política de Privacidad</string>
<string name="okay">OK</string>
<string name="tunnel_on_ethernet">Túnel en ethernet</string>
<string name="prominent_background_location_title">Divulgación de la ubicación en segundo plano</string>
<string name="thank_you">¡Gracias por usar WG Tunnel!</string>
<string name="endpoint">Endpoint</string>
<string name="peer">Peer</string>
<string name="name">Nombre</string>
<string name="always_on_vpn_support">Permitir VPN siempre-activada</string>
<string name="location_services_not_detected">Servicios de Ubicación No Detectados</string>
<string name="hint_search_packages">Buscar paquetes</string>
<string name="clear_icon">Icono claro</string>
<string name="attempt_connection">Intentando conexión...</string>
<string name="vpn_starting">Iniciando VPN</string>
<string name="db_name">wg-tunnel-db</string>
<string name="scanning_qr">Escaneando QR</string>
<string name="none">Sin nombres Wi-Fi de confianza</string>
<string name="auto_tunneling">Túnel-automático</string>
<string name="vpn_on">VPN on</string>
<string name="vpn_off">VPN off</string>
<string name="default_vpn_on">VPN Principal on</string>
<string name="default_vpn_off">VPN Principal off</string>
<string name="create_import">Crear desde cero</string>
<string name="turn_off_auto">La acción necesita que túnel-automático esté desactivado o en pausa</string>
<string name="turn_on_tunnel">La acción necesita un túnel activado</string>
<string name="rotate_keys">Rotar claves</string>
<string name="comma_separated_list">lista separada por comas</string>
<string name="random">(aleatorio)</string>
<string name="optional">(opcional)</string>
<string name="optional_no_recommend">(opcional, no recomendado)</string>
<string name="seconds">segundos</string>
<string name="cancel">Cancelar</string>
<string name="error_authentication_failed">Fallo de autenticación</string>
<string name="error_authorization_failed">Fallo de autorización</string>
<string name="enabled_app_shortcuts">Habilitar acesos directos de app</string>
<string name="export_configs">Exportar configuración</string>
<string name="background_location_required">Se necesita ubicación en segundo plano</string>
<string name="location_services_required">Se necesita servicio de ubicación</string>
<string name="precise_location_required">Necesita ubicación precisa</string>
<string name="unknown_error">Error desconocido</string>
<string name="email_subject">Ayuda WG Tunnel</string>
<string name="interface_">Interfaz</string>
<string name="listen_port">Puerto de escucha</string>
<string name="preshared_key">Clave previamente compartida</string>
<string name="persistent_keepalive">Keepalive persistente</string>
<string name="search_icon">Icono de búsqueda</string>
<string name="docs_description">Leer documentación</string>
<string name="email_description">Envíame un email</string>
<string name="support_help_text">Si tienes problemas, ideas para mejoras, o simlemente comprometerte, tienes disponibles los siguientes recursos:</string>
<string name="use_kernel">Usar módulo del Kernel</string>
<string name="error_ssid_exists">SSID existente</string>
<string name="error_root_denied">Shell root denegado</string>
<string name="error_no_file_explorer">Explorador de archivos no instalado</string>
<string name="error_invalid_code">Código QR no valido</string>
<string name="auto_tunnel_title">Servicio túnel-automático</string>
<string name="delete_tunnel">Eliminar túnel</string>
<string name="delete_tunnel_message">¿Estás seguro de que quieres eliminar este túnel?</string>
<string name="yes"></string>
<string name="resume">Reanudar</string>
<string name="pause">Pausar</string>
<string name="active">activado</string>
<string name="paused">en pausa</string>
<string name="tunneling_apps">Apps por el túnel</string>
<string name="included">incluida(s)</string>
<string name="always_on_disabled">VPN siempre-activada ha intentado iniciar un túnel, pero está característica está deshabilitada en los ajustes.</string>
<string name="no_email_detected">Ninguna app de email detectada</string>
<string name="no_browser_detected">Ningún navegador detectado</string>
<string name="logs_saved">Registros guardados en Descargas</string>
<string name="open_issue">Abrir una incidencia</string>
<string name="read_logs">Leer los registros</string>
<string name="auto">(automático)</string>
<string name="config_parse_error">Fallo al analizar la configuración</string>
<string name="incorrect_pin">El pin no es correcto</string>
<string name="pin_created">Pin creado con éxito</string>
<string name="enter_pin">Introduce tu pin</string>
<string name="create_pin">Crear pin</string>
<string name="enable_app_lock">Bloqueo de app activado</string>
<string name="restart_on_ping">Reiniciar al fallar ping (beta)</string>
<string name="set_primary_tunnel">Establecer como túnel Principal</string>
<string name="no_wifi_names_configured">No hay nombres Wi-Fi configurados para este túnel</string>
<string name="general">General</string>
<string name="edit_tunnel">Editar túnel</string>
<string name="auto_on">Reanudar túnel-automático</string>
<string name="auto_off">Pausar túnel-automático</string>
<string name="auto_tun_on">Reanudar el túnel automático</string>
<string name="auto_tun_off">Pausa del túnel automático</string>
<string name="settings">Ajustes</string>
<string name="app_name">WG Tunnel</string>
<string name="vpn_channel_id">Canal VPN</string>
<string name="vpn_channel_name">Canal de notificación VPN</string>
<string name="watcher_channel_id">Canal del obvervador</string>
<string name="watcher_channel_name">Canal de notificación del obvervador</string>
<string name="prominent_background_location_message">La monitorización SSID Wi-Fi necesita de permiso de ubicación en segundo plano incluso si la app está cerrada. Mira el enlace a la Política de Privacidad en la pantalla de ayuda para más detalles.</string>
</resources>
+3
View File
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
</resources>
+162
View File
@@ -0,0 +1,162 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">WG Tunnel</string>
<string name="vpn_channel_id">VPN Kanalı</string>
<string name="vpn_channel_name">VPN Bildirim Kanalı</string>
<string name="watcher_channel_id">İzleyici Kanalı</string>
<string name="watcher_channel_name">İzleyici Bildirim Kanalı</string>
<string name="github_url" translatable="false">https://github.com/zaneschepke/wgtunnel/issues</string>
<string name="docs_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/overview.html</string>
<string name="privacy_policy_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/privacypolicy.html</string>
<string name="error_file_extension">Dosya bir .conf veya .zip değil</string>
<string name="turn_off_tunnel">Eylem tünelin kapalı olmasını gerektiriyor</string>
<string name="no_tunnels">Henüz bir tünel ekli değil!</string>
<string name="discord_url" translatable="false">https://discord.gg/rbRRNh6H7V</string>
<string name="watcher_notification_text_active">Ağ durumu değişikliklerini izleme: etkin</string>
<string name="watcher_notification_text_paused">Ağ durumu değişikliklerini izleme: duraklatıldı</string>
<string name="tunnel_start_title">VPN bağlandı</string>
<string name="tunnel_start_text">Tünele bağlı</string>
<string name="notification_permission_required">Bildirim izni gerekli.</string>
<string name="add_trusted_ssid">Güvenilir Wi-Fi adı ekle</string>
<string name="tunnels">Tüneller</string>
<string name="enable_auto_tunnel">Otomatik tünellemeyi başlat</string>
<string name="disable_auto_tunnel">Otomatik tünellemeyi durdur</string>
<string name="tunnel_mobile_data">Mobil veri üzerinde tünel</string>
<string name="one_tunnel_required">Bu özelliği kullanabilmek için en az bir tünel gereklidir.</string>
<string name="privacy_policy">Gizlilik Politikası</string>
<string name="okay">Tamam</string>
<string name="tunnel_on_ethernet">Ethernet üzerinde tünel</string>
<string name="prominent_background_location_message">Bu özellik, uygulama kapalıyken bile Wi-Fi SSID izlemesini etkinleştirmek için arka plan konumu izni gerektirir. Daha fazla bilgi için lütfen Destek ekranında bağlantısı verilen Gizlilik Politikasına bakın.</string>
<string name="prominent_background_location_title">Arka Plan Konum Açıklaması</string>
<string name="thank_you">WG Tunnel\'i kullandığınız için teşekkür ederiz!</string>
<string name="trusted_ssid_empty_description">SSID\'yi gir</string>
<string name="trusted_ssid_value_description">SSID\'yi gönder</string>
<string name="add_tunnels_text">Dosyadan veya ZIP\'ten ekle</string>
<string name="open_file">Dosya Aç</string>
<string name="add_from_qr">QR kodundan ekle</string>
<string name="qr_scan">QR kodu tarat</string>
<string name="tunnel_name">Tünel adı</string>
<string name="add_tunnel">Tünel ekle</string>
<string name="exclude">Hariç tut</string>
<string name="include">Dahil et</string>
<string name="tunnel_all">Tüm uygulamaları dahil et</string>
<string name="config_changes_saved">Yapılandırma değişiklikleri kaydedildi.</string>
<string name="save_changes">Kaydet</string>
<string name="icon">Simge</string>
<string name="no_thanks">Hayır, teşekkürler</string>
<string name="turn_on"></string>
<string name="map">Harita</string>
<string name="public_key">Genel anahtar</string>
<string name="addresses">Adresler</string>
<string name="dns_servers">DNS sunucuları</string>
<string name="mtu">MTU</string>
<string name="peer">Eş (peer)</string>
<string name="allowed_ips">İzin verilen IP\'ler</string>
<string name="endpoint">Uç nokta (endpoint)</string>
<string name="name">Ad</string>
<string name="restart">Tüneli Yeniden Başlat</string>
<string name="vpn_connection_failed">Bağlantı kurulamadı</string>
<string name="always_on_vpn_support">Her Zaman Açık VPN\'e İzin Ver</string>
<string name="location_services_not_detected">Konum Hizmetleri Algılanmadı</string>
<string name="hint_search_packages">Uygulama arayın</string>
<string name="clear_icon">Simgeyi Temizle</string>
<string name="search_icon">Simge Ara</string>
<string name="attempt_connection">Bağlantı kurulmaya çalışılıyor..</string>
<string name="vpn_starting">VPN başlatılıyor</string>
<string name="db_name">wg-tunnel-db</string>
<string name="scanning_qr">QR taranıyor</string>
<string name="none">Güvenilir Wi-Fi adı yok</string>
<string name="other">Diğer</string>
<string name="auto_tunneling">Otomatik tünelleme</string>
<string name="vpn_on">VPN açık</string>
<string name="vpn_off">VPN kapalı</string>
<string name="default_vpn_on">Birincil VPN açık</string>
<string name="default_vpn_off">Birincil VPN kapalı</string>
<string name="create_import">Sıfırdan oluştur</string>
<string name="turn_off_auto">Eylem, otomatik tünelin devre dışı bırakılmasını veya duraklatılmasını gerektirir</string>
<string name="turn_on_tunnel">Eylem aktif tünel gerektirir</string>
<string name="add_peer">Eş (peer) ekle</string>
<string name="done">Tamam</string>
<string name="interface_">Arayüz</string>
<string name="rotate_keys">Tuşları döndür</string>
<string name="private_key">Özel anahtar</string>
<string name="copy_public_key">Genel anahtarı kopyala</string>
<string name="base64_key">Base64 anahtarı</string>
<string name="comma_separated_list">Virgül ile ayrılmış liste ekleyin</string>
<string name="listen_port">Bağlantı noktası</string>
<string name="random">(Rastgele)</string>
<string name="optional">(İsteğe bağlı)</string>
<string name="optional_no_recommend">(İsteğe bağlı, önerilmez)</string>
<string name="preshared_key">Ön paylaşımlı anahtar</string>
<string name="seconds">saniye</string>
<string name="persistent_keepalive">Kalıcı olarak canlı tutma</string>
<string name="cancel">İptal</string>
<string name="error_authentication_failed">Kimlik doğrulama başarısız oldu</string>
<string name="error_authorization_failed">Yetkilendirilemedi</string>
<string name="enabled_app_shortcuts">Uygulama kısayollarını etkinleştir</string>
<string name="export_configs">Yapılandırmaları dışa aktar</string>
<string name="location_services_required">Gerekli konum hizmetleri</string>
<string name="background_location_required">Arka plan konumu gerekli</string>
<string name="precise_location_required">Kesin konum gerekli</string>
<string name="unknown_error">Bilinmeyen bir hata oluştu</string>
<string name="exported_configs_message">Yapılandırmalar indirilenler\'e aktarıldı</string>
<string name="tunnel_on_wifi">Güvenilmeyen kablosuz internet bağlantısında tünel</string>
<string name="my_email" translatable="false">support@zaneschepke.com</string>
<string name="email_subject">WG Tunnel Desteği</string>
<string name="email_chooser">Bir e-posta gönderin…</string>
<string name="go">git</string>
<string name="docs_description">Belgeleri oku</string>
<string name="discord_description">Topluluğa katıl</string>
<string name="email_description">Bana e-posta gönder</string>
<string name="support_help_text">Sorun yaşıyorsanız, iyileştirme fikirleriniz varsa veya sadece etkileşimde bulunmak istiyorsanız, aşağıdaki kaynaklar mevcuttur:</string>
<string name="kernel">Çekirdek</string>
<string name="use_kernel">Çekirdek modülünü kullan</string>
<string name="error_ssid_exists">SSID zaten var</string>
<string name="error_root_denied">Kök kabuğu reddedildi</string>
<string name="error_no_file_explorer">Yüklü dosya gezgini yok</string>
<string name="error_invalid_code">Geçersiz QR kodu</string>
<string name="error_none">Hata yok</string>
<string name="location_services_missing_message">Uygulama, cihazınızda etkinleştirilen herhangi bir konum hizmeti algılamıyor. Cihaza bağlı olarak bu, güvenilmeyen Wi-Fi özelliğinin Wi-Fi adını okuyamamasına neden olabilir. Yine de devam etmek ister misiniz?</string>
<string name="auto_tunnel_title">Otomatik Tünel Hizmeti</string>
<string name="delete_tunnel">Tüneli sil</string>
<string name="delete_tunnel_message">Bu tüneli silmek istediğinizden emin misiniz?</string>
<string name="yes">Evet</string>
<string name="resume">Devam et</string>
<string name="pause">Duraklat</string>
<string name="paused">duraklatıldı</string>
<string name="active">aktif</string>
<string name="tunneling_apps">Tünellenen uygulamalar</string>
<string name="included">dahil edildi</string>
<string name="excluded">hariç tutuldu</string>
<string name="all">tümü</string>
<string name="always_on_disabled">Her zaman açık VPN bir tünel başlatmaya çalıştı, ancak bu özellik ayarlarda devre dışı bırakıldı.</string>
<string name="no_email_detected">E-posta uygulaması algılanmadı</string>
<string name="no_browser_detected">Tarayıcı algılanmadı</string>
<string name="logs_saved">İndirilenlere kaydedilen günlükler</string>
<string name="open_issue">Bir sorun bildir</string>
<string name="read_logs">Günlükleri oku</string>
<string name="auto">(Otomatik)</string>
<string name="config_parse_error">Yapılandırma kaydedilemedi</string>
<string name="incorrect_pin">PIN kodu yanlış</string>
<string name="pin_created">Pin başarıyla oluşturuldu</string>
<string name="enter_pin">PIN kodunuzu girin</string>
<string name="create_pin">PIN kodu oluştur</string>
<string name="enable_app_lock">Uygulama kilidini etkinleştir</string>
<string name="restart_on_ping">Ping başarısız olduğunda yeniden başlat (beta)</string>
<string name="mobile_data_tunnel">Mobil veri tüneli olarak ayarla</string>
<string name="set_primary_tunnel">Birincil tünel olarak ayarla</string>
<string name="use_tunnel_on_wifi_name">Wi-Fi adında tünel kullan</string>
<string name="no_wifi_names_configured">Bu tünel için yapılandırılmış Wi-Fi adı yok</string>
<string name="general">Genel</string>
<string name="edit_tunnel">Tüneli düzenle</string>
<string name="disabled">Devre dışı</string>
<string name="auto_on">Otomatik ayarlamayı sürdür</string>
<string name="auto_off">Otomatik ayarlamayı duraklat</string>
<string name="auto_tun_on">Otomatik tüneli sürdür</string>
<string name="auto_tun_off">Otomatik tüneli duraklat</string>
<string name="version">Sürüm</string>
<string name="mode">Mod</string>
<string name="userspace">Kullanıcı alanı</string>
<string name="settings">Ayarlar</string>
<string name="support">Destek</string>
</resources>
-7
View File
@@ -1,11 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black_background">#FF1c1b20</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>
+24 -38
View File
@@ -4,32 +4,24 @@
<string name="vpn_channel_name">VPN Notification Channel</string>
<string name="watcher_channel_id">Watcher Channel</string>
<string name="watcher_channel_name">Watcher Notification Channel</string>
<string name="foreground_file">FOREGROUND_FILE</string>
<string name="github_url" translatable="false">https://github.com/zaneschepke/wgtunnel/issues</string>
<string name="docs_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/overview.html</string>
<string name="privacy_policy_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/privacypolicy.html</string>
<string name="error_file_extension">File is not a .conf or .zip</string>
<string name="turn_off_tunnel">Action requires tunnel off</string>
<string name="no_tunnels">No tunnels added yet!</string>
<string name="tunnel_exists">Tunnel name already exists</string>
<string name="discord_url" translatable="false">https://discord.gg/rbRRNh6H7V</string>
<string name="watcher_notification_title">Watcher Service</string>
<string name="watcher_notification_text_active">Monitoring network state changes: active</string>
<string name="watcher_notification_text_paused">Monitoring network state changes: paused</string>
<string name="tunnel_start_title">VPN connected</string>
<string name="tunnel_start_text">Connected to tunnel</string>
<string name="vpn_permission_required">VPN permission is required for the app to work properly. If this permission is not launching, please disable \"Always-on VPN\" in your phone settings for the official WireGuard mobile app and try again.</string>
<string name="notification_permission_required">Notifications permission required.</string>
<string name="open_settings">Open Settings</string>
<string name="add_trusted_ssid">Add trusted wifi name</string>
<string name="tunnels">Tunnels</string>
<string name="enable_auto_tunnel">Start auto-tunneling</string>
<string name="disable_auto_tunnel">Stop auto-tunneling</string>
<string name="tunnel_mobile_data">Tunnel on mobile data</string>
<string name="background_location_reason">\"Allow all the time\" location permission is required for retrieving Wi-Fi SSID in the background. Permission is needed for this feature.</string>
<string name="location_permission_reason">Location permission is required for this feature to work properly.</string>
<string name="one_tunnel_required">At least one tunnel required to use this feature</string>
<string name="retry">"Retry"</string>
<string name="privacy_policy">View Privacy Policy</string>
<string name="okay">Okay</string>
<string name="tunnel_on_ethernet">Tunnel on ethernet</string>
@@ -38,12 +30,10 @@
<string name="thank_you">Thank you for using WG Tunnel!</string>
<string name="trusted_ssid_empty_description">Enter SSID</string>
<string name="trusted_ssid_value_description">Submit SSID</string>
<string name="config_validation" translatable="false">[Interface]</string>
<string name="add_tunnels_text">Add from file or zip</string>
<string name="open_file">File Open</string>
<string name="add_from_qr">Add from QR code</string>
<string name="qr_scan">QR Scan</string>
<string name="tunnel_edit">Tunnel Edit</string>
<string name="tunnel_name">Tunnel Name</string>
<string name="add_tunnel">Add Tunnel</string>
<string name="exclude">Exclude</string>
@@ -55,34 +45,18 @@
<string name="no_thanks">No thanks</string>
<string name="turn_on">Turn on</string>
<string name="map">Map</string>
<string name="bad_config">Bad config. Please try again.</string>
<string name="config_interface">Interface</string>
<string name="public_key">Public key</string>
<string name="barcode_downloading">Waiting for the Barcode UI module to be downloaded.</string>
<string name="barcode_downloading_message">Barcode module downloading. Try again.</string>
<string name="addresses">Addresses</string>
<string name="dns_servers">DNS servers</string>
<string name="mtu">MTU</string>
<string name="peer">Peer</string>
<string name="allowed_ips">Allowed IPs</string>
<string name="endpoint">Endpoint</string>
<string name="transfer">Transfer</string>
<string name="last_handshake">Last handshake</string>
<string name="name">Name</string>
<string name="restart">Restart Tunnel</string>
<string name="vpn_connection_failed">Connection failed</string>
<string name="failed_connection_to">Failed connection to -</string>
<string name="initial_connection_failure_message">Attempting to connect to server after 30 seconds of no response.</string>
<string name="lost_connection_failure_message">Attempting to reconnect to server after more than one minute of no response.</string>
<string name="always_on_vpn_support">Allow Always-On VPN </string>
<string name="select_tunnel_message">Please select a tunnel first</string>
<string name="location_services_not_detected">Location Services Not Detected</string>
<string name="check_again">Check again</string>
<string name="detecting_location_services_disabled">Detecting Location Services disabled</string>
<string name="precise_location_message">This feature requires precise location to access Wi-Fi SSID name. Please enable precise location here or in the app settings.</string>
<string name="request">Request</string>
<string name="toggle_vpn">Toggle VPN</string>
<string name="no_tunnel_available">No tunnels available</string>
<string name="hint_search_packages">Search packages</string>
<string name="clear_icon">Clear Icon</string>
<string name="search_icon">Search Icon</string>
@@ -90,13 +64,9 @@
<string name="vpn_starting">VPN starting</string>
<string name="db_name">wg-tunnel-db</string>
<string name="scanning_qr">Scanning for QR</string>
<string name="qr_result_failed">QR scan failed</string>
<string name="none">No trusted wifi names</string>
<string name="never">Never</string>
<string name="stream_failed">Failed to open file stream.</string>
<string name="other">Other</string>
<string name="auto_tunneling">Auto-tunneling</string>
<string name="select_tunnel">Select tunnel to use</string>
<string name="vpn_on">VPN on</string>
<string name="vpn_off">VPN off</string>
<string name="default_vpn_on">Primary VPN on</string>
@@ -124,7 +94,6 @@
<string name="error_authorization_failed">Failed to authorize</string>
<string name="enabled_app_shortcuts">Enable app shortcuts</string>
<string name="export_configs">Export configs</string>
<string name="battery_saver">Battery saver (beta)</string>
<string name="location_services_required">Location services required</string>
<string name="background_location_required">Background location required</string>
<string name="precise_location_required">Precise location required</string>
@@ -135,20 +104,16 @@
<string name="email_subject">WG Tunnel Support</string>
<string name="email_chooser">Send an email…</string>
<string name="go">go</string>
<string name="docs_description">Read the docs (WIP)</string>
<string name="docs_description">Read the docs</string>
<string name="discord_description">Join the community</string>
<string name="email_description">Send me an email</string>
<string name="support_help_text">If you are experiencing issues, have improvement ideas, or just want to engage, the following resources are available:</string>
<string name="kernel">Kernel</string>
<string name="use_kernel">Use kernel module</string>
<string name="error_ssid_exists">SSID already exists</string>
<string name="error_root_denied">Root shell denied</string>
<string name="error_no_file_explorer">No file explorer installed</string>
<string name="error_no_scan">No code scanned</string>
<string name="error_invalid_code">Invalid QR code</string>
<string name="error_none">No error</string>
<string name="error_file_read">Failed to read file</string>
<string name="location_service_missing">Location Services Not Detected</string>
<string name="location_services_missing_message">The app is not detecting any location services enabled on your device. Depending on the device, this could cause the untrusted wifi feature to fail to read the wifi name. Would you like to continue anyways?</string>
<string name="auto_tunnel_title">Auto-tunnel Service</string>
<string name="delete_tunnel">Delete tunnel</string>
@@ -174,7 +139,7 @@
<string name="pin_created">Pin successfully created</string>
<string name="enter_pin">Enter your pin</string>
<string name="create_pin">Create pin</string>
<string name="enable_app_lock">Enabled app lock</string>
<string name="enable_app_lock">Enable app lock</string>
<string name="restart_on_ping">Restart on ping fail (beta)</string>
<string name="mobile_data_tunnel">Set as mobile data tunnel</string>
<string name="set_primary_tunnel">Set as primary tunnel</string>
@@ -182,10 +147,31 @@
<string name="no_wifi_names_configured">No wifi names configured for this tunnel</string>
<string name="general">General</string>
<string name="edit_tunnel">Edit tunnel</string>
<string name="tunnel">Tunnel</string>
<string name="disabled">disabled</string>
<string name="auto_on">Resume auto tun</string>
<string name="auto_off">Pause auto tun</string>
<string name="auto_tun_on">Resume auto-tunnel</string>
<string name="auto_tun_off">Pause auto-tunnel</string>
<string name="version">Version</string>
<string name="mode">Mode</string>
<string name="userspace">Userspace</string>
<string name="settings">Settings</string>
<string name="support">Support</string>
<string name="backend">Backend</string>
<string name="kernel">Kernel</string>
<string name="use_amnezia">"Use Amnezia userspace "</string>
<string name="junk_packet_count">Junk packet count</string>
<string name="junk_packet_minimum_size">Junk packet minimum size</string>
<string name="junk_packet_maximum_size">Junk packet maximum size</string>
<string name="init_packet_junk_size">Init packet junk size</string>
<string name="response_packet_junk_size">Response packet junk size</string>
<string name="init_packet_magic_header">Init packet magic header</string>
<string name="response_packet_magic_header">Response packet magic header</string>
<string name="transport_packet_magic_header">Transport packet magic header</string>
<string name="underload_packet_magic_header">Underload packet magic header</string>
<string name="telegram_url" translatable="false">https://t.me/wgtunnel</string>
<string name="unsure_how">if you are unsure how to proceed</string>
<string name="see_the">See the</string>
<string name="getting_started_url" translatable="false">https://zaneschepke.com/wgtunnel-docs/getting-started.html</string>
<string name="getting_started_guide">Getting started guide</string>
</resources>
+2 -2
View File
@@ -1,7 +1,7 @@
object Constants {
const val VERSION_NAME = "3.4.1"
const val VERSION_NAME = "3.4.3-beta"
const val JVM_TARGET = "17"
const val VERSION_CODE = 34100
const val VERSION_CODE = 34202
const val TARGET_SDK = 34
const val MIN_SDK = 26
const val APP_ID = "com.zaneschepke.wireguardautotunnel"
@@ -0,0 +1,3 @@
Verbesserungen:
- Berechtigungsfehler auf Android Versionen vor 9 behoben
- Andere Optimierungen
@@ -0,0 +1,5 @@
Verbesserungen:
- Tunnelstatistiken zum Hauptbildschirm hinzugefügt
- Navigation der Einstellungen auf AndroidTV verbessert
- Vibration von Benachrichtigungen entfernt
- Verschiedene andere Fehlerbehebungen
@@ -0,0 +1,5 @@
Verbesserungen:
- Unterstützung für Tunnelung nur bei Verwendung von Mobilen Daten
- Verbesserungen der Support Oberfläche
- Aktualisierung der Ressourcenlinks
- Verschiedene andere Fehlerbehebungen
@@ -0,0 +1,5 @@
Verbesserungen:
- Grundlegende Unterstützung für WireGuard Kernelmodus hinzugefügt
- Verbesserung des Positionsanfrage
- Behebung eines Berechtigungsfehlers bei Auto-Tunneln
- Verschiedene andere GUI Fehlerbehebungen
@@ -0,0 +1,2 @@
Fehlerbehebungen:
- Android 14 Vordergrundberechtigungen
@@ -0,0 +1,7 @@
Verbesserungen:
- Refaktorierung der Zustandsverwaltung
- Verbesserung der AndroidTV Navigation
- Verbesserung der Auto-Tunnel Effizienz
- Navigation verbessert
- Auto-Tunnel Pausefunktion
- Viele Fehlerbehebungen
@@ -0,0 +1,7 @@
Verbesserungen:
- Refaktorisierung des Zustandsmanagements
- Verbesserung der AndroidTV Navigation
- Verbesserung der Auto-Tunnel Effizienz
- Verbesserung der Navigation
- Auto-Tunnel Pause Funktion
- Viele Fehlerbehebungen
@@ -0,0 +1,8 @@
Verbesserungen:
- Refaktorisierung des Zustandsmanagements
- Verbesserung der AndroidTV Navigation
- Verbesserung der Auto-Tunnel Effizienz
- Verbesserung der Navigation
- Auto-Tunnel Pause Funktion
- Reparatur des Auto-Tunnel starts im Vordergrund
- Viele Fehlerbehebungen
@@ -0,0 +1,7 @@
Verbesserungen:
- Bestätigung beim Tunnel löschen hinzugefügt
- Hintergrundberechtigung für Energiesparmodus hinzugefügt
Fehlerbehebungen:
- Einfrieren der App bei Tunnel deaktivierung
- Fehler im E-Mail Empfängerfeld
- Konfigurationsbearbeitung mit leerem DNS Feld
@@ -0,0 +1,3 @@
Verbesserungen:
- Erstellte Konfiguration wurde nicht gespeichert
- Version angehoben
@@ -0,0 +1,2 @@
Was ist neu:
- Dies ist eine CI Test version
@@ -0,0 +1,5 @@
Was ist neu:
- Automatischer Start des Always-On VPN im Kernelmodus nach einem Systemneustart
- Unterstützung für adaptive Designicons
- Benachrichtigungs- und Kachelicons korrigiert
- AndroidTV icons korrigiert
@@ -0,0 +1,5 @@
Was ist neu:
- Verbesserung des ersten Starts
- Wechsel zu Wireguard Bibliotheks Fork
- Abfrage der VPN Berechtigung beim ersten VPN Start
- Version angehoben
@@ -0,0 +1,2 @@
Was ist neu:
- GUI Fix for Tunnel Anzeige
@@ -0,0 +1,4 @@
Was ist neu:
- GUI Fix für Konfigurationseditor
- AOVPN Benachrichtung für ersten Start auf GrapheneOS hinzugefügt
- Version angehoben
@@ -0,0 +1,5 @@
Was ist neu:
- Log anzeige hinzugefügt
- Lokale App Sperre hinzugefügt
- Funktion für Neustart des Tunnels nach Fehlerhaftem Ping
- Verschiedene Fehlerbehebungen
@@ -0,0 +1,5 @@
Was ist neu:
- Auto-Tunnel Auswahl nach WLAN-Name
- Auto-Tunnel Kontrolle durch Kacheln und Verknüpfungen
- Automatischer Neustart des Manuellen Tunnels nach einem Systemneustart
- Verschiedene Fehlerbehebungen und Performanceverbesserungen
@@ -0,0 +1,5 @@
Was ist neu:
- Verbesserung der Auto-Tunnel Zuverlässigkeit
- Verbesserung der Kachelsynchronisierung
- AndroidTV Asset hinzugefügt
- APK Fingerabdruck hinzugefügt
@@ -0,0 +1,5 @@
Was ist neu:
- Behebung einer Regression beim Tunnel Stop
- Log Verschleierung hinzugefügt
- Ausblenden des Icons beim Scrollen
- Türkische Übersetzung hinzugefügt
@@ -0,0 +1,13 @@
Funktionen
- Hinzufügen von Tunneln über .conf Dateien, Zip, Manuelle Eingabe oder QR Codes
- Automatische Verbindung zum VPN basierend auf der Wifi-SSID, Ethernet und mobilen Daten
- Geteilter Tunnel für Anwendungen mit Suche
- Unterstützung für Wireguard Userspace- und Kernel-modus
- Always-On VPN Unterstützung
- Export von Tunneln ins Zip Format
- Quicktiles zum aktivieren/deaktivieren der VPN Verbindung
- Feste Shortcuts für den Haupttunnel für automatische Integration
- Intent basierten Automationssupport für alle Tunnel
- Automatischer Servicestart nach Geräteneustart
- Akkuerhaltungsfunktionen
@@ -0,0 +1 @@
Eine alternative VPN Client App für Wireguard mit zusätzlichen Funktionen
@@ -0,0 +1 @@
WG Tunnel

Some files were not shown because too many files have changed in this diff Show More