├── common ├── consumer-rules.pro ├── src │ └── main │ │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── kr328 │ │ │ └── clash │ │ │ └── common │ │ │ ├── util │ │ │ ├── Patterns.kt │ │ │ ├── Global.kt │ │ │ ├── Components.kt │ │ │ ├── Ticker.kt │ │ │ └── Intent.kt │ │ │ ├── constants │ │ │ ├── Metadata.kt │ │ │ ├── Permissions.kt │ │ │ ├── Authorities.kt │ │ │ ├── Components.kt │ │ │ └── Intents.kt │ │ │ ├── compat │ │ │ ├── Services.kt │ │ │ ├── Package.kt │ │ │ ├── Resource.kt │ │ │ ├── Html.kt │ │ │ ├── Intents.kt │ │ │ ├── View.kt │ │ │ ├── Context.kt │ │ │ └── App.kt │ │ │ ├── id │ │ │ └── UndefinedIds.kt │ │ │ ├── Global.kt │ │ │ ├── store │ │ │ └── StoreProvider.kt │ │ │ └── log │ │ │ └── Log.kt │ │ ├── res │ │ ├── values-zh │ │ │ └── strings.xml │ │ ├── values-zh-rTW │ │ │ └── strings.xml │ │ └── values │ │ │ └── strings.xml │ │ └── AndroidManifest.xml ├── build.gradle.kts └── proguard-rules.pro ├── design ├── consumer-rules.pro ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ ├── values │ │ │ ├── ids.xml │ │ │ ├── colors.xml │ │ │ └── styles.xml │ │ ├── drawable │ │ │ ├── ic_baseline_stop.xml │ │ │ ├── ic_baseline_flash_on.xml │ │ │ ├── ic_baseline_add.xml │ │ │ ├── ic_baseline_get_app.xml │ │ │ ├── ic_baseline_publish.xml │ │ │ ├── ic_baseline_clear_all.xml │ │ │ ├── ic_baseline_arrow_back.xml │ │ │ ├── ic_baseline_swap_vert.xml │ │ │ ├── ic_baseline_delete.xml │ │ │ ├── ic_baseline_close.xml │ │ │ ├── ic_baseline_vpn_key_48.xml │ │ │ ├── ic_outline_delete.xml │ │ │ ├── ic_baseline_info.xml │ │ │ ├── ic_baseline_replay.xml │ │ │ ├── ic_baseline_view_list.xml │ │ │ ├── ic_outline_folder.xml │ │ │ ├── ic_baseline_apps.xml │ │ │ ├── ic_outline_info.xml │ │ │ ├── ic_baseline_edit.xml │ │ │ ├── ic_outline_label.xml │ │ │ ├── ic_baseline_content_copy.xml │ │ │ ├── ic_baseline_save.xml │ │ │ ├── ic_baseline_swap_vertical_circle.xml │ │ │ ├── ic_baseline_work.xml │ │ │ ├── ic_baseline_more_vert.xml │ │ │ ├── ic_outline_check_circle.xml │ │ │ ├── ic_baseline_cloud_download.xml │ │ │ ├── ic_baseline_domain.xml │ │ │ ├── ic_outline_inbox.xml │ │ │ ├── ic_outline_update.xml │ │ │ ├── ic_outline_check_circle_48.xml │ │ │ ├── ic_baseline_search.xml │ │ │ ├── ic_baseline_brightness_4.xml │ │ │ ├── ic_baseline_sync.xml │ │ │ ├── ic_outline_article.xml │ │ │ ├── ic_baseline_restore.xml │ │ │ ├── ic_outline_not_interested.xml │ │ │ ├── ic_baseline_assignment.xml │ │ │ ├── ic_outline_block_48.xml │ │ │ ├── ic_baseline_attach_file.xml │ │ │ ├── ic_baseline_update.xml │ │ │ ├── ic_baseline_dns.xml │ │ │ ├── ic_baseline_extension.xml │ │ │ ├── ic_baseline_adb.xml │ │ │ ├── ic_baseline_help_center.xml │ │ │ ├── bg_bottom_sheet.xml │ │ │ ├── ic_baseline_vpn_lock.xml │ │ │ └── ic_baseline_settings.xml │ │ └── layout │ │ │ ├── preference_category.xml │ │ │ ├── common_recycler_list.xml │ │ │ ├── dialog_fetch_status.xml │ │ │ ├── preference_tips.xml │ │ │ ├── dialog_text_field.xml │ │ │ ├── common_activity_bar.xml │ │ │ └── design_new_profile.xml │ │ └── java │ │ └── com │ │ └── github │ │ └── kr328 │ │ └── clash │ │ └── design │ │ ├── model │ │ ├── ProxyState.kt │ │ ├── Behavior.kt │ │ ├── DarkMode.kt │ │ ├── ProxyPageState.kt │ │ ├── File.kt │ │ ├── AppInfo.kt │ │ ├── AppInfoSort.kt │ │ ├── LogFile.kt │ │ └── ProviderState.kt │ │ ├── ui │ │ ├── DayNight.kt │ │ ├── ToastDuration.kt │ │ ├── Insets.kt │ │ ├── Surface.kt │ │ └── ObservableCurrentTime.kt │ │ ├── util │ │ ├── ScrollView.kt │ │ ├── Binding.kt │ │ ├── View.kt │ │ ├── App.kt │ │ ├── Validator.kt │ │ ├── ActivityBar.kt │ │ ├── Interval.kt │ │ ├── Landscape.kt │ │ ├── Toast.kt │ │ ├── Diff.kt │ │ ├── Context.kt │ │ ├── ListView.kt │ │ └── Inserts.kt │ │ ├── preference │ │ ├── Preference.kt │ │ ├── Category.kt │ │ ├── Screen.kt │ │ ├── Tips.kt │ │ └── Value.kt │ │ ├── view │ │ ├── AppRecyclerView.kt │ │ ├── ActivityBarLayout.kt │ │ ├── ObservableScrollView.kt │ │ └── VerticalScrollableHost.kt │ │ ├── AppCrashedDesign.kt │ │ ├── SettingsDesign.kt │ │ └── adapter │ │ ├── LogMessageAdapter.kt │ │ └── LogFileAdapter.kt ├── build.gradle.kts └── proguard-rules.pro ├── hideapi ├── consumer-rules.pro ├── build.gradle.kts ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── android │ │ └── app │ │ └── ActivityThread.java └── proguard-rules.pro ├── service ├── consumer-rules.pro ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── colors.xml │ │ │ ├── ids.xml │ │ │ └── strings.xml │ │ ├── values-zh │ │ │ └── strings.xml │ │ ├── values-zh-rHK │ │ │ └── strings.xml │ │ ├── values-zh-rTW │ │ │ └── strings.xml │ │ └── drawable │ │ │ └── ic_logo_service.xml │ │ └── java │ │ └── com │ │ └── github │ │ └── kr328 │ │ └── clash │ │ └── service │ │ ├── document │ │ ├── Flag.kt │ │ ├── Document.kt │ │ ├── VirtualDocument.kt │ │ ├── FileDocument.kt │ │ └── Path.kt │ │ ├── model │ │ ├── AccessControlMode.kt │ │ └── Profile.kt │ │ ├── data │ │ ├── migrations │ │ │ └── Migrations.kt │ │ ├── Daos.kt │ │ ├── Converters.kt │ │ ├── Imported.kt │ │ ├── Pending.kt │ │ ├── SelectionDao.kt │ │ ├── Selection.kt │ │ ├── PendingDao.kt │ │ └── ImportedDao.kt │ │ ├── util │ │ ├── Intent.kt │ │ ├── Connectivity.kt │ │ ├── Coroutine.kt │ │ ├── Net.kt │ │ ├── Database.kt │ │ ├── Files.kt │ │ ├── Serializers.kt │ │ └── Address.kt │ │ ├── remote │ │ ├── IRemoteService.kt │ │ ├── ILogObserver.kt │ │ ├── IFetchObserver.kt │ │ ├── IProfileManager.kt │ │ └── IClashManager.kt │ │ ├── BaseService.kt │ │ ├── clash │ │ └── module │ │ │ ├── CloseModule.kt │ │ │ └── TimeZoneModule.kt │ │ ├── sideload │ │ └── ExternalGeoip.kt │ │ └── PreferenceProvider.kt ├── proguard-rules.pro └── build.gradle.kts ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── 04-feature-request-zh-cn.yml │ └── 02-feature-request-en.yml └── workflows │ └── build-unsigned.yaml ├── .gitattributes ├── core ├── src │ ├── premium │ │ └── golang │ │ │ └── main.go │ ├── foss │ │ └── golang │ │ │ └── main.go │ └── main │ │ ├── cpp │ │ ├── bridge_helper.h │ │ ├── bridge_helper.c │ │ ├── CMakeLists.txt │ │ └── jni_helper.h │ │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── kr328 │ │ │ └── clash │ │ │ └── core │ │ │ ├── model │ │ │ ├── Traffic.kt │ │ │ ├── ProxySort.kt │ │ │ ├── UiConfiguration.kt │ │ │ ├── ProviderList.kt │ │ │ ├── FetchStatus.kt │ │ │ ├── TunnelState.kt │ │ │ └── Provider.kt │ │ │ ├── bridge │ │ │ ├── ClashException.kt │ │ │ ├── LogcatInterface.kt │ │ │ ├── FetchCallback.kt │ │ │ ├── TunInterface.kt │ │ │ └── Content.kt │ │ │ └── util │ │ │ ├── Net.kt │ │ │ └── Serializers.kt │ │ ├── golang │ │ ├── native │ │ │ ├── tunnel │ │ │ │ ├── loopback_open.go │ │ │ │ ├── loopback_premium.go │ │ │ │ ├── suspend.go │ │ │ │ ├── state.go │ │ │ │ ├── statistic.go │ │ │ │ ├── geoip.go │ │ │ │ ├── conn.go │ │ │ │ ├── init.go │ │ │ │ ├── connectivity.go │ │ │ │ └── providers_open.go │ │ │ ├── trace.c │ │ │ ├── config │ │ │ │ ├── process_open.go │ │ │ │ ├── process_premium.go │ │ │ │ ├── provider_open.go │ │ │ │ ├── provider_premium.go │ │ │ │ └── defaults.go │ │ │ ├── debug.go │ │ │ ├── all │ │ │ │ └── imports.go │ │ │ ├── app │ │ │ │ ├── dns.go │ │ │ │ ├── content.go │ │ │ │ ├── ui.go │ │ │ │ ├── app.go │ │ │ │ └── tun.go │ │ │ ├── proxy.go │ │ │ ├── trace.h │ │ │ ├── common │ │ │ │ └── path.go │ │ │ ├── tun │ │ │ │ ├── udp.go │ │ │ │ ├── metadata_open.go │ │ │ │ ├── dns.go │ │ │ │ └── metadata_premium.go │ │ │ ├── utils.go │ │ │ ├── proxy │ │ │ │ └── http.go │ │ │ ├── platform │ │ │ │ └── limit.go │ │ │ ├── main.go │ │ │ └── app.go │ │ ├── .idea │ │ │ └── codeStyles │ │ │ │ ├── codeStyleConfig.xml │ │ │ │ └── Project.xml │ │ └── go.mod │ │ └── AndroidManifest.xml ├── consumer-rules.pro └── proguard-rules.pro ├── app ├── src │ ├── main │ │ ├── ic_launcher-web.png │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_banner.png │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── values │ │ │ │ ├── ids.xml │ │ │ │ ├── colors.xml │ │ │ │ └── themes.xml │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── values-night │ │ │ │ └── themes.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── xml │ │ │ │ ├── network_security_config.xml │ │ │ │ └── full_backup_content.xml │ │ │ └── drawable │ │ │ │ └── ic_launcher_foreground.xml │ │ └── java │ │ │ └── com │ │ │ └── github │ │ │ └── kr328 │ │ │ └── clash │ │ │ ├── util │ │ │ ├── Uri.kt │ │ │ ├── Files.kt │ │ │ ├── Service.kt │ │ │ ├── Content.kt │ │ │ ├── Clash.kt │ │ │ └── Activity.kt │ │ │ ├── HelpActivity.kt │ │ │ ├── ApkBrokenActivity.kt │ │ │ ├── store │ │ │ ├── AppStore.kt │ │ │ └── TipsStore.kt │ │ │ ├── RestartReceiver.kt │ │ │ ├── ScSwitchProxyActivity.kt │ │ │ ├── log │ │ │ ├── LogcatFilter.kt │ │ │ ├── LogcatWriter.kt │ │ │ ├── SystemLogcat.kt │ │ │ └── LogcatReader.kt │ │ │ ├── MainApplication.kt │ │ │ ├── remote │ │ │ └── StatusClient.kt │ │ │ ├── AppCrashedActivity.kt │ │ │ ├── SettingsActivity.kt │ │ │ └── NetworkSettingsActivity.kt │ ├── foss │ │ └── res │ │ │ └── xml │ │ │ └── shortcuts.xml │ └── premium │ │ └── res │ │ └── xml │ │ └── shortcuts.xml └── build.gradle.kts ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea └── codeStyles │ └── codeStyleConfig.xml ├── .gitmodules ├── CONTRIBUTING.md ├── .gitignore └── gradle.properties /common/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /design/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hideapi/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /service/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /hideapi/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.library") 3 | } 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.bat text eol=crlf 4 | *.jar binary 5 | -------------------------------------------------------------------------------- /core/src/premium/golang/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import _ "cfa/native/all" 4 | -------------------------------------------------------------------------------- /design/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/src/foss/golang/main.go: -------------------------------------------------------------------------------- 1 | package golang 2 | 3 | import ( 4 | _ "cfa/native/all" 5 | ) 6 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyMax0/ClashForAndroid-1/HEAD/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /core/src/main/cpp/bridge_helper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | uint64_t down_scale_traffic(uint64_t value); -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyMax0/ClashForAndroid-1/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /hideapi/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/model/Traffic.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.model 2 | 3 | typealias Traffic = Long -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyMax0/ClashForAndroid-1/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyMax0/ClashForAndroid-1/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyMax0/ClashForAndroid-1/HEAD/app/src/main/res/mipmap-xhdpi/ic_banner.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyMax0/ClashForAndroid-1/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /design/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyMax0/ClashForAndroid-1/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyMax0/ClashForAndroid-1/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyMax0/ClashForAndroid-1/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyMax0/ClashForAndroid-1/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/model/ProxyState.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.model 2 | 3 | data class ProxyState(var now: String) -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/ui/DayNight.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.ui 2 | 3 | enum class DayNight { 4 | Day, Night 5 | } -------------------------------------------------------------------------------- /service/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #1E4376 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyMax0/ClashForAndroid-1/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyMax0/ClashForAndroid-1/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/util/Patterns.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.util 2 | 3 | val PatternFileName = Regex("[^*&%\\n\\r/]+") -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HappyMax0/ClashForAndroid-1/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 18 | 19 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/model/UiConfiguration.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.model 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | import com.github.kr328.clash.core.util.Parcelizer 6 | import kotlinx.serialization.Serializable 7 | 8 | @Serializable 9 | class UiConfiguration : Parcelable { 10 | override fun writeToParcel(parcel: Parcel, flags: Int) { 11 | Parcelizer.encodeToParcel(serializer(), parcel, this) 12 | } 13 | 14 | override fun describeContents(): Int { 15 | return 0 16 | } 17 | 18 | companion object CREATOR : Parcelable.Creator { 19 | override fun createFromParcel(parcel: Parcel): UiConfiguration { 20 | return Parcelizer.decodeFromParcel(serializer(), parcel) 21 | } 22 | 23 | override fun newArray(size: Int): Array { 24 | return arrayOfNulls(size) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/src/main/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(clash-bridge C) 4 | 5 | set(CMAKE_POSITION_INDEPENDENT_CODE on) 6 | set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -O3") 7 | 8 | set(GO_OUTPUT_BASE ${GO_OUTPUT}/${FLAVOR_NAME}) 9 | 10 | if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") 11 | set(GO_OUTPUT_BASE "${GO_OUTPUT_BASE}Debug") 12 | elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "Release") 13 | set(GO_OUTPUT_BASE "${GO_OUTPUT_BASE}Release") 14 | elseif ("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo") 15 | set(GO_OUTPUT_BASE "${GO_OUTPUT_BASE}Release") 16 | else () 17 | message(FATAL_ERROR "Unknown build type ${CMAKE_BUILD_TYPE}") 18 | endif () 19 | 20 | include_directories("${GO_OUTPUT_BASE}/${CMAKE_ANDROID_ARCH_ABI}") 21 | include_directories("${GO_SOURCE}") 22 | 23 | link_directories("${GO_OUTPUT_BASE}/${CMAKE_ANDROID_ARCH_ABI}") 24 | 25 | add_library(bridge SHARED main.c jni_helper.c bridge_helper.c) 26 | target_link_libraries(bridge log clash) -------------------------------------------------------------------------------- /design/src/main/res/layout/dialog_fetch_status.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 15 | 16 | 21 | 22 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/view/ActivityBarLayout.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.MotionEvent 6 | import android.widget.FrameLayout 7 | import androidx.annotation.AttrRes 8 | import androidx.annotation.StyleRes 9 | import com.github.kr328.clash.design.util.resolveThemedColor 10 | 11 | class ActivityBarLayout @JvmOverloads constructor( 12 | context: Context, 13 | attributeSet: AttributeSet? = null, 14 | @AttrRes defStyleAttr: Int = 0, 15 | @StyleRes defStyleRes: Int = 0 16 | ) : FrameLayout(context, attributeSet, defStyleAttr, defStyleRes) { 17 | init { 18 | alpha = 0.96f 19 | 20 | setBackgroundColor(context.resolveThemedColor(android.R.attr.windowBackground)) 21 | } 22 | 23 | override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { 24 | super.dispatchTouchEvent(ev) 25 | 26 | return true 27 | } 28 | } -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_vpn_lock.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/foss/res/xml/shortcuts.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /service/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("android") 3 | id("kotlinx-serialization") 4 | id("com.android.library") 5 | id("com.google.devtools.ksp") 6 | } 7 | 8 | dependencies { 9 | implementation(project(":core")) 10 | implementation(project(":common")) 11 | 12 | ksp(libs.kaidl.compiler) 13 | ksp(libs.androidx.room.compiler) 14 | 15 | implementation(libs.kotlin.coroutine) 16 | implementation(libs.kotlin.serialization.json) 17 | implementation(libs.androidx.core) 18 | implementation(libs.androidx.room.runtime) 19 | implementation(libs.androidx.room.ktx) 20 | implementation(libs.kaidl.runtime) 21 | implementation(libs.rikkax.multiprocess) 22 | } 23 | 24 | afterEvaluate { 25 | android { 26 | libraryVariants.forEach { 27 | sourceSets[it.name].kotlin.srcDir(buildDir.resolve("generated/ksp/${it.name}/kotlin")) 28 | sourceSets[it.name].java.srcDir(buildDir.resolve("generated/ksp/${it.name}/java")) 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/premium/res/xml/shortcuts.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/compat/App.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.compat 2 | 3 | import android.app.ActivityThread 4 | import android.app.Application 5 | import android.graphics.drawable.AdaptiveIconDrawable 6 | import android.graphics.drawable.Drawable 7 | import android.os.Build 8 | import com.github.kr328.clash.common.log.Log 9 | 10 | val Application.currentProcessName: String 11 | get() { 12 | if (Build.VERSION.SDK_INT >= 28) 13 | return Application.getProcessName() 14 | 15 | return try { 16 | ActivityThread.currentProcessName() 17 | } catch (throwable: Throwable) { 18 | Log.w("Resolve process name: $throwable") 19 | 20 | packageName 21 | } 22 | } 23 | 24 | fun Drawable.foreground(): Drawable { 25 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && 26 | this is AdaptiveIconDrawable && this.background == null 27 | ) { 28 | return this.foreground 29 | } 30 | return this 31 | } 32 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/util/Diff.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.util 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | 5 | fun List.diffWith( 6 | newList: List, 7 | detectMove: Boolean = false, 8 | id: (T) -> Any? = { it } 9 | ): DiffUtil.DiffResult { 10 | val oldList = this 11 | 12 | return DiffUtil.calculateDiff(object : DiffUtil.Callback() { 13 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 14 | return id(oldList[oldItemPosition]) == id(newList[newItemPosition]) 15 | } 16 | 17 | override fun getOldListSize(): Int { 18 | return oldList.size 19 | } 20 | 21 | override fun getNewListSize(): Int { 22 | return newList.size 23 | } 24 | 25 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 26 | return oldList[oldItemPosition] == newList[newItemPosition] 27 | } 28 | }, detectMove) 29 | } 30 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/util/Context.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.util 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.text.Spanned 6 | import android.view.LayoutInflater 7 | import android.view.ViewGroup 8 | import androidx.annotation.DimenRes 9 | import androidx.annotation.StringRes 10 | import com.github.kr328.clash.common.compat.fromHtmlCompat 11 | 12 | val Context.layoutInflater: LayoutInflater 13 | get() = LayoutInflater.from(this) 14 | 15 | val Context.root: ViewGroup? 16 | get() { 17 | return when (this) { 18 | is Activity -> { 19 | findViewById(android.R.id.content) 20 | } 21 | else -> { 22 | null 23 | } 24 | } 25 | } 26 | 27 | fun Context.getPixels(@DimenRes resId: Int): Int { 28 | return resources.getDimensionPixelSize(resId) 29 | } 30 | 31 | fun Context.getHtml(@StringRes resId: Int): Spanned { 32 | return fromHtmlCompat(getString(resId)) 33 | } 34 | -------------------------------------------------------------------------------- /service/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Clash 状态 4 | 正在运行 5 | 更新 %s 成功 6 | "更新 %1$s: %2$s " 7 | Clash for Android 8 | 配置文件和外部资源 9 | 配置文件.yaml 10 | 外部资源文件列表 11 | 载入中 12 | 配置文件处理状态 13 | 更新成功 14 | 更新失败 15 | 配置更新服务 16 | 配置更新中 17 | 配置文件服务状态 18 | 配置文件处理结果 19 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/model/ProviderList.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.model 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | import com.github.kr328.clash.common.util.createListFromParcelSlice 6 | import com.github.kr328.clash.common.util.writeToParcelSlice 7 | 8 | class ProviderList(data: List) : List by data, Parcelable { 9 | constructor(parcel: Parcel) : this(Provider.createListFromParcelSlice(parcel, 0, 20)) 10 | 11 | override fun describeContents(): Int { 12 | return 0 13 | } 14 | 15 | override fun writeToParcel(parcel: Parcel, flags: Int) { 16 | return writeToParcelSlice(parcel, flags) 17 | } 18 | 19 | companion object CREATOR : Parcelable.Creator { 20 | override fun createFromParcel(parcel: Parcel): ProviderList { 21 | return ProviderList(parcel) 22 | } 23 | 24 | override fun newArray(size: Int): Array { 25 | return arrayOfNulls(size) 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/AppCrashedDesign.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import com.github.kr328.clash.design.databinding.DesignAppCrashedBinding 6 | import com.github.kr328.clash.design.util.applyFrom 7 | import com.github.kr328.clash.design.util.bindAppBarElevation 8 | import com.github.kr328.clash.design.util.layoutInflater 9 | import com.github.kr328.clash.design.util.root 10 | 11 | class AppCrashedDesign(context: Context) : Design(context) { 12 | private val binding = DesignAppCrashedBinding 13 | .inflate(context.layoutInflater, context.root, false) 14 | 15 | override val root: View 16 | get() = binding.root 17 | 18 | fun setAppLogs(logs: String) { 19 | binding.logsView.text = logs 20 | } 21 | 22 | init { 23 | binding.self = this 24 | 25 | binding.activityBarLayout.applyFrom(context) 26 | 27 | binding.scrollRoot.bindAppBarElevation(binding.activityBarLayout) 28 | } 29 | } -------------------------------------------------------------------------------- /service/src/main/res/values-zh-rHK/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Clash 狀態 4 | 正在運行 5 | 更新 %s 成功 6 | "更新 %1$s: %2$s " 7 | Clash for Android 8 | 配置文件和外部資源 9 | 配置文件.yaml 10 | 外部資源文件列表 11 | 載入中 12 | 配置文件處理狀態 13 | 更新成功 14 | 更新失敗 15 | 配置更新服務 16 | 配置更新中 17 | 配置文件服務狀態 18 | 配置文件處理結果 19 | -------------------------------------------------------------------------------- /service/src/main/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Clash 狀態 4 | 正在運作 5 | 更新 %s 成功 6 | "更新 %1$s: %2$s " 7 | Clash for Android 8 | 設定檔和外部資源 9 | 設定檔.yaml 10 | 外部資源文件列表 11 | 載入中 12 | 設定檔處理狀態 13 | 更新成功 14 | 更新失敗 15 | 設定檔更新服務 16 | 設定檔更新中 17 | 設定檔服務狀態 18 | 設定檔處理結果 19 | 20 | -------------------------------------------------------------------------------- /design/src/main/res/layout/preference_tips.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 15 | 16 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import com.github.kr328.clash.common.Global 6 | import com.github.kr328.clash.common.compat.currentProcessName 7 | import com.github.kr328.clash.common.log.Log 8 | import com.github.kr328.clash.remote.Remote 9 | import com.github.kr328.clash.service.util.sendServiceRecreated 10 | 11 | @Suppress("unused") 12 | class MainApplication : Application() { 13 | override fun attachBaseContext(base: Context?) { 14 | super.attachBaseContext(base) 15 | 16 | Global.init(this) 17 | } 18 | 19 | override fun onCreate() { 20 | super.onCreate() 21 | 22 | val processName = currentProcessName 23 | 24 | Log.d("Process $processName started") 25 | 26 | if (processName == packageName) { 27 | Remote.launch() 28 | } else { 29 | sendServiceRecreated() 30 | } 31 | } 32 | 33 | fun finalize() { 34 | Global.destroy() 35 | } 36 | } -------------------------------------------------------------------------------- /service/src/main/res/drawable/ic_logo_service.xml: -------------------------------------------------------------------------------- 1 | 8 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/remote/StatusClient.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.remote 2 | 3 | import android.content.Context 4 | import android.net.Uri 5 | import com.github.kr328.clash.common.constants.Authorities 6 | import com.github.kr328.clash.common.log.Log 7 | import com.github.kr328.clash.service.StatusProvider 8 | 9 | class StatusClient(private val context: Context) { 10 | private val uri: Uri 11 | get() { 12 | return Uri.Builder() 13 | .scheme("content") 14 | .authority(Authorities.STATUS_PROVIDER) 15 | .build() 16 | } 17 | 18 | fun currentProfile(): String? { 19 | return try { 20 | val result = context.contentResolver.call( 21 | uri, 22 | StatusProvider.METHOD_CURRENT_PROFILE, 23 | null, 24 | null 25 | ) 26 | 27 | result?.getString("name") 28 | } catch (e: Exception) { 29 | Log.w("Query current profile: $e", e) 30 | 31 | null 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/remote/IClashManager.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.remote 2 | 3 | import com.github.kr328.clash.core.Clash 4 | import com.github.kr328.clash.core.model.* 5 | import com.github.kr328.kaidl.BinderInterface 6 | 7 | @BinderInterface 8 | interface IClashManager { 9 | fun queryTunnelState(): TunnelState 10 | fun queryTrafficTotal(): Long 11 | fun queryProxyGroupNames(excludeNotSelectable: Boolean): List 12 | fun queryProxyGroup(name: String, proxySort: ProxySort): ProxyGroup 13 | fun queryConfiguration(): UiConfiguration 14 | fun queryProviders(): ProviderList 15 | 16 | fun patchSelector(group: String, name: String): Boolean 17 | 18 | suspend fun healthCheck(group: String) 19 | suspend fun updateProvider(type: Provider.Type, name: String) 20 | 21 | fun queryOverride(slot: Clash.OverrideSlot): ConfigurationOverride 22 | fun patchOverride(slot: Clash.OverrideSlot, configuration: ConfigurationOverride) 23 | fun clearOverride(slot: Clash.OverrideSlot) 24 | 25 | fun setLogObserver(observer: ILogObserver?) 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/log/LogcatReader.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.log 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.core.model.LogMessage 5 | import com.github.kr328.clash.design.model.LogFile 6 | import com.github.kr328.clash.util.logsDir 7 | import java.io.BufferedReader 8 | import java.io.FileReader 9 | import java.util.* 10 | 11 | class LogcatReader(context: Context, file: LogFile) : AutoCloseable { 12 | private val reader = BufferedReader(FileReader(context.logsDir.resolve(file.fileName))) 13 | 14 | override fun close() { 15 | reader.close() 16 | } 17 | 18 | fun readAll(): List { 19 | return reader.lineSequence() 20 | .map { it.trim() } 21 | .filter { !it.startsWith("#") } 22 | .map { it.split(":", limit = 3) } 23 | .map { 24 | LogMessage( 25 | time = Date(it[0].toLong()), 26 | level = LogMessage.Level.valueOf(it[1]), 27 | message = it[2] 28 | ) 29 | } 30 | .toList() 31 | } 32 | } -------------------------------------------------------------------------------- /core/src/main/golang/native/app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "strconv" 5 | "strings" 6 | "time" 7 | ) 8 | 9 | var appVersionName string 10 | var platformVersion int 11 | var installedAppsUid = map[int]string{} 12 | 13 | func ApplyVersionName(versionName string) { 14 | appVersionName = versionName 15 | } 16 | 17 | func ApplyPlatformVersion(version int) { 18 | platformVersion = version 19 | } 20 | 21 | func VersionName() string { 22 | return appVersionName 23 | } 24 | 25 | func PlatformVersion() int { 26 | return platformVersion 27 | } 28 | 29 | func NotifyInstallAppsChanged(uidList string) { 30 | uids := map[int]string{} 31 | 32 | for _, item := range strings.Split(uidList, ",") { 33 | kv := strings.Split(item, ":") 34 | if len(kv) == 2 { 35 | uid, err := strconv.Atoi(kv[0]) 36 | if err != nil { 37 | continue 38 | } 39 | 40 | uids[uid] = kv[1] 41 | } 42 | } 43 | 44 | installedAppsUid = uids 45 | } 46 | 47 | func QueryAppByUid(uid int) string { 48 | return installedAppsUid[uid] 49 | } 50 | 51 | func NotifyTimeZoneChanged(name string, offset int) { 52 | time.Local = time.FixedZone(name, offset) 53 | } -------------------------------------------------------------------------------- /core/src/main/golang/go.mod: -------------------------------------------------------------------------------- 1 | module cfa 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/Dreamacro/clash v1.7.1 7 | github.com/Kr328/tun2socket v0.0.0-20220414050025-d07c78d06d34 8 | github.com/dlclark/regexp2 v1.4.0 9 | github.com/miekg/dns v1.1.43 10 | github.com/oschwald/geoip2-golang v1.5.0 11 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c 12 | gopkg.in/yaml.v2 v2.4.0 13 | ) 14 | 15 | require ( 16 | github.com/Dreamacro/go-shadowsocks2 v0.1.7 // indirect 17 | github.com/gofrs/uuid v4.0.0+incompatible // indirect 18 | github.com/gorilla/websocket v1.4.2 // indirect 19 | github.com/insomniacslk/dhcp v0.0.0-20210827173440-b95caade3eac // indirect 20 | github.com/oschwald/maxminddb-golang v1.8.0 // indirect 21 | github.com/sirupsen/logrus v1.8.1 // indirect 22 | github.com/u-root/uio v0.0.0-20210528114334-82958018845c // indirect 23 | go.uber.org/atomic v1.9.0 // indirect 24 | golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect 25 | golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f // indirect 26 | golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8 // indirect 27 | golang.org/x/text v0.3.6 // indirect 28 | ) 29 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tunnel/init.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "context" 5 | "net" 6 | "strings" 7 | 8 | "github.com/Dreamacro/clash/component/dialer" 9 | C "github.com/Dreamacro/clash/constant" 10 | CTX "github.com/Dreamacro/clash/context" 11 | "github.com/Dreamacro/clash/tunnel" 12 | ) 13 | 14 | func init() { 15 | dialer.DefaultTunnelDialer = func(context context.Context, network, address string) (net.Conn, error) { 16 | if !strings.HasPrefix(network, "tcp") { 17 | return nil, net.UnknownNetworkError("unsupported network") 18 | } 19 | 20 | host, port, err := net.SplitHostPort(address) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | left, right := net.Pipe() 26 | 27 | metadata := &C.Metadata{ 28 | NetWork: C.TCP, 29 | Type: C.HTTPCONNECT, 30 | SrcIP: loopback, 31 | SrcPort: "65535", 32 | DstPort: port, 33 | AddrType: C.AtypDomainName, 34 | Host: host, 35 | RawSrcAddr: left.RemoteAddr(), 36 | RawDstAddr: left.LocalAddr(), 37 | } 38 | 39 | tunnel.TCPIn() <- CTX.NewConnContext(right, metadata) 40 | 41 | return left, nil 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/AppCrashedActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import com.github.kr328.clash.common.compat.versionCodeCompat 4 | import com.github.kr328.clash.common.log.Log 5 | import com.github.kr328.clash.design.AppCrashedDesign 6 | import com.github.kr328.clash.log.SystemLogcat 7 | import kotlinx.coroutines.Dispatchers 8 | import kotlinx.coroutines.isActive 9 | import kotlinx.coroutines.withContext 10 | 11 | class AppCrashedActivity : BaseActivity() { 12 | override suspend fun main() { 13 | val design = AppCrashedDesign(this) 14 | 15 | setContentDesign(design) 16 | 17 | val packageInfo = withContext(Dispatchers.IO) { 18 | packageManager.getPackageInfo(packageName, 0) 19 | } 20 | 21 | Log.i("App version: versionName = ${packageInfo.versionName} versionCode = ${packageInfo.versionCodeCompat}") 22 | 23 | val logs = withContext(Dispatchers.IO) { 24 | SystemLogcat.dumpCrash() 25 | } 26 | 27 | design.setAppLogs(logs) 28 | 29 | while (isActive) { 30 | events.receive() 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/SettingsDesign.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import com.github.kr328.clash.design.databinding.DesignSettingsBinding 6 | import com.github.kr328.clash.design.util.applyFrom 7 | import com.github.kr328.clash.design.util.bindAppBarElevation 8 | import com.github.kr328.clash.design.util.layoutInflater 9 | import com.github.kr328.clash.design.util.root 10 | 11 | class SettingsDesign(context: Context) : Design(context) { 12 | enum class Request { 13 | StartApp, StartNetwork, StartOverride, 14 | } 15 | 16 | private val binding = DesignSettingsBinding 17 | .inflate(context.layoutInflater, context.root, false) 18 | 19 | override val root: View 20 | get() = binding.root 21 | 22 | init { 23 | binding.self = this 24 | 25 | binding.activityBarLayout.applyFrom(context) 26 | 27 | binding.scrollRoot.bindAppBarElevation(binding.activityBarLayout) 28 | } 29 | 30 | fun request(request: Request) { 31 | requests.trySend(request) 32 | } 33 | } -------------------------------------------------------------------------------- /core/src/main/golang/native/tunnel/connectivity.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/Dreamacro/clash/adapter" 7 | "github.com/Dreamacro/clash/adapter/outboundgroup" 8 | "github.com/Dreamacro/clash/constant/provider" 9 | "github.com/Dreamacro/clash/log" 10 | "github.com/Dreamacro/clash/tunnel" 11 | ) 12 | 13 | func HealthCheck(name string) { 14 | p := tunnel.Proxies()[name] 15 | 16 | if p == nil { 17 | log.Warnln("Request health check for `%s`: not found", name) 18 | 19 | return 20 | } 21 | 22 | g, ok := p.(*adapter.Proxy).ProxyAdapter.(outboundgroup.ProxyGroup) 23 | if !ok { 24 | log.Warnln("Request health check for `%s`: invalid type %s", name, p.Type().String()) 25 | 26 | return 27 | } 28 | 29 | wg := &sync.WaitGroup{} 30 | 31 | for _, pr := range g.Providers() { 32 | wg.Add(1) 33 | 34 | go func(provider provider.ProxyProvider) { 35 | provider.HealthCheck() 36 | 37 | wg.Done() 38 | }(pr) 39 | } 40 | 41 | wg.Wait() 42 | } 43 | 44 | func HealthCheckAll() { 45 | for _, g := range QueryProxyGroupNames(false) { 46 | go func(group string) { 47 | HealthCheck(group) 48 | }(g) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Address.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import java.net.Inet4Address 4 | import java.net.Inet6Address 5 | import java.net.InetAddress 6 | 7 | fun InetAddress.asSocketAddressText(port: Int): String { 8 | return when (this) { 9 | is Inet6Address -> 10 | "[${numericToTextFormat(this.address)}]:$port" 11 | is Inet4Address -> 12 | "${this.hostAddress}:$port" 13 | else -> throw IllegalArgumentException("Unsupported Inet type ${this.javaClass}") 14 | } 15 | } 16 | 17 | private const val INT16SZ = 2 18 | private const val INADDRSZ = 16 19 | private fun numericToTextFormat(src: ByteArray): String { 20 | val sb = StringBuilder(39) 21 | for (i in 0 until INADDRSZ / INT16SZ) { 22 | sb.append( 23 | Integer.toHexString( 24 | src[i shl 1].toInt() shl 8 and 0xff00 25 | or (src[(i shl 1) + 1].toInt() and 0xff) 26 | ) 27 | ) 28 | if (i < INADDRSZ / INT16SZ - 1) { 29 | sb.append(":") 30 | } 31 | } 32 | return sb.toString() 33 | } 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/util/Clash.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.util 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.VpnService 6 | import com.github.kr328.clash.common.compat.startForegroundServiceCompat 7 | import com.github.kr328.clash.common.constants.Intents 8 | import com.github.kr328.clash.common.util.intent 9 | import com.github.kr328.clash.design.store.UiStore 10 | import com.github.kr328.clash.service.ClashService 11 | import com.github.kr328.clash.service.TunService 12 | import com.github.kr328.clash.service.util.sendBroadcastSelf 13 | 14 | fun Context.startClashService(): Intent? { 15 | val startTun = UiStore(this).enableVpn 16 | 17 | if (startTun) { 18 | val vpnRequest = VpnService.prepare(this) 19 | if (vpnRequest != null) 20 | return vpnRequest 21 | 22 | startForegroundServiceCompat(TunService::class.intent) 23 | } else { 24 | startForegroundServiceCompat(ClashService::class.intent) 25 | } 26 | 27 | return null 28 | } 29 | 30 | fun Context.stopClashService() { 31 | sendBroadcastSelf(Intent(Intents.ACTION_CLASH_REQUEST_STOP)) 32 | } -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/model/FetchStatus.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.model 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | import com.github.kr328.clash.core.util.Parcelizer 6 | import kotlinx.serialization.Serializable 7 | 8 | @Serializable 9 | data class FetchStatus( 10 | val action: Action, 11 | val args: List, 12 | val progress: Int, 13 | val max: Int 14 | ) : Parcelable { 15 | enum class Action { 16 | FetchConfiguration, 17 | FetchProviders, 18 | Verifying, 19 | } 20 | 21 | override fun describeContents(): Int { 22 | return 0 23 | } 24 | 25 | override fun writeToParcel(dest: Parcel, flags: Int) { 26 | Parcelizer.encodeToParcel(serializer(), dest, this) 27 | } 28 | 29 | companion object CREATOR : Parcelable.Creator { 30 | override fun createFromParcel(parcel: Parcel): FetchStatus { 31 | return Parcelizer.decodeFromParcel(serializer(), parcel) 32 | } 33 | 34 | override fun newArray(size: Int): Array { 35 | return arrayOfNulls(size) 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | /app/foss/release 4 | /app/premium/release 5 | /captures 6 | 7 | # Ignore Gradle GUI config 8 | gradle-app.setting 9 | 10 | # Avoid ignoring Gradle wrapper jar targetFile (.jar files are usually ignored) 11 | !gradle-wrapper.jar 12 | 13 | # Cache of project 14 | .gradletasknamecache 15 | 16 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 17 | # gradle/wrapper/gradle-wrapper.properties 18 | 19 | # Ignore IDEA config 20 | *.iml 21 | /.idea/* 22 | !/.idea/codeStyles 23 | /core/src/main/golang/.idea/* 24 | !/core/src/main/golang/.idea/codeStyles 25 | /core/src/foss/golang/.idea/* 26 | !/core/src/foss/golang/.idea/codeStyles 27 | /core/src/premium/golang/.idea/* 28 | !/core/src/premium/golang/.idea/codeStyles 29 | 30 | # KeyStore 31 | signing.properties 32 | *.keystore 33 | *.jks 34 | 35 | # clion cmake build 36 | cmake-build-* 37 | 38 | # local.properties 39 | local.properties 40 | 41 | 42 | # tracker 43 | tracker.properties 44 | 45 | # vscode 46 | .vscode 47 | 48 | # cxx 49 | .cxx 50 | 51 | *.hprof 52 | 53 | # firebase 54 | google-services.json 55 | 56 | # Dolphin 57 | .directory 58 | 59 | # logs 60 | *.log 61 | 62 | # MacOS 63 | .DS_Store 64 | -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/constants/Intents.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.constants 2 | 3 | import com.github.kr328.clash.common.util.packageName 4 | 5 | object Intents { 6 | // Public 7 | val ACTION_PROVIDE_URL = "$packageName.action.PROVIDE_URL" 8 | 9 | const val EXTRA_NAME = "name" 10 | 11 | // Self 12 | val ACTION_SERVICE_RECREATED = "$packageName.intent.action.CLASH_RECREATED" 13 | val ACTION_CLASH_STARTED = "$packageName.intent.action.CLASH_STARTED" 14 | val ACTION_CLASH_STOPPED = "$packageName.intent.action.CLASH_STOPPED" 15 | val ACTION_CLASH_REQUEST_STOP = "$packageName.intent.action.CLASH_REQUEST_STOP" 16 | val ACTION_PROFILE_CHANGED = "$packageName.intent.action.PROFILE_CHANGED" 17 | val ACTION_PROFILE_REQUEST_UPDATE = "$packageName.intent.action.REQUEST_UPDATE" 18 | val ACTION_PROFILE_SCHEDULE_UPDATES = "$packageName.intent.action.SCHEDULE_UPDATES" 19 | val ACTION_PROFILE_LOADED = "$packageName.intent.action.PROFILE_LOADED" 20 | val ACTION_OVERRIDE_CHANGED = "$packageName.intent.action.OVERRIDE_CHANGED" 21 | 22 | const val EXTRA_STOP_REASON = "stop_reason" 23 | const val EXTRA_UUID = "uuid" 24 | } -------------------------------------------------------------------------------- /design/src/main/res/layout/dialog_text_field.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | 11 | 17 | 18 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/util/ListView.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.util 2 | 3 | import android.content.Context 4 | import android.view.View 5 | import android.view.View.MeasureSpec 6 | import android.widget.FrameLayout 7 | import android.widget.ListAdapter 8 | 9 | fun ListAdapter.measureWidth(context: Context): Int { 10 | val parent = FrameLayout(context) 11 | 12 | val widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) 13 | val heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) 14 | 15 | var itemView: View? = null 16 | var maxWidth = 0 17 | var itemType = 0 18 | 19 | for (i in 0 until count) { 20 | val positionType = getItemViewType(i) 21 | if (positionType != itemType) { 22 | itemType = positionType 23 | itemView = null 24 | } 25 | 26 | itemView = getView(i, itemView, parent) 27 | itemView.measure(widthMeasureSpec, heightMeasureSpec) 28 | 29 | val itemWidth: Int = itemView.measuredWidth 30 | 31 | if (itemWidth > maxWidth) { 32 | maxWidth = itemWidth 33 | } 34 | } 35 | 36 | return maxWidth 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import com.github.kr328.clash.common.util.intent 4 | import com.github.kr328.clash.design.SettingsDesign 5 | import kotlinx.coroutines.isActive 6 | import kotlinx.coroutines.selects.select 7 | 8 | class SettingsActivity : BaseActivity() { 9 | override suspend fun main() { 10 | val design = SettingsDesign(this) 11 | 12 | setContentDesign(design) 13 | 14 | while (isActive) { 15 | select { 16 | events.onReceive { 17 | 18 | } 19 | design.requests.onReceive { 20 | when (it) { 21 | SettingsDesign.Request.StartApp -> 22 | startActivity(AppSettingsActivity::class.intent) 23 | SettingsDesign.Request.StartNetwork -> 24 | startActivity(NetworkSettingsActivity::class.intent) 25 | SettingsDesign.Request.StartOverride -> 26 | startActivity(OverrideSettingsActivity::class.intent) 27 | } 28 | } 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /core/src/main/golang/native/app/tun.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "net" 5 | "syscall" 6 | 7 | "cfa/native/platform" 8 | ) 9 | 10 | var markSocketImpl func(fd int) 11 | var querySocketUidImpl func(protocol int, source, target string) int 12 | 13 | func MarkSocket(fd int) { 14 | markSocketImpl(fd) 15 | } 16 | 17 | func QuerySocketUid(source, target net.Addr) int { 18 | var protocol int 19 | 20 | switch source.Network() { 21 | case "udp", "udp4", "udp6": 22 | protocol = syscall.IPPROTO_UDP 23 | case "tcp", "tcp4", "tcp6": 24 | protocol = syscall.IPPROTO_TCP 25 | default: 26 | return -1 27 | } 28 | 29 | if PlatformVersion() < 29 { 30 | return platform.QuerySocketUidFromProcFs(source, target) 31 | } 32 | 33 | return querySocketUidImpl(protocol, source.String(), target.String()) 34 | } 35 | 36 | func ApplyTunContext(markSocket func(fd int), querySocketUid func(int, string, string) int) { 37 | if markSocket == nil { 38 | markSocket = func(fd int) {} 39 | } 40 | 41 | if querySocketUid == nil { 42 | querySocketUid = func(int, string, string) int { return -1 } 43 | } 44 | 45 | markSocketImpl = markSocket 46 | querySocketUidImpl = querySocketUid 47 | } 48 | 49 | func init() { 50 | ApplyTunContext(nil, nil) 51 | } 52 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/view/ObservableScrollView.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.widget.ScrollView 6 | import androidx.annotation.AttrRes 7 | import androidx.annotation.StyleRes 8 | 9 | class ObservableScrollView @JvmOverloads constructor( 10 | context: Context, 11 | attributeSet: AttributeSet? = null, 12 | @AttrRes defStyleAttr: Int = 0, 13 | @StyleRes defStyleRes: Int = 0 14 | ) : ScrollView(context, attributeSet, defStyleAttr, defStyleRes) { 15 | fun interface OnScrollChangedListener { 16 | fun onChanged(scrollView: ObservableScrollView, x: Int, y: Int, oldl: Int, oldt: Int) 17 | } 18 | 19 | private val scrollChangedListeners: MutableSet = mutableSetOf() 20 | 21 | override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) { 22 | super.onScrollChanged(l, t, oldl, oldt) 23 | 24 | scrollChangedListeners.forEach { 25 | it.onChanged(this, l, t, oldl, oldt) 26 | } 27 | } 28 | 29 | fun addOnScrollChangedListener(listener: OnScrollChangedListener) { 30 | scrollChangedListeners.add(listener) 31 | } 32 | } -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/util/Intent.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.util 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import java.util.* 6 | 7 | fun Intent.grantPermissions(read: Boolean = true, write: Boolean = true): Intent { 8 | var flags = 0 9 | 10 | if (read) 11 | flags = flags or Intent.FLAG_GRANT_READ_URI_PERMISSION 12 | 13 | if (write) 14 | flags = flags or Intent.FLAG_GRANT_WRITE_URI_PERMISSION 15 | 16 | addFlags(flags) 17 | 18 | return this 19 | } 20 | 21 | var Intent.fileName: String? 22 | get() { 23 | return data?.takeIf { it.scheme == "file" }?.schemeSpecificPart 24 | } 25 | set(value) { 26 | data = Uri.fromParts("file", value, null) 27 | } 28 | 29 | var Intent.uuid: UUID? 30 | get() { 31 | return data?.takeIf { it.scheme == "uuid" }?.schemeSpecificPart?.let(UUID::fromString) 32 | } 33 | set(value) { 34 | data = Uri.fromParts("uuid", value.toString(), null) 35 | } 36 | 37 | fun Intent.setUUID(uuid: UUID): Intent { 38 | this.uuid = uuid 39 | 40 | return this 41 | } 42 | 43 | fun Intent.setFileName(fileName: String): Intent { 44 | this.fileName = fileName 45 | 46 | return this 47 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/util/Inserts.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.util 2 | 3 | import android.view.View 4 | import androidx.core.view.ViewCompat 5 | import androidx.core.view.WindowInsetsCompat 6 | import com.github.kr328.clash.design.ui.Insets 7 | 8 | fun View.setOnInsertsChangedListener(adaptLandscape: Boolean = true, listener: (Insets) -> Unit) { 9 | setOnApplyWindowInsetsListener { v, ins -> 10 | val compat = WindowInsetsCompat.toWindowInsetsCompat(ins) 11 | val insets = compat.getInsets(WindowInsetsCompat.Type.systemBars()) 12 | 13 | val rInsets = if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR) { 14 | Insets( 15 | insets.left, 16 | insets.top, 17 | insets.right, 18 | insets.bottom, 19 | ) 20 | } else { 21 | Insets( 22 | insets.right, 23 | insets.top, 24 | insets.left, 25 | insets.bottom, 26 | ) 27 | } 28 | 29 | listener(if (adaptLandscape) rInsets.landscape(v.context) else rInsets) 30 | 31 | compat.toWindowInsets() 32 | } 33 | 34 | requestApplyInsets() 35 | } 36 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/PreferenceProvider.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import com.github.kr328.clash.common.constants.Authorities 6 | import rikka.preference.MultiProcessPreference 7 | import rikka.preference.PreferenceProvider 8 | 9 | class PreferenceProvider : PreferenceProvider() { 10 | override fun onCreatePreference(context: Context): SharedPreferences { 11 | return context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE) 12 | } 13 | 14 | companion object { 15 | private const val FILE_NAME = "service" 16 | 17 | fun createSharedPreferencesFromContext(context: Context): SharedPreferences { 18 | return when (context) { 19 | is BaseService, is TunService -> 20 | context.getSharedPreferences( 21 | FILE_NAME, 22 | Context.MODE_PRIVATE 23 | ) 24 | else -> 25 | MultiProcessPreference( 26 | context, 27 | Authorities.SETTINGS_PROVIDER 28 | ) 29 | } 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/preference/Screen.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.preference 2 | 3 | import android.content.Context 4 | import android.view.ViewGroup 5 | import android.widget.LinearLayout 6 | import android.widget.LinearLayout.LayoutParams 7 | import android.widget.LinearLayout.LayoutParams.MATCH_PARENT 8 | import android.widget.LinearLayout.LayoutParams.WRAP_CONTENT 9 | import kotlinx.coroutines.CoroutineScope 10 | 11 | interface PreferenceScreen : CoroutineScope { 12 | val context: Context 13 | val root: ViewGroup 14 | } 15 | 16 | fun CoroutineScope.preferenceScreen( 17 | context: Context, 18 | configure: PreferenceScreen.() -> Unit 19 | ): PreferenceScreen { 20 | val root = LinearLayout(context).apply { 21 | orientation = LinearLayout.VERTICAL 22 | } 23 | 24 | val impl = object : PreferenceScreen, CoroutineScope by this { 25 | override val context: Context 26 | get() = context 27 | override val root: ViewGroup 28 | get() = root 29 | } 30 | 31 | impl.configure() 32 | 33 | return impl 34 | } 35 | 36 | fun PreferenceScreen.addElement(preference: Preference) { 37 | root.addView(preference.view, LayoutParams(MATCH_PARENT, WRAP_CONTENT)) 38 | } -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_settings.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/model/TunnelState.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.model 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | import com.github.kr328.clash.core.util.Parcelizer 6 | import kotlinx.serialization.SerialName 7 | import kotlinx.serialization.Serializable 8 | 9 | @Serializable 10 | data class TunnelState( 11 | val mode: Mode, 12 | ) : Parcelable { 13 | @Serializable 14 | enum class Mode { 15 | @SerialName("direct") 16 | Direct, 17 | 18 | @SerialName("global") 19 | Global, 20 | 21 | @SerialName("rule") 22 | Rule, 23 | 24 | @SerialName("script") 25 | Script, 26 | } 27 | 28 | override fun writeToParcel(parcel: Parcel, flags: Int) { 29 | Parcelizer.encodeToParcel(serializer(), parcel, this) 30 | } 31 | 32 | override fun describeContents(): Int { 33 | return 0 34 | } 35 | 36 | companion object CREATOR : Parcelable.Creator { 37 | override fun createFromParcel(parcel: Parcel): TunnelState { 38 | return Parcelizer.decodeFromParcel(serializer(), parcel) 39 | } 40 | 41 | override fun newArray(size: Int): Array { 42 | return arrayOfNulls(size) 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /core/src/main/golang/native/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //#include "bridge.h" 4 | import "C" 5 | 6 | import ( 7 | "errors" 8 | "unsafe" 9 | 10 | "cfa/native/app" 11 | 12 | "github.com/Dreamacro/clash/log" 13 | ) 14 | 15 | func openRemoteContent(url string) (int, error) { 16 | u := C.CString(url) 17 | e := (*C.char)(C.malloc(1024)) 18 | 19 | log.Debugln("Open remote url: %s", url) 20 | 21 | defer C.free(unsafe.Pointer(e)) 22 | 23 | fd := C.open_content(u, e, 1024) 24 | 25 | if fd < 0 { 26 | return -1, errors.New(C.GoString(e)) 27 | } 28 | 29 | return int(fd), nil 30 | } 31 | 32 | //export notifyDnsChanged 33 | func notifyDnsChanged(dnsList C.c_string) { 34 | d := C.GoString(dnsList) 35 | 36 | app.NotifyDnsChanged(d) 37 | } 38 | 39 | //export notifyInstalledAppsChanged 40 | func notifyInstalledAppsChanged(uids C.c_string) { 41 | u := C.GoString(uids) 42 | 43 | app.NotifyInstallAppsChanged(u) 44 | } 45 | 46 | //export notifyTimeZoneChanged 47 | func notifyTimeZoneChanged(name C.c_string, offset C.int) { 48 | app.NotifyTimeZoneChanged(C.GoString(name), int(offset)) 49 | } 50 | 51 | 52 | //export queryConfiguration 53 | func queryConfiguration() *C.char { 54 | response := &struct{}{} 55 | 56 | return marshalJson(&response) 57 | } 58 | 59 | func init() { 60 | app.ApplyContentContext(openRemoteContent) 61 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official 22 | # Gradle parallel build 23 | org.gradle.parallel=true -------------------------------------------------------------------------------- /core/src/main/cpp/jni_helper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct _scoped_jni { 10 | JNIEnv *env; 11 | int require_release; 12 | }; 13 | 14 | extern void initialize_jni(JavaVM *vm, JNIEnv *env); 15 | extern jstring jni_new_string(JNIEnv *env, const char *str); 16 | extern char *jni_get_string(JNIEnv *env, jstring str); 17 | extern int jni_catch_exception(JNIEnv *env); 18 | extern void jni_attach_thread(struct _scoped_jni *jni); 19 | extern void jni_detach_thread(struct _scoped_jni *env); 20 | extern void release_string(char **str); 21 | 22 | #define ATTACH_JNI() __attribute__((unused, cleanup(jni_detach_thread))) \ 23 | struct _scoped_jni _jni; \ 24 | jni_attach_thread(&_jni); \ 25 | JNIEnv *env = _jni.env 26 | 27 | #define scoped_string __attribute__((cleanup(release_string))) char* 28 | 29 | #define find_class(name) (*env)->FindClass(env, name) 30 | #define find_method(cls, name, signature) (*env)->GetMethodID(env, cls, name, signature) 31 | #define new_global(obj) (*env)->NewGlobalRef(env, obj) 32 | #define del_global(obj) (*env)->DeleteGlobalRef(env, obj) 33 | #define get_string(jstr) jni_get_string(env, jstr) 34 | #define new_string(cstr) jni_new_string(env, cstr) -------------------------------------------------------------------------------- /design/src/main/res/layout/common_activity_bar.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 28 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/model/Provider.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.model 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | import com.github.kr328.clash.core.util.Parcelizer 6 | import kotlinx.serialization.Serializable 7 | 8 | @Serializable 9 | data class Provider( 10 | val name: String, 11 | val type: Type, 12 | val vehicleType: VehicleType, 13 | val updatedAt: Long 14 | ) : Parcelable, Comparable { 15 | enum class Type { 16 | Proxy, Rule 17 | } 18 | 19 | enum class VehicleType { 20 | HTTP, File, Compatible 21 | } 22 | 23 | override fun writeToParcel(parcel: Parcel, flags: Int) { 24 | Parcelizer.encodeToParcel(serializer(), parcel, this) 25 | } 26 | 27 | override fun describeContents(): Int { 28 | return 0 29 | } 30 | 31 | override fun compareTo(other: Provider): Int { 32 | return compareValuesBy(this, other, Provider::type, Provider::name) 33 | } 34 | 35 | companion object CREATOR : Parcelable.Creator { 36 | override fun createFromParcel(parcel: Parcel): Provider { 37 | return Parcelizer.decodeFromParcel(serializer(), parcel) 38 | } 39 | 40 | override fun newArray(size: Int): Array { 41 | return arrayOfNulls(size) 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/util/Activity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.util 2 | 3 | import androidx.lifecycle.Lifecycle 4 | import androidx.lifecycle.LifecycleOwner 5 | import androidx.lifecycle.LifecycleRegistry 6 | import kotlinx.coroutines.NonCancellable 7 | import kotlinx.coroutines.withContext 8 | 9 | class ActivityResultLifecycle : LifecycleOwner { 10 | private val lifecycle = LifecycleRegistry(this) 11 | 12 | init { 13 | lifecycle.currentState = Lifecycle.State.INITIALIZED 14 | } 15 | 16 | override fun getLifecycle(): Lifecycle { 17 | return lifecycle 18 | } 19 | 20 | suspend fun use(block: suspend (lifecycle: ActivityResultLifecycle, start: () -> Unit) -> T): T { 21 | return try { 22 | markCreated() 23 | 24 | block(this, this::markStarted) 25 | } finally { 26 | withContext(NonCancellable) { 27 | markDestroy() 28 | } 29 | } 30 | } 31 | 32 | private fun markCreated() { 33 | lifecycle.currentState = Lifecycle.State.CREATED 34 | } 35 | 36 | private fun markStarted() { 37 | lifecycle.currentState = Lifecycle.State.STARTED 38 | lifecycle.currentState = Lifecycle.State.RESUMED 39 | } 40 | 41 | private fun markDestroy() { 42 | lifecycle.currentState = Lifecycle.State.DESTROYED 43 | } 44 | } -------------------------------------------------------------------------------- /design/src/main/res/layout/design_new_profile.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 15 | 16 | 20 | 21 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/preference/Tips.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.preference 2 | 3 | import android.view.View 4 | import androidx.annotation.StringRes 5 | import com.github.kr328.clash.design.databinding.PreferenceTipsBinding 6 | import com.github.kr328.clash.design.util.getHtml 7 | import com.github.kr328.clash.design.util.layoutInflater 8 | import com.github.kr328.clash.design.util.root 9 | 10 | interface TipsPreference : Preference { 11 | var text: CharSequence? 12 | } 13 | 14 | fun PreferenceScreen.tips( 15 | @StringRes text: Int, 16 | configure: TipsPreference.() -> Unit = {}, 17 | ): TipsPreference { 18 | val binding = PreferenceTipsBinding 19 | .inflate(context.layoutInflater, context.root, false) 20 | val impl = object : TipsPreference { 21 | override var text: CharSequence? 22 | get() = binding.tips.text 23 | set(value) { 24 | binding.tips.text = value 25 | } 26 | override val view: View 27 | get() = binding.root 28 | override var enabled: Boolean 29 | get() = binding.root.isEnabled 30 | set(value) { 31 | binding.root.isEnabled = value 32 | } 33 | } 34 | 35 | binding.tips.text = context.getHtml(text) 36 | 37 | impl.configure() 38 | 39 | addElement(impl) 40 | 41 | return impl 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/NetworkSettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import com.github.kr328.clash.common.util.intent 4 | import com.github.kr328.clash.design.NetworkSettingsDesign 5 | import com.github.kr328.clash.service.store.ServiceStore 6 | import kotlinx.coroutines.isActive 7 | import kotlinx.coroutines.selects.select 8 | 9 | class NetworkSettingsActivity : BaseActivity() { 10 | override suspend fun main() { 11 | val design = NetworkSettingsDesign( 12 | this, 13 | uiStore, 14 | ServiceStore(this), 15 | clashRunning, 16 | ) 17 | 18 | setContentDesign(design) 19 | 20 | while (isActive) { 21 | select { 22 | events.onReceive { 23 | when (it) { 24 | Event.ClashStart, Event.ClashStop, Event.ServiceRecreated -> 25 | recreate() 26 | else -> Unit 27 | } 28 | } 29 | design.requests.onReceive { 30 | when (it) { 31 | NetworkSettingsDesign.Request.StartAccessControlList -> 32 | startActivity(AccessControlActivity::class.intent) 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/adapter/LogMessageAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.adapter 2 | 3 | import android.content.Context 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.github.kr328.clash.core.model.LogMessage 7 | import com.github.kr328.clash.design.databinding.AdapterLogMessageBinding 8 | import com.github.kr328.clash.design.util.layoutInflater 9 | 10 | class LogMessageAdapter( 11 | private val context: Context, 12 | private val copy: (LogMessage) -> Unit, 13 | ) : 14 | RecyclerView.Adapter() { 15 | class Holder(val binding: AdapterLogMessageBinding) : RecyclerView.ViewHolder(binding.root) 16 | 17 | var messages: List = emptyList() 18 | 19 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { 20 | return Holder( 21 | AdapterLogMessageBinding 22 | .inflate(context.layoutInflater, parent, false) 23 | ) 24 | } 25 | 26 | override fun onBindViewHolder(holder: Holder, position: Int) { 27 | val current = messages[position] 28 | 29 | holder.binding.message = current 30 | holder.binding.root.setOnLongClickListener { 31 | copy(current) 32 | 33 | true 34 | } 35 | } 36 | 37 | override fun getItemCount(): Int { 38 | return messages.size 39 | } 40 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/adapter/LogFileAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.adapter 2 | 3 | import android.content.Context 4 | import android.view.ViewGroup 5 | import android.view.ViewGroup.LayoutParams 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.github.kr328.clash.design.model.LogFile 8 | import com.github.kr328.clash.design.util.format 9 | import com.github.kr328.clash.design.view.ActionLabel 10 | 11 | class LogFileAdapter( 12 | private val context: Context, 13 | private val open: (LogFile) -> Unit, 14 | ) : RecyclerView.Adapter() { 15 | class Holder(val label: ActionLabel) : RecyclerView.ViewHolder(label) 16 | 17 | var logs: List = emptyList() 18 | 19 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { 20 | return Holder(ActionLabel(context).apply { 21 | layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) 22 | }) 23 | } 24 | 25 | override fun onBindViewHolder(holder: Holder, position: Int) { 26 | val current = logs[position] 27 | 28 | holder.label.text = current.fileName 29 | holder.label.subtext = current.date.format(context) 30 | holder.label.setOnClickListener { 31 | open(current) 32 | } 33 | } 34 | 35 | override fun getItemCount(): Int { 36 | return logs.size 37 | } 38 | } -------------------------------------------------------------------------------- /service/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | "%1$s↑\t%2$s↓" 4 | 5 | Clash Status 6 | Profile Service Status 7 | Profile Processing Status 8 | Profile Process Result 9 | Update Successfully 10 | Update Failure 11 | Update %s completed 12 | Update %1$s: %2$s 13 | Running 14 | Loading 15 | Clash for Android 16 | Profiles and Providers 17 | Configuration.yaml 18 | Provider Files 19 | Profile Updater 20 | Profile Updating 21 | 22 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/model/Profile.kt: -------------------------------------------------------------------------------- 1 | @file:UseSerializers(UUIDSerializer::class) 2 | 3 | package com.github.kr328.clash.service.model 4 | 5 | import android.os.Parcel 6 | import android.os.Parcelable 7 | import com.github.kr328.clash.core.util.Parcelizer 8 | import com.github.kr328.clash.service.util.UUIDSerializer 9 | import kotlinx.serialization.Serializable 10 | import kotlinx.serialization.UseSerializers 11 | import java.util.* 12 | 13 | @Serializable 14 | data class Profile( 15 | val uuid: UUID, 16 | val name: String, 17 | val type: Type, 18 | val source: String, 19 | val active: Boolean, 20 | val interval: Long, 21 | 22 | val updatedAt: Long, 23 | val imported: Boolean, 24 | val pending: Boolean, 25 | ) : Parcelable { 26 | enum class Type { 27 | File, Url, External 28 | } 29 | 30 | override fun writeToParcel(parcel: Parcel, flags: Int) { 31 | Parcelizer.encodeToParcel(serializer(), parcel, this) 32 | } 33 | 34 | override fun describeContents(): Int { 35 | return 0 36 | } 37 | 38 | companion object CREATOR : Parcelable.Creator { 39 | override fun createFromParcel(parcel: Parcel): Profile { 40 | return Parcelizer.decodeFromParcel(serializer(), parcel) 41 | } 42 | 43 | override fun newArray(size: Int): Array { 44 | return arrayOfNulls(size) 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /.github/workflows/build-unsigned.yaml: -------------------------------------------------------------------------------- 1 | name: Build Unsigned 2 | on: 3 | push: 4 | branches: 5 | - main 6 | paths-ignore: 7 | - '.github/**' 8 | - '.idea/**' 9 | - '.gitattributes' 10 | - '.gitignore' 11 | - '.gitmodules' 12 | - '**.md' 13 | - 'LICENSE' 14 | - 'NOTICE' 15 | pull_request: 16 | paths-ignore: 17 | - '.github/**' 18 | - '.idea/**' 19 | - '.gitattributes' 20 | - '.gitignore' 21 | - '.gitmodules' 22 | - '**.md' 23 | - 'LICENSE' 24 | - 'NOTICE' 25 | 26 | jobs: 27 | BuildUnsigned: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Checkout Repository 31 | uses: actions/checkout@v3 32 | with: 33 | submodules: recursive 34 | - name: Setup Java 35 | uses: actions/setup-java@v3 36 | with: 37 | distribution: 'zulu' 38 | java-version: 17 39 | - name: Setup Go 40 | uses: actions/setup-go@v3 41 | with: 42 | go-version: 1.18 43 | - uses: actions/cache@v3 44 | with: 45 | path: | 46 | ~/.cache/go-build 47 | ~/go/pkg/mod 48 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 49 | restore-keys: | 50 | ${{ runner.os }}-go- 51 | - name: Build 52 | uses: gradle/gradle-build-action@v2 53 | with: 54 | arguments: --no-daemon app:assembleFossRelease 55 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/preference/Value.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.preference 2 | 3 | interface NullableTextAdapter { 4 | fun from(value: T): String? 5 | fun to(text: String?): T 6 | 7 | companion object { 8 | val Port = object : NullableTextAdapter { 9 | override fun from(value: Int?): String? { 10 | if (value == null) return null 11 | 12 | return if (value > 0) value.toString() else "" 13 | } 14 | 15 | override fun to(text: String?): Int? { 16 | if (text == null) return null 17 | 18 | return text.toIntOrNull() ?: 0 19 | } 20 | } 21 | 22 | val String = object : NullableTextAdapter { 23 | override fun from(value: String?): String? { 24 | return value 25 | } 26 | 27 | override fun to(text: String?): String? { 28 | return text 29 | } 30 | } 31 | } 32 | } 33 | 34 | interface TextAdapter { 35 | fun from(value: T): String 36 | fun to(text: String): T 37 | 38 | companion object { 39 | val String = object : TextAdapter { 40 | override fun from(value: String): String { 41 | return value 42 | } 43 | 44 | override fun to(text: String): String { 45 | return text 46 | } 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/view/VerticalScrollableHost.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.view 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.MotionEvent 6 | import android.widget.FrameLayout 7 | import kotlin.math.absoluteValue 8 | import kotlin.math.tan 9 | 10 | class VerticalScrollableHost @JvmOverloads constructor( 11 | context: Context, 12 | attributeSet: AttributeSet? = null, 13 | defStyleAttr: Int = 0, 14 | defStyleRes: Int = 0 15 | ) : FrameLayout(context, attributeSet, defStyleAttr, defStyleRes) { 16 | private var initialX = 0f 17 | private var initialY = 0f 18 | 19 | private val degree = tan(Math.toRadians(15.0)) 20 | 21 | override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { 22 | val parentView = parent ?: return super.onInterceptTouchEvent(ev) 23 | 24 | if (ev.action == MotionEvent.ACTION_DOWN) { 25 | initialX = ev.x 26 | initialY = ev.y 27 | parentView.requestDisallowInterceptTouchEvent(true) 28 | } else if (ev.action == MotionEvent.ACTION_MOVE) { 29 | val dx = ev.x - initialX 30 | val dy = ev.y - initialY 31 | 32 | val t = dy.absoluteValue / dx.absoluteValue 33 | 34 | if (t < degree) { 35 | parentView.requestDisallowInterceptTouchEvent(false) 36 | } 37 | } 38 | 39 | return super.onInterceptTouchEvent(ev) 40 | } 41 | } -------------------------------------------------------------------------------- /core/src/main/golang/native/tunnel/providers_open.go: -------------------------------------------------------------------------------- 1 | //go:build !premium 2 | 3 | package tunnel 4 | 5 | import ( 6 | "fmt" 7 | "time" 8 | 9 | P "github.com/Dreamacro/clash/adapter/provider" 10 | "github.com/Dreamacro/clash/constant/provider" 11 | "github.com/Dreamacro/clash/tunnel" 12 | ) 13 | 14 | type Provider struct { 15 | Name string `json:"name"` 16 | VehicleType string `json:"vehicleType"` 17 | Type string `json:"type"` 18 | UpdatedAt int64 `json:"updatedAt"` 19 | } 20 | 21 | func QueryProviders() []*Provider { 22 | p := tunnel.Providers() 23 | 24 | providers := make([]provider.Provider, 0, len(p)) 25 | 26 | for _, proxy := range p { 27 | if proxy.VehicleType() == provider.Compatible { 28 | continue 29 | } 30 | 31 | providers = append(providers, proxy) 32 | } 33 | 34 | result := make([]*Provider, 0, len(providers)) 35 | 36 | for _, p := range providers { 37 | updatedAt := time.Time{} 38 | 39 | if s, ok := p.(P.UpdatableProvider); ok { 40 | updatedAt = s.UpdatedAt() 41 | } 42 | 43 | result = append(result, &Provider{ 44 | Name: p.Name(), 45 | VehicleType: p.VehicleType().String(), 46 | Type: p.Type().String(), 47 | UpdatedAt: updatedAt.UnixNano() / 1000 / 1000, 48 | }) 49 | } 50 | 51 | return result 52 | } 53 | 54 | func UpdateProvider(_ string, name string) error { 55 | p, ok := tunnel.Providers()[name] 56 | if !ok { 57 | return fmt.Errorf("%s not found", name) 58 | } 59 | 60 | return p.Update() 61 | } 62 | --------------------------------------------------------------------------------