├── 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 │ │ │ ├── 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 │ │ └── values-ru │ │ │ └── strings.xml │ │ └── AndroidManifest.xml ├── build.gradle.kts └── proguard-rules.pro ├── design ├── consumer-rules.pro ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── res │ │ ├── values │ │ │ ├── ids.xml │ │ │ ├── styles.xml │ │ │ └── colors.xml │ │ ├── drawable │ │ │ ├── bg_b.xml │ │ │ ├── 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_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_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_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 │ │ ├── anim │ │ │ └── rotate_infinite.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 │ │ ├── ProfilePageState.kt │ │ ├── ProxyPageState.kt │ │ ├── File.kt │ │ ├── AppInfo.kt │ │ ├── AppInfoSort.kt │ │ ├── LogFile.kt │ │ ├── ProviderState.kt │ │ └── ProfileProvider.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 │ │ └── ProxyAdapter.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 │ │ ├── values-ko-rKR │ │ │ └── strings.xml │ │ ├── values-ja-rJP │ │ │ └── strings.xml │ │ └── values-ru │ │ │ └── strings.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 │ │ ├── SelectionDao.kt │ │ ├── Selection.kt │ │ ├── PendingDao.kt │ │ ├── ImportedDao.kt │ │ ├── Imported.kt │ │ └── Pending.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 │ │ └── RemoteService.kt ├── proguard-rules.pro └── build.gradle.kts ├── fastlane └── metadata │ └── android │ ├── zh-CN │ ├── short_description.txt │ └── full_description.txt │ └── en-US │ ├── short_description.txt │ └── full_description.txt ├── .github └── ISSUE_TEMPLATE │ ├── config.yml │ ├── 04-feature-request-zh-cn.yml │ └── 02-feature-request-en.yml ├── .gitattributes ├── core ├── src │ ├── 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_premium.go │ │ │ │ ├── suspend.go │ │ │ │ ├── loopback_open.go │ │ │ │ ├── state.go │ │ │ │ ├── statistic.go │ │ │ │ ├── geoip.go │ │ │ │ ├── conn.go │ │ │ │ └── connectivity.go │ │ │ ├── trace.c │ │ │ ├── debug.go │ │ │ ├── all │ │ │ │ └── imports.go │ │ │ ├── app │ │ │ │ ├── dns.go │ │ │ │ ├── content.go │ │ │ │ ├── ui.go │ │ │ │ ├── app.go │ │ │ │ └── tun.go │ │ │ ├── proxy.go │ │ │ ├── trace.h │ │ │ ├── common │ │ │ │ └── path.go │ │ │ ├── tun │ │ │ │ ├── metadata_open.go │ │ │ │ ├── udp.go │ │ │ │ ├── dns.go │ │ │ │ └── metadata_premium.go │ │ │ ├── utils.go │ │ │ ├── proxy │ │ │ │ └── http.go │ │ │ ├── config │ │ │ │ ├── provider.go │ │ │ │ └── defaults.go │ │ │ ├── platform │ │ │ │ └── limit.go │ │ │ ├── main.go │ │ │ ├── app.go │ │ │ └── delegate │ │ │ │ └── init.go │ │ └── .idea │ │ │ └── codeStyles │ │ │ ├── codeStyleConfig.xml │ │ │ └── Project.xml │ │ └── AndroidManifest.xml ├── consumer-rules.pro └── proguard-rules.pro ├── release.keystore ├── app └── src │ └── main │ ├── ic_launcher-web.png │ ├── ic_launcher-playstore.png │ ├── res │ ├── values │ │ ├── ids.xml │ │ ├── colors.xml │ │ ├── themes.xml │ │ ├── ic_banner_background.xml │ │ └── ic_launcher_background.xml │ ├── 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 │ ├── 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 │ └── java │ └── com │ └── github │ └── kr328 │ └── clash │ ├── util │ ├── Uri.kt │ ├── Files.kt │ ├── Service.kt │ ├── Content.kt │ ├── Clash.kt │ ├── Activity.kt │ └── Remote.kt │ ├── HelpActivity.kt │ ├── ApkBrokenActivity.kt │ ├── store │ ├── AppStore.kt │ └── TipsStore.kt │ ├── RestartReceiver.kt │ ├── log │ ├── LogcatFilter.kt │ ├── LogcatWriter.kt │ ├── SystemLogcat.kt │ └── LogcatCache.kt │ ├── remote │ └── StatusClient.kt │ ├── AppCrashedActivity.kt │ ├── SettingsActivity.kt │ └── NetworkSettingsActivity.kt ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitmodules ├── .idea └── codeStyles │ └── codeStyleConfig.xml ├── 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 | -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/short_description.txt: -------------------------------------------------------------------------------- 1 | 基于规则的隧道 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | A rule-based tunnel -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /release.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevejohnson7/ClashMetaForAndroid/HEAD/release.keystore -------------------------------------------------------------------------------- /core/src/main/cpp/bridge_helper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | uint64_t down_scale_traffic(uint64_t value); -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevejohnson7/ClashMetaForAndroid/HEAD/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevejohnson7/ClashMetaForAndroid/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevejohnson7/ClashMetaForAndroid/HEAD/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /design/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "clash-foss"] 2 | path = core/src/foss/golang/clash 3 | url = https://github.com/MetaCubeX/Clash.Meta 4 | branch = android-real 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevejohnson7/ClashMetaForAndroid/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevejohnson7/ClashMetaForAndroid/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevejohnson7/ClashMetaForAndroid/HEAD/app/src/main/res/mipmap-xhdpi/ic_banner.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevejohnson7/ClashMetaForAndroid/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevejohnson7/ClashMetaForAndroid/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevejohnson7/ClashMetaForAndroid/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.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 | -------------------------------------------------------------------------------- /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-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevejohnson7/ClashMetaForAndroid/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/stevejohnson7/ClashMetaForAndroid/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevejohnson7/ClashMetaForAndroid/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/stevejohnson7/ClashMetaForAndroid/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/constants/Authorities.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.constants 2 | 3 | import com.github.kr328.clash.common.util.packageName 4 | 5 | object Authorities { 6 | val STATUS_PROVIDER = "$packageName.status" 7 | val SETTINGS_PROVIDER = "$packageName.settings" 8 | val FILES_PROVIDER = "$packageName.files" 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/util/Service.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.util 2 | 3 | import android.content.Context 4 | import android.content.ServiceConnection 5 | 6 | fun Context.unbindServiceSilent(connection: ServiceConnection) { 7 | try { 8 | unbindService(connection) 9 | } catch (e: Exception) { 10 | // ignore 11 | } 12 | } -------------------------------------------------------------------------------- /core/src/main/golang/native/all/imports.go: -------------------------------------------------------------------------------- 1 | package all 2 | 3 | import ( 4 | _ "cfa/native/app" 5 | _ "cfa/native/common" 6 | _ "cfa/native/config" 7 | _ "cfa/native/delegate" 8 | _ "cfa/native/platform" 9 | _ "cfa/native/proxy" 10 | _ "cfa/native/tun" 11 | _ "cfa/native/tunnel" 12 | 13 | _ "golang.org/x/sync/semaphore" 14 | 15 | _ "github.com/Dreamacro/clash/log" 16 | ) 17 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/util/Net.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.util 2 | 3 | import java.net.InetAddress 4 | import java.net.InetSocketAddress 5 | import java.net.URL 6 | 7 | fun parseInetSocketAddress(address: String): InetSocketAddress { 8 | val url = URL("https://$address") 9 | 10 | return InetSocketAddress(InetAddress.getByName(url.host), url.port) 11 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/document/VirtualDocument.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.document 2 | 3 | class VirtualDocument( 4 | override val id: String, 5 | override val name: String, 6 | override val mimeType: String, 7 | override val size: Long, 8 | override val updatedAt: Long, 9 | override val flags: Set, 10 | ) : Document 11 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/Daos.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | fun ImportedDao(): ImportedDao { 4 | return Database.database.openImportedDao() 5 | } 6 | 7 | fun PendingDao(): PendingDao { 8 | return Database.database.openPendingDao() 9 | } 10 | 11 | fun SelectionDao(): SelectionDao { 12 | return Database.database.openSelectionProxyDao() 13 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/model/AppInfoSort.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.model 2 | 3 | enum class AppInfoSort(comparator: Comparator) : Comparator by comparator { 4 | Label(compareBy(AppInfo::label)), 5 | PackageName(compareBy(AppInfo::packageName)), 6 | InstallTime(compareBy(AppInfo::installTime)), 7 | UpdateTime(compareBy(AppInfo::updateDate)), 8 | } 9 | -------------------------------------------------------------------------------- /fastlane/metadata/android/zh-CN/full_description.txt: -------------------------------------------------------------------------------- 1 | Clash.Meta 的 Android 图形界面。 2 | 3 |

功能

4 |
    5 |
  • 本地 HTTP/HTTPS/SOCKS 服务器
  • 6 |
  • 支持 VMess, Shadowsocks, Trojan, Snell 协议远程连接
  • 7 |
  • 内置 DNS 服务器,旨在最小化 DNS 污染攻击影响,支持 DoH/DoT 上游和 fake IP
  • 8 |
  • 基于规则根据域名,GEOIP, IPCIDR 或进程转发数据包到不同节点
  • 9 |
  • 远程分组允许用户实现强大的规则。支持自动回退,负载均衡或基于延迟自动选择节点
  • 10 |
  • 远程提供者,允许用户远程获取节点列表而不是在配置中硬编码
  • 11 |
-------------------------------------------------------------------------------- /core/src/main/golang/native/tunnel/statistic.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "github.com/Dreamacro/clash/tunnel/statistic" 5 | ) 6 | 7 | func ResetStatistic() { 8 | statistic.DefaultManager.ResetStatistic() 9 | } 10 | 11 | func Now() (up int64, down int64) { 12 | return statistic.DefaultManager.Now() 13 | } 14 | 15 | func Total() (up int64, down int64) { 16 | return statistic.DefaultManager.Total() 17 | } 18 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/bg_b.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_stop.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/compat/Services.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.compat 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.os.Build 6 | 7 | fun Context.startForegroundServiceCompat(intent: Intent) { 8 | if (Build.VERSION.SDK_INT >= 26) { 9 | startForegroundService(intent) 10 | } else { 11 | startService(intent) 12 | } 13 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Connectivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import android.net.ConnectivityManager 4 | import android.net.Network 5 | 6 | fun ConnectivityManager.resolvePrimaryDns(network: Network?): String? { 7 | val properties = getLinkProperties(network) ?: return null 8 | 9 | return properties.dnsServers.firstOrNull()?.asSocketAddressText(53) 10 | } 11 | -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/id/UndefinedIds.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.id 2 | 3 | object UndefinedIds { 4 | private const val PREFIX = 0x14000000 5 | private const val MASK = 0x00FFFFFF 6 | 7 | private var current: Int = 0 8 | 9 | @Synchronized 10 | fun next(): Int { 11 | current = ((current and MASK) + 1 or PREFIX) 12 | 13 | return current 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_flash_on.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/compat/Package.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package com.github.kr328.clash.common.compat 4 | 5 | import android.content.pm.PackageInfo 6 | 7 | val PackageInfo.versionCodeCompat: Long 8 | get() { 9 | return if (android.os.Build.VERSION.SDK_INT >= 28) { 10 | longVersionCode 11 | } else { 12 | versionCode.toLong() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_add.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_get_app.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /core/src/main/golang/native/app/dns.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/Dreamacro/clash/dns" 7 | ) 8 | 9 | func NotifyDnsChanged(dnsList string) { 10 | dL := strings.Split(dnsList, ",") 11 | 12 | ns := make([]dns.NameServer, 0, len(dnsList)) 13 | for _, d := range dL { 14 | ns = append(ns, dns.NameServer{Addr: d}) 15 | } 16 | 17 | dns.UpdateSystemDNS(dL) 18 | dns.FlushCacheWithDefaultResolver() 19 | } 20 | 21 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Coroutine.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.job 5 | import kotlinx.coroutines.runBlocking 6 | 7 | fun CoroutineScope.cancelAndJoinBlocking() { 8 | val scope = this 9 | 10 | runBlocking { 11 | scope.coroutineContext.job.cancel() 12 | scope.coroutineContext.job.join() 13 | } 14 | } -------------------------------------------------------------------------------- /core/src/main/golang/native/proxy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | //#include "bridge.h" 4 | import "C" 5 | 6 | import ( 7 | "cfa/native/proxy" 8 | ) 9 | 10 | //export startHttp 11 | func startHttp(listenAt C.c_string) *C.char { 12 | l := C.GoString(listenAt) 13 | 14 | listen, err := proxy.Start(l) 15 | if err != nil { 16 | return nil 17 | } 18 | 19 | return C.CString(listen) 20 | } 21 | 22 | //export stopHttp 23 | func stopHttp() { 24 | proxy.Stop() 25 | } -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_publish.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Net.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | data class IPNet(val ip: String, val prefix: Int) 4 | 5 | fun parseCIDR(cidr: String): IPNet { 6 | val s = cidr.split("/", limit = 2) 7 | 8 | if (s.size != 2) 9 | throw IllegalArgumentException("Invalid address") 10 | 11 | val address = s[0] 12 | val prefix = s[1].toInt() 13 | 14 | return IPNet(address, prefix) 15 | } -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/compat/Resource.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package com.github.kr328.clash.common.compat 4 | 5 | import android.content.res.Configuration 6 | import android.os.Build 7 | import java.util.* 8 | 9 | val Configuration.preferredLocale: Locale 10 | get() { 11 | return if (Build.VERSION.SDK_INT >= 24) { 12 | locales[0] 13 | } else { 14 | locale 15 | } 16 | } -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_clear_all.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/compat/Html.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package com.github.kr328.clash.common.compat 4 | 5 | import android.os.Build 6 | import android.text.Html 7 | import android.text.Spanned 8 | 9 | fun fromHtmlCompat(content: String): Spanned { 10 | return if (Build.VERSION.SDK_INT >= 24) { 11 | Html.fromHtml(content, Html.FROM_HTML_MODE_COMPACT) 12 | } else { 13 | Html.fromHtml(content) 14 | } 15 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/ui/Surface.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.ui 2 | 3 | import androidx.databinding.BaseObservable 4 | import androidx.databinding.Bindable 5 | import com.github.kr328.clash.design.BR 6 | 7 | class Surface : BaseObservable() { 8 | var insets: Insets = Insets.EMPTY 9 | @Bindable get 10 | set(value) { 11 | field = value 12 | 13 | notifyPropertyChanged(BR.insets) 14 | } 15 | } -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_arrow_back.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_swap_vert.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/util/Components.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.util 2 | 3 | import android.content.ComponentName 4 | import android.content.Intent 5 | import com.github.kr328.clash.common.Global 6 | import kotlin.reflect.KClass 7 | 8 | val KClass<*>.componentName: ComponentName 9 | get() = ComponentName(Global.application.packageName, this.java.name) 10 | 11 | val KClass<*>.intent: Intent 12 | get() = Intent(Global.application, this.java) -------------------------------------------------------------------------------- /core/src/main/golang/native/trace.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "bridge.h" 4 | 5 | #include 6 | 7 | #define ENABLE_TRACE 0 8 | 9 | #if ENABLE_TRACE 10 | 11 | extern void trace_method_exit(const char **name); 12 | 13 | #define TRACE_METHOD() __attribute__((cleanup(trace_method_exit))) const char *__method_name = __FUNCTION__; __android_log_print(ANDROID_LOG_VERBOSE, TAG, "TRACE-IN %s", __method_name) 14 | 15 | #else 16 | 17 | #define TRACE_METHOD() 18 | 19 | #endif -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_delete.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /core/src/main/cpp/bridge_helper.c: -------------------------------------------------------------------------------- 1 | #include "bridge_helper.h" 2 | 3 | uint64_t down_scale_traffic(uint64_t value) { 4 | if (value > 1042 * 1024 * 1024) 5 | return ((value * 100u / 1024u / 1024u / 1024u) & 0x3FFFFFFFu) | (3u << 30u); 6 | if (value > 1024 * 1024) 7 | return ((value * 100u / 1024u / 1024u) & 0x3FFFFFFFu) | (2u << 30u); 8 | if (value > 1024) 9 | return ((value * 100u / 1024u) & 0x3FFFFFFFu) | (1u << 30u); 10 | return value & 0x3FFFFFFFu; 11 | } 12 | 13 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/util/View.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.util 2 | 3 | import android.view.View 4 | import android.view.inputmethod.InputMethodManager 5 | import androidx.core.content.getSystemService 6 | 7 | fun View.requestTextInput() { 8 | post { 9 | requestFocus() 10 | 11 | postDelayed({ 12 | context.getSystemService() 13 | ?.showSoftInput(this, 0) 14 | }, 300) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_close.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_delete.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/xml/full_backup_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 12 | 15 | 18 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_info.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Database.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import com.github.kr328.clash.service.data.ImportedDao 4 | import com.github.kr328.clash.service.data.PendingDao 5 | import java.util.* 6 | 7 | suspend fun generateProfileUUID(): UUID { 8 | var result = UUID.randomUUID() 9 | 10 | while (ImportedDao().exists(result) || PendingDao().exists(result)) { 11 | result = UUID.randomUUID() 12 | } 13 | 14 | return result 15 | } 16 | -------------------------------------------------------------------------------- /design/src/main/res/anim/rotate_infinite.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/BaseService.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service 2 | 3 | import android.app.Service 4 | import com.github.kr328.clash.service.util.cancelAndJoinBlocking 5 | import kotlinx.coroutines.CoroutineScope 6 | import kotlinx.coroutines.Dispatchers 7 | 8 | abstract class BaseService : Service(), CoroutineScope by CoroutineScope(Dispatchers.Default) { 9 | override fun onDestroy() { 10 | super.onDestroy() 11 | 12 | cancelAndJoinBlocking() 13 | } 14 | } -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_replay.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_view_list.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/constants/Components.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.constants 2 | 3 | import android.content.ComponentName 4 | import com.github.kr328.clash.common.util.packageName 5 | 6 | object Components { 7 | private const val componentsPackageName = "com.github.kr328.clash" 8 | 9 | val MAIN_ACTIVITY = ComponentName(packageName, "$componentsPackageName.MainActivity") 10 | val PROPERTIES_ACTIVITY = ComponentName(packageName, "$componentsPackageName.PropertiesActivity") 11 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to Clash for Android 2 | 3 | #### Code Style 4 | 5 | Please use `Android Studio` or `Intellij IDEA` to open the project and use the project code style profile. 6 | 7 | `File` -> `Settings` -> `Editor` -> `Code Style` -> `C/C++ and Kotlin` -> `Scheme` -> `Project` 8 | 9 | 10 | 11 | #### License 12 | 13 | Contributing to Clash for Android that assumes you allow code to be merged into closed-source branch of Clash for Android. Other terms follow the [GPLv3](https://www.gnu.org/licenses/gpl-3.0.html) 14 | 15 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tunnel/geoip.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/Dreamacro/clash/component/mmdb" 7 | "github.com/oschwald/maxminddb-golang" 8 | ) 9 | 10 | func InstallSideloadGeoip(block []byte) error { 11 | if block == nil { 12 | mmdb.InstallOverride(nil) 13 | return nil 14 | } 15 | 16 | db, err := maxminddb.FromBytes(block) 17 | if err != nil { 18 | return fmt.Errorf("load sideload geoip mmdb: %s", err.Error()) 19 | } 20 | 21 | mmdb.InstallOverride(db) 22 | 23 | return nil 24 | } 25 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_folder.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /common/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/compat/Intents.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.compat 2 | 3 | import android.app.PendingIntent 4 | import android.os.Build 5 | 6 | fun pendingIntentFlags(flags: Int, mutable: Boolean = false): Int { 7 | return if (Build.VERSION.SDK_INT >= 24) { 8 | if (Build.VERSION.SDK_INT > 30 && mutable) { 9 | flags or PendingIntent.FLAG_MUTABLE 10 | } else { 11 | flags or PendingIntent.FLAG_IMMUTABLE 12 | } 13 | } else { 14 | flags 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /core/src/main/golang/native/common/path.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import "strings" 4 | 5 | func ResolveAsRoot(path string) string { 6 | directories := strings.Split(path, "/") 7 | result := make([]string, 0, len(directories)) 8 | 9 | for _, directory := range directories { 10 | switch directory { 11 | case "", ".": 12 | continue 13 | case "..": 14 | if len(result) > 0 { 15 | result = result[:len(result)-1] 16 | } 17 | default: 18 | result = append(result, directory) 19 | } 20 | } 21 | 22 | return strings.Join(result, "/") 23 | } 24 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_apps.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Files.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import android.content.Context 4 | import java.io.File 5 | 6 | val Context.importedDir: File 7 | get() = filesDir.resolve("imported") 8 | 9 | val Context.pendingDir: File 10 | get() = filesDir.resolve("pending") 11 | 12 | val Context.processingDir: File 13 | get() = filesDir.resolve("processing") 14 | 15 | val File.directoryLastModified: Long? 16 | get() { 17 | return walk().map { it.lastModified() }.maxOrNull() 18 | } -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_info.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_edit.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_label.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tun/metadata_open.go: -------------------------------------------------------------------------------- 1 | //go:build !premium 2 | 3 | package tun 4 | 5 | import ( 6 | "net" 7 | 8 | C "github.com/Dreamacro/clash/constant" 9 | ) 10 | 11 | func createMetadata(lAddr, rAddr *net.TCPAddr) *C.Metadata { 12 | return &C.Metadata{ 13 | NetWork: C.TCP, 14 | Type: C.SOCKS5, 15 | SrcIP: lAddr.AddrPort().Addr(), 16 | DstIP: rAddr.AddrPort().Addr(), 17 | SrcPort: uint16(lAddr.Port), 18 | DstPort: uint16(rAddr.Port), 19 | Host: "", 20 | RawSrcAddr: lAddr, 21 | RawDstAddr: rAddr, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/preference/Preference.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.preference 2 | 3 | import android.view.View 4 | 5 | fun interface OnChangedListener { 6 | fun onChanged() 7 | } 8 | 9 | interface Preference { 10 | val view: View 11 | 12 | var enabled: Boolean 13 | get() = view.isEnabled 14 | set(value) { 15 | view.isEnabled = value 16 | view.isClickable = value 17 | view.isFocusable = value 18 | view.alpha = if (value) 1.0f else 0.33f 19 | } 20 | } -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_content_copy.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_save.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_swap_vertical_circle.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/HelpActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import android.content.Intent 4 | import com.github.kr328.clash.design.HelpDesign 5 | import kotlinx.coroutines.isActive 6 | 7 | class HelpActivity : BaseActivity() { 8 | override suspend fun main() { 9 | val design = HelpDesign(this) { 10 | startActivity(Intent(Intent.ACTION_VIEW).setData(it)) 11 | } 12 | 13 | setContentDesign(design) 14 | 15 | while (isActive) { 16 | events.receive() 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/compat/View.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package com.github.kr328.clash.common.compat 4 | 5 | import android.os.Build 6 | import android.widget.TextView 7 | import androidx.annotation.StyleRes 8 | 9 | var TextView.textAppearance: Int 10 | get() = throw UnsupportedOperationException("set value only") 11 | set(@StyleRes value) { 12 | if (Build.VERSION.SDK_INT >= 23) { 13 | setTextAppearance(value) 14 | } else { 15 | setTextAppearance(context, value) 16 | } 17 | } -------------------------------------------------------------------------------- /core/src/main/golang/.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_work.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tun/udp.go: -------------------------------------------------------------------------------- 1 | package tun 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | type packet struct { 8 | local *net.UDPAddr 9 | data []byte 10 | writeBack func(b []byte, addr net.Addr) (int, error) 11 | drop func() 12 | } 13 | 14 | func (pkt *packet) Data() []byte { 15 | return pkt.data 16 | } 17 | 18 | func (pkt *packet) WriteBack(b []byte, addr net.Addr) (n int, err error) { 19 | return pkt.writeBack(b, addr) 20 | } 21 | 22 | func (pkt *packet) Drop() { 23 | pkt.drop() 24 | } 25 | 26 | func (pkt *packet) LocalAddr() net.Addr { 27 | return pkt.local 28 | } 29 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_more_vert.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_check_circle.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_cloud_download.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/compat/Context.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package com.github.kr328.clash.common.compat 4 | 5 | import android.content.Context 6 | import android.graphics.drawable.Drawable 7 | import androidx.annotation.ColorRes 8 | import androidx.annotation.DrawableRes 9 | import androidx.core.content.ContextCompat 10 | 11 | fun Context.getColorCompat(@ColorRes id: Int): Int { 12 | return ContextCompat.getColor(this, id) 13 | } 14 | 15 | fun Context.getDrawableCompat(@DrawableRes id: Int): Drawable? { 16 | return ContextCompat.getDrawable(this, id) 17 | } -------------------------------------------------------------------------------- /core/src/main/golang/native/app/content.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "syscall" 7 | ) 8 | 9 | var openContentImpl = func(url string) (int, error) { 10 | return -1, errors.New("not implement") 11 | } 12 | 13 | func OpenContent(url string) (*os.File, error) { 14 | fd, err := openContentImpl(url) 15 | 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | _ = syscall.SetNonblock(fd, true) 21 | 22 | return os.NewFile(uintptr(fd), "fd"), nil 23 | } 24 | 25 | func ApplyContentContext(openContent func(string) (int, error)) { 26 | openContentImpl = openContent 27 | } 28 | -------------------------------------------------------------------------------- /design/src/main/res/layout/preference_category.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/ui/ObservableCurrentTime.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.ui 2 | 3 | import androidx.databinding.BaseObservable 4 | import androidx.databinding.Bindable 5 | import androidx.databinding.library.baseAdapters.BR 6 | 7 | class ObservableCurrentTime : BaseObservable() { 8 | var value: Long = System.currentTimeMillis() 9 | @Bindable get 10 | private set(value) { 11 | field = value 12 | 13 | notifyPropertyChanged(BR.value) 14 | } 15 | 16 | fun update() { 17 | value = System.currentTimeMillis() 18 | } 19 | } -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_domain.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /core/src/main/golang/native/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "C" 4 | 5 | import ( 6 | "encoding/json" 7 | "reflect" 8 | ) 9 | 10 | func marshalJson(obj any) *C.char { 11 | res, err := json.Marshal(obj) 12 | if err != nil { 13 | panic(err.Error()) 14 | } 15 | 16 | return C.CString(string(res)) 17 | } 18 | 19 | func marshalString(obj any) *C.char { 20 | if obj == nil { 21 | return nil 22 | } 23 | 24 | switch o := obj.(type) { 25 | case error: 26 | return C.CString(o.Error()) 27 | case string: 28 | return C.CString(o) 29 | } 30 | 31 | panic("invalid marshal type " + reflect.TypeOf(obj).Name()) 32 | } 33 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_inbox.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_update.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/Global.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common 2 | 3 | import android.app.Application 4 | import kotlinx.coroutines.CoroutineScope 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.cancel 7 | 8 | object Global : CoroutineScope by CoroutineScope(Dispatchers.IO) { 9 | val application: Application 10 | get() = application_ 11 | 12 | private lateinit var application_: Application 13 | 14 | fun init(application: Application) { 15 | this.application_ = application 16 | } 17 | 18 | fun destroy() { 19 | cancel() 20 | } 21 | } -------------------------------------------------------------------------------- /core/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -keep class kotlinx.coroutines.CompletableDeferred { 2 | *; 3 | } 4 | 5 | -keep class kotlin.Unit { 6 | *; 7 | } 8 | 9 | -keepattributes *Annotation*, InnerClasses 10 | -dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations 11 | 12 | # kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer 13 | -keepclassmembers class kotlinx.serialization.json.** { 14 | *** Companion; 15 | } 16 | -keepclasseswithmembers class kotlinx.serialization.json.** { 17 | kotlinx.serialization.KSerializer serializer(...); 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/ApkBrokenActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import com.github.kr328.clash.design.ApkBrokenDesign 6 | import kotlinx.coroutines.isActive 7 | 8 | class ApkBrokenActivity : BaseActivity() { 9 | override suspend fun main() { 10 | val design = ApkBrokenDesign(this) 11 | 12 | setContentDesign(design) 13 | 14 | while (isActive) { 15 | val req = design.requests.receive() 16 | 17 | startActivity(Intent(Intent.ACTION_VIEW).setData(Uri.parse(req.url))) 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_search.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_brightness_4.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_sync.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/util/App.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.util 2 | 3 | import android.content.pm.PackageInfo 4 | import android.content.pm.PackageManager 5 | import com.github.kr328.clash.common.compat.foreground 6 | import com.github.kr328.clash.design.model.AppInfo 7 | 8 | fun PackageInfo.toAppInfo(pm: PackageManager): AppInfo { 9 | return AppInfo( 10 | packageName = packageName, 11 | icon = applicationInfo.loadIcon(pm).foreground(), 12 | label = applicationInfo.loadLabel(pm).toString(), 13 | installTime = firstInstallTime, 14 | updateDate = lastUpdateTime, 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_article.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 13 | 14 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_restore.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_outline_not_interested.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_assignment.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/store/AppStore.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.store 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.common.store.Store 5 | import com.github.kr328.clash.common.store.asStoreProvider 6 | 7 | class AppStore(context: Context) { 8 | private val store = Store( 9 | context 10 | .getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE) 11 | .asStoreProvider() 12 | ) 13 | 14 | var updatedAt: Long by store.long( 15 | key = "updated_at", 16 | defaultValue = -1, 17 | ) 18 | 19 | companion object { 20 | private const val FILE_NAME = "app" 21 | } 22 | } -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_attach_file.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/layout/common_recycler_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 16 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tun/dns.go: -------------------------------------------------------------------------------- 1 | package tun 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/Dreamacro/clash/dns" 7 | 8 | D "github.com/miekg/dns" 9 | ) 10 | 11 | func shouldHijackDns(dns net.IP, target net.IP, targetPort int) bool { 12 | if targetPort != 53 { 13 | return false 14 | } 15 | 16 | return net.IPv4zero.Equal(dns) || target.Equal(dns) 17 | } 18 | 19 | func relayDns(payload []byte) ([]byte, error) { 20 | msg := &D.Msg{} 21 | if err := msg.Unpack(payload); err != nil { 22 | return nil, err 23 | } 24 | 25 | r, err := dns.ServeDNSWithDefaultServer(msg) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | r.SetRcode(msg, r.Rcode) 31 | 32 | return r.Pack() 33 | } 34 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/clash/module/CloseModule.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.clash.module 2 | 3 | import android.app.Service 4 | import com.github.kr328.clash.common.constants.Intents 5 | import com.github.kr328.clash.common.log.Log 6 | 7 | class CloseModule(service: Service) : Module(service) { 8 | object RequestClose 9 | 10 | override suspend fun run() { 11 | val broadcasts = receiveBroadcast { 12 | addAction(Intents.ACTION_CLASH_REQUEST_STOP) 13 | } 14 | 15 | broadcasts.receive() 16 | 17 | Log.d("User request close") 18 | 19 | return enqueueEvent(RequestClose) 20 | } 21 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/04-feature-request-zh-cn.yml: -------------------------------------------------------------------------------- 1 | name: "[简体中文] 功能请求" 2 | description: "您希望的能够在应用中增加功能" 3 | title: "[Feature Request] " 4 | labels: ["enhancement"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 感谢您在百忙之中填写此功能请求报告。 10 | 11 | 注意: 请务必在上方文本框的 `[Feature Request]` **之后**填写清晰明了的标题。 12 | 13 | 14 | - type: textarea 15 | id: "description" 16 | attributes: 17 | label: "功能描述" 18 | description: | 19 | 简介明了的描述此功能。 20 | validations: 21 | required: true 22 | - type: textarea 23 | id: "additional" 24 | attributes: 25 | label: "附加信息" 26 | description: | 27 | 与此功能相关的其他附加信息。 28 | -------------------------------------------------------------------------------- /design/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("android") 3 | kotlin("kapt") 4 | id("com.android.library") 5 | } 6 | 7 | dependencies { 8 | implementation(project(":common")) 9 | implementation(project(":core")) 10 | implementation(project(":service")) 11 | 12 | implementation(libs.kotlin.coroutine) 13 | implementation(libs.androidx.core) 14 | implementation(libs.androidx.appcompat) 15 | implementation(libs.androidx.activity) 16 | implementation(libs.androidx.coordinator) 17 | implementation(libs.androidx.recyclerview) 18 | implementation(libs.androidx.fragment) 19 | implementation(libs.androidx.viewpager) 20 | implementation(libs.google.material) 21 | } 22 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_update.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_dns.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /design/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #1e4376 4 | #1976d2 5 | #50000000 6 | #FFFAFAFA 7 | #FF121212 8 | #FF202020 9 | #FF808080 10 | #FFD3D3D3 11 | #FF808080 12 | #FFB00020 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/RestartReceiver.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import com.github.kr328.clash.service.StatusProvider 7 | import com.github.kr328.clash.util.startClashService 8 | 9 | class RestartReceiver : BroadcastReceiver() { 10 | override fun onReceive(context: Context, intent: Intent) { 11 | when (intent.action) { 12 | Intent.ACTION_BOOT_COMPLETED, Intent.ACTION_MY_PACKAGE_REPLACED -> { 13 | if (StatusProvider.shouldStartClashOnBoot) 14 | context.startClashService() 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_extension.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tun/metadata_premium.go: -------------------------------------------------------------------------------- 1 | //go:build premium 2 | 3 | package tun 4 | 5 | import ( 6 | "net" 7 | "net/netip" 8 | "strconv" 9 | 10 | C "github.com/Dreamacro/clash/constant" 11 | ) 12 | 13 | func createMetadata(lAddr, rAddr *net.TCPAddr) *C.Metadata { 14 | srcAddr, _ := netip.AddrFromSlice(lAddr.IP) 15 | dstAddr, _ := netip.AddrFromSlice(rAddr.IP) 16 | 17 | return &C.Metadata{ 18 | NetWork: C.TCP, 19 | Type: C.SOCKS5, 20 | SrcIP: srcAddr, 21 | DstIP: dstAddr, 22 | SrcPort: strconv.Itoa(lAddr.Port), 23 | DstPort: strconv.Itoa(rAddr.Port), 24 | AddrType: C.AtypIPv4, 25 | Host: "", 26 | RawSrcAddr: lAddr, 27 | RawDstAddr: rAddr, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/bridge/Content.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.bridge 2 | 3 | import android.net.Uri 4 | import androidx.annotation.Keep 5 | import com.github.kr328.clash.common.Global 6 | import java.io.FileNotFoundException 7 | 8 | @Keep 9 | object Content { 10 | @JvmStatic 11 | fun open(url: String): Int { 12 | val uri = Uri.parse(url) 13 | 14 | if (uri.scheme != "content") { 15 | throw UnsupportedOperationException("Unsupported scheme ${uri.scheme}") 16 | } 17 | 18 | return Global.application.contentResolver.openFileDescriptor(uri, "r")?.detachFd() 19 | ?: throw FileNotFoundException("$uri not found") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/util/Ticker.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.util 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.channels.Channel 5 | import kotlinx.coroutines.delay 6 | import kotlinx.coroutines.isActive 7 | import kotlinx.coroutines.launch 8 | 9 | fun CoroutineScope.ticker(period: Long): Channel { 10 | val channel = Channel(Channel.RENDEZVOUS) 11 | 12 | launch { 13 | try { 14 | while (isActive) { 15 | channel.send(System.currentTimeMillis()) 16 | 17 | delay(period) 18 | } 19 | } catch (ignored: Exception) { 20 | 21 | } 22 | } 23 | 24 | return channel 25 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/clash/module/TimeZoneModule.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.clash.module 2 | 3 | import android.app.Service 4 | import android.content.Intent 5 | import com.github.kr328.clash.core.Clash 6 | import java.util.* 7 | 8 | class TimeZoneModule(service: Service) : Module(service) { 9 | override suspend fun run() { 10 | val timeZones = receiveBroadcast { 11 | addAction(Intent.ACTION_TIMEZONE_CHANGED) 12 | } 13 | 14 | while (true) { 15 | val timeZone = TimeZone.getDefault() 16 | 17 | Clash.notifyTimeZoneChanged(timeZone.id, timeZone.rawOffset) 18 | 19 | timeZones.receive() 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/store/StoreProvider.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.store 2 | 3 | interface StoreProvider { 4 | fun getInt(key: String, defaultValue: Int): Int 5 | fun setInt(key: String, value: Int) 6 | 7 | fun getLong(key: String, defaultValue: Long): Long 8 | fun setLong(key: String, value: Long) 9 | 10 | fun getString(key: String, defaultValue: String): String 11 | fun setString(key: String, value: String) 12 | 13 | fun getStringSet(key: String, defaultValue: Set): Set 14 | fun setStringSet(key: String, value: Set) 15 | 16 | fun getBoolean(key: String, defaultValue: Boolean): Boolean 17 | fun setBoolean(key: String, value: Boolean) 18 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/util/Validator.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.util 2 | 3 | import com.github.kr328.clash.common.util.PatternFileName 4 | 5 | typealias Validator = (String) -> Boolean 6 | 7 | val ValidatorAcceptAll: Validator = { 8 | true 9 | } 10 | 11 | val ValidatorFileName: Validator = { 12 | PatternFileName.matches(it) && it.isNotBlank() 13 | } 14 | 15 | val ValidatorNotBlank: Validator = { 16 | it.isNotBlank() 17 | } 18 | 19 | val ValidatorHttpUrl: Validator = { 20 | it.startsWith("https://", ignoreCase = true) || it.startsWith("http://", ignoreCase = true) 21 | } 22 | 23 | val ValidatorAutoUpdateInterval: Validator = { 24 | it.isEmpty() || (it.toLongOrNull() ?: 0) >= 15 25 | } -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | A Graphical user interface of Clash.Meta for Android. 2 | 3 |

Features

4 |
    5 |
  • Local HTTP/HTTPS/SOCKS server
  • 6 |
  • VMess, Shadowsocks, Trojan, Snell protocol support for remote connections
  • 7 |
  • Built-in DNS server that aims to minimize DNS pollution attack impact, supports DoH/DoT upstream and fake IP
  • 8 |
  • Rules based off domains, GEOIP, IPCIDR or Process to forward packets to different nodes
  • 9 |
  • Remote groups allow users to implement powerful rules. Supports automatic fallback, load balancing or auto select node based off latency
  • 10 |
  • Remote providers, allowing users to get node lists remotely instead of hardcoding in config
  • 11 |
-------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_adb.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/Converters.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.TypeConverter 4 | import com.github.kr328.clash.service.model.Profile 5 | import java.util.* 6 | 7 | class Converters { 8 | @TypeConverter 9 | fun fromUUID(uuid: UUID): String { 10 | return uuid.toString() 11 | } 12 | 13 | @TypeConverter 14 | fun toUUID(uuid: String): UUID { 15 | return UUID.fromString(uuid) 16 | } 17 | 18 | @TypeConverter 19 | fun fromProfileType(type: Profile.Type): String { 20 | return type.name 21 | } 22 | 23 | @TypeConverter 24 | fun toProfileType(type: String): Profile.Type { 25 | return Profile.Type.valueOf(type) 26 | } 27 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/util/ActivityBar.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.util 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.widget.ImageView 6 | import android.widget.TextView 7 | import com.github.kr328.clash.design.R 8 | import com.github.kr328.clash.design.view.ActivityBarLayout 9 | 10 | fun ActivityBarLayout.applyFrom(context: Context) { 11 | if (context is Activity) { 12 | findViewById(R.id.activity_bar_close_view)?.apply { 13 | setOnClickListener { 14 | context.onBackPressed() 15 | } 16 | } 17 | findViewById(R.id.activity_bar_title_view)?.apply { 18 | text = context.title 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /core/src/main/golang/native/app/ui.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/dlclark/regexp2" 5 | 6 | "github.com/Dreamacro/clash/log" 7 | ) 8 | 9 | var uiSubtitlePattern *regexp2.Regexp 10 | 11 | func ApplySubtitlePattern(pattern string) { 12 | if pattern == "" { 13 | uiSubtitlePattern = nil 14 | 15 | return 16 | } 17 | 18 | if o := uiSubtitlePattern; o != nil && o.String() == pattern { 19 | return 20 | } 21 | 22 | reg, err := regexp2.Compile(pattern, regexp2.IgnoreCase|regexp2.Compiled) 23 | if err == nil { 24 | uiSubtitlePattern = reg 25 | } else { 26 | uiSubtitlePattern = nil 27 | 28 | log.Warnln("Compile ui-subtitle-pattern: %s", err.Error()) 29 | } 30 | } 31 | 32 | func SubtitlePattern() *regexp2.Regexp { 33 | return uiSubtitlePattern 34 | } 35 | -------------------------------------------------------------------------------- /core/src/main/golang/native/proxy/http.go: -------------------------------------------------------------------------------- 1 | package proxy 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/Dreamacro/clash/listener/http" 7 | "github.com/Dreamacro/clash/tunnel" 8 | ) 9 | 10 | var listener *http.Listener 11 | var lock sync.Mutex 12 | 13 | func Start(listen string) (listenAt string, err error) { 14 | lock.Lock() 15 | defer lock.Unlock() 16 | 17 | stopLocked() 18 | 19 | listener, err = http.NewWithAuthenticate(listen, tunnel.Tunnel, false) 20 | if err == nil { 21 | listenAt = listener.Listener().Addr().String() 22 | } 23 | 24 | return 25 | } 26 | 27 | func Stop() { 28 | lock.Lock() 29 | defer lock.Unlock() 30 | 31 | stopLocked() 32 | } 33 | 34 | func stopLocked() { 35 | if listener != nil { 36 | listener.Close() 37 | } 38 | 39 | listener = nil 40 | } 41 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/util/Interval.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.util 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.design.R 5 | import java.util.concurrent.TimeUnit 6 | 7 | fun Long.elapsedIntervalString(context: Context): String { 8 | val day = TimeUnit.MILLISECONDS.toDays(this) 9 | val hour = TimeUnit.MILLISECONDS.toHours(this) 10 | val minute = TimeUnit.MILLISECONDS.toMinutes(this) 11 | 12 | return when { 13 | day > 0 -> context.getString(R.string.format_days_ago, day) 14 | hour > 0 -> context.getString(R.string.format_hours_ago, hour) 15 | minute > 0 -> context.getString(R.string.format_minutes_ago, minute) 16 | else -> context.getString(R.string.recently) 17 | } 18 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/SelectionDao.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.* 4 | import java.util.* 5 | 6 | @Dao 7 | @TypeConverters(Converters::class) 8 | interface SelectionDao { 9 | @Insert(onConflict = OnConflictStrategy.REPLACE) 10 | fun setSelected(selection: Selection) 11 | 12 | @Query("DELETE FROM selections WHERE uuid = :uuid AND proxy = :proxy") 13 | fun removeSelected(uuid: UUID, proxy: String) 14 | 15 | @Query("SELECT * FROM selections WHERE uuid = :uuid") 16 | suspend fun querySelections(uuid: UUID): List 17 | 18 | @Query("DELETE FROM selections WHERE uuid = :uuid AND proxy in (:proxies)") 19 | suspend fun removeSelections(uuid: UUID, proxies: List) 20 | } 21 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/view/AppRecyclerView.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.view 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.util.AttributeSet 6 | import androidx.annotation.AttrRes 7 | import androidx.recyclerview.widget.RecyclerView 8 | 9 | class AppRecyclerView @JvmOverloads constructor( 10 | context: Context, 11 | attributeSet: AttributeSet? = null, 12 | @AttrRes defStyleAttr: Int = 0 13 | ) : RecyclerView(context, attributeSet, defStyleAttr) { 14 | init { 15 | isFocusable = false 16 | } 17 | 18 | override fun onDraw(c: Canvas?) { 19 | super.onDraw(c) 20 | } 21 | 22 | override fun dispatchDraw(canvas: Canvas?) { 23 | super.dispatchDraw(canvas) 24 | } 25 | } -------------------------------------------------------------------------------- /core/src/main/golang/native/config/provider.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/Dreamacro/clash/config" 7 | ) 8 | 9 | func forEachProviders(rawCfg *config.RawConfig, fun func(index int, total int, key string, provider map[string]any)) { 10 | total := len(rawCfg.ProxyProvider) + len(rawCfg.RuleProvider) 11 | index := 0 12 | 13 | for k, v := range rawCfg.ProxyProvider { 14 | fun(index, total, k, v) 15 | 16 | index++ 17 | } 18 | 19 | for k, v := range rawCfg.RuleProvider { 20 | fun(index, total, k, v) 21 | 22 | index++ 23 | } 24 | } 25 | 26 | func destroyProviders(cfg *config.Config) { 27 | for _, p := range cfg.Providers { 28 | _ = p.(io.Closer).Close() 29 | } 30 | 31 | for _, p := range cfg.RuleProviders { 32 | _ = p.(io.Closer).Close() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/model/LogFile.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.model 2 | 3 | import java.util.* 4 | 5 | data class LogFile(val fileName: String, val date: Date) { 6 | companion object { 7 | private val REGEX_FILE = Regex("clash-(\\d+).log") 8 | private const val FORMAT_FILE_NAME = "clash-%d.log" 9 | 10 | fun parseFromFileName(fileName: String): LogFile? { 11 | return REGEX_FILE.matchEntire(fileName)?.run { 12 | LogFile(fileName, Date(groupValues[1].toLong())) 13 | } 14 | } 15 | 16 | fun generate(): LogFile { 17 | val current = Date() 18 | val fileName = FORMAT_FILE_NAME.format(current.time) 19 | 20 | return LogFile(fileName, current) 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /core/src/main/golang/native/platform/limit.go: -------------------------------------------------------------------------------- 1 | // +build linux 2 | 3 | package platform 4 | 5 | import "syscall" 6 | 7 | var nullFd int 8 | var maxFdCount int 9 | 10 | func init() { 11 | fd, err := syscall.Open("/dev/null", syscall.O_WRONLY, 0644) 12 | if err != nil { 13 | panic(err.Error()) 14 | } 15 | 16 | nullFd = fd 17 | 18 | var limit syscall.Rlimit 19 | 20 | if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil { 21 | maxFdCount = 1024 22 | } else { 23 | maxFdCount = int(limit.Cur) 24 | } 25 | 26 | maxFdCount = maxFdCount / 4 * 3 27 | } 28 | 29 | func ShouldBlockConnection() bool { 30 | fd, err := syscall.Dup(nullFd) 31 | if err != nil { 32 | return true 33 | } 34 | 35 | _ = syscall.Close(fd) 36 | 37 | if fd > maxFdCount { 38 | return true 39 | } 40 | 41 | return false 42 | } 43 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/model/ProviderState.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.model 2 | 3 | import androidx.databinding.BaseObservable 4 | import androidx.databinding.Bindable 5 | import com.github.kr328.clash.core.model.Provider 6 | import com.github.kr328.clash.design.BR 7 | 8 | class ProviderState( 9 | val provider: Provider, 10 | updatedAt: Long, 11 | updating: Boolean, 12 | ) : BaseObservable() { 13 | var updatedAt: Long = updatedAt 14 | @Bindable get 15 | set(value) { 16 | field = value 17 | 18 | notifyPropertyChanged(BR.updatedAt) 19 | } 20 | 21 | var updating: Boolean = updating 22 | @Bindable get 23 | set(value) { 24 | field = value 25 | 26 | notifyPropertyChanged(BR.updating) 27 | } 28 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/document/FileDocument.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.document 2 | 3 | import android.provider.DocumentsContract 4 | import java.io.File 5 | 6 | class FileDocument( 7 | val file: File, 8 | override val flags: Set, 9 | private val idOverride: String? = null, 10 | private val nameOverride: String? = null, 11 | ) : Document { 12 | override val id: String 13 | get() = idOverride ?: file.name 14 | override val name: String 15 | get() = nameOverride ?: file.name 16 | override val mimeType: String 17 | get() = if (file.isDirectory) DocumentsContract.Document.MIME_TYPE_DIR else "text/plain" 18 | override val size: Long 19 | get() = file.length() 20 | override val updatedAt: Long 21 | get() = file.lastModified() 22 | } -------------------------------------------------------------------------------- /common/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /core/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /core/src/main/golang/native/tunnel/conn.go: -------------------------------------------------------------------------------- 1 | package tunnel 2 | 3 | import ( 4 | C "github.com/Dreamacro/clash/constant" 5 | "github.com/Dreamacro/clash/tunnel/statistic" 6 | ) 7 | 8 | func CloseAllConnections() { 9 | statistic.DefaultManager.Range(func(c statistic.Tracker) bool { 10 | _ = c.Close() 11 | return true 12 | }) 13 | } 14 | 15 | func closeMatch(filter func(conn C.Conn) bool) { 16 | statistic.DefaultManager.Range(func(c statistic.Tracker) bool { 17 | if cc, ok := c.(C.Conn); ok { 18 | if filter(cc) { 19 | _ = cc.Close() 20 | return true 21 | } 22 | } 23 | return false 24 | }) 25 | } 26 | 27 | func closeConnByGroup(name string) { 28 | closeMatch(func(conn C.Conn) bool { 29 | for _, c := range conn.Chains() { 30 | if c == name { 31 | return true 32 | } 33 | } 34 | 35 | return false 36 | }) 37 | } 38 | -------------------------------------------------------------------------------- /design/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /hideapi/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /service/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/document/Path.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.document 2 | 3 | import java.util.* 4 | 5 | data class Path( 6 | val uuid: UUID?, 7 | val scope: Scope?, 8 | val relative: List? 9 | ) { 10 | enum class Scope { 11 | Configuration, Providers 12 | } 13 | 14 | override fun toString(): String { 15 | if (uuid == null) 16 | return "/" 17 | 18 | if (scope == null) 19 | return "/$uuid" 20 | 21 | val sc = when (scope) { 22 | Scope.Configuration -> Paths.CONFIGURATION_ID 23 | Scope.Providers -> Paths.PROVIDERS_ID 24 | } 25 | 26 | if (relative == null) 27 | return "/$uuid/$sc" 28 | 29 | return "/$uuid/$sc/${relative.joinToString(separator = "/")}" 30 | } 31 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/Selection.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.ForeignKey 6 | import androidx.room.TypeConverters 7 | import java.util.* 8 | 9 | @Entity( 10 | tableName = "selections", 11 | foreignKeys = [ForeignKey( 12 | entity = Imported::class, 13 | childColumns = ["uuid"], 14 | parentColumns = ["uuid"], 15 | onDelete = ForeignKey.CASCADE, 16 | onUpdate = ForeignKey.CASCADE 17 | )], 18 | primaryKeys = ["uuid", "proxy"] 19 | ) 20 | @TypeConverters(Converters::class) 21 | data class Selection( 22 | @ColumnInfo(name = "uuid") val uuid: UUID, 23 | @ColumnInfo(name = "proxy") val proxy: String, 24 | @ColumnInfo(name = "selected") val selected: String, 25 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/util/Content.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.util 2 | 3 | import android.content.ContentResolver 4 | import android.net.Uri 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.withContext 7 | import java.io.FileNotFoundException 8 | 9 | private fun fileNotFound(file: Uri): FileNotFoundException { 10 | return FileNotFoundException("$file not found") 11 | } 12 | 13 | @Suppress("BlockingMethodInNonBlockingContext") 14 | suspend fun ContentResolver.copyContentTo( 15 | source: Uri, 16 | target: Uri 17 | ) { 18 | withContext(Dispatchers.IO) { 19 | (openInputStream(source) ?: throw fileNotFound(source)).use { input -> 20 | (openOutputStream(target, "rwt") ?: throw fileNotFound(target)).use { output -> 21 | input.copyTo(output) 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/log/LogcatFilter.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.util.format 6 | import java.io.BufferedWriter 7 | import java.io.Writer 8 | import java.util.* 9 | 10 | class LogcatFilter(output: Writer, private val context: Context) : BufferedWriter(output) { 11 | fun writeHeader(time: Date) { 12 | appendLine("# Capture on ${time.format(context)}") 13 | } 14 | 15 | fun writeMessage(message: LogMessage) { 16 | val time = message.time.format(context, includeDate = false) 17 | val level = message.level.name 18 | 19 | appendLine(FORMAT.format(time, level, message.message)) 20 | } 21 | 22 | companion object { 23 | private const val FORMAT = "%12s %7s: %s" 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/store/TipsStore.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.store 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.common.store.Store 5 | import com.github.kr328.clash.common.store.asStoreProvider 6 | 7 | class TipsStore(context: Context) { 8 | private val store = Store( 9 | context 10 | .getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE) 11 | .asStoreProvider() 12 | ) 13 | 14 | var requestDonate: Boolean by store.boolean( 15 | key = "request_donate", 16 | defaultValue = true, 17 | ) 18 | 19 | var primaryVersion: Int by store.int( 20 | key = "primary_version", 21 | defaultValue = -1, 22 | ) 23 | 24 | companion object { 25 | const val CURRENT_PRIMARY_VERSION = 1 26 | 27 | private const val FILE_NAME = "tips" 28 | } 29 | } -------------------------------------------------------------------------------- /core/src/main/java/com/github/kr328/clash/core/util/Serializers.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.core.util 2 | 3 | import kotlinx.serialization.KSerializer 4 | import kotlinx.serialization.descriptors.PrimitiveKind 5 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor 6 | import kotlinx.serialization.descriptors.SerialDescriptor 7 | import kotlinx.serialization.encoding.Decoder 8 | import kotlinx.serialization.encoding.Encoder 9 | import java.util.* 10 | 11 | object DateSerializer : KSerializer { 12 | override val descriptor: SerialDescriptor 13 | get() = PrimitiveSerialDescriptor("Date", PrimitiveKind.LONG) 14 | 15 | override fun deserialize(decoder: Decoder): Date { 16 | return Date(decoder.decodeLong()) 17 | } 18 | 19 | override fun serialize(encoder: Encoder, value: Date) { 20 | encoder.encodeLong(value.time) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/log/LogcatWriter.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.BufferedWriter 8 | import java.io.FileWriter 9 | 10 | class LogcatWriter(context: Context) : AutoCloseable { 11 | private val file = LogFile.generate() 12 | private val writer = BufferedWriter(FileWriter(context.logsDir.resolve(file.fileName))) 13 | 14 | override fun close() { 15 | writer.close() 16 | } 17 | 18 | fun appendMessage(message: LogMessage) { 19 | writer.appendLine(FORMAT.format(message.time.time, message.level.name, message.message)) 20 | } 21 | 22 | companion object { 23 | private const val FORMAT = "%d:%s:%s" 24 | } 25 | } -------------------------------------------------------------------------------- /core/src/main/golang/native/config/defaults.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | var ( 4 | defaultNameServers = []string{ 5 | "223.5.5.5", 6 | "119.29.29.29", 7 | "8.8.4.4", 8 | "1.0.0.1", 9 | } 10 | defaultFakeIPFilter = []string{ 11 | // Stun Services 12 | "+.stun.*.*", 13 | "+.stun.*.*.*", 14 | "+.stun.*.*.*.*", 15 | "+.stun.*.*.*.*.*", 16 | 17 | // Google Voices 18 | "lens.l.google.com", 19 | 20 | // Nintendo Switch STUN 21 | "*.n.n.srv.nintendo.net", 22 | 23 | // PlayStation STUN 24 | "+.stun.playstation.net", 25 | 26 | // XBox 27 | "xbox.*.*.microsoft.com", 28 | "*.*.xboxlive.com", 29 | 30 | // Microsoft Captive Portal 31 | "*.msftncsi.com", 32 | "*.msftconnecttest.com", 33 | 34 | // Bilibili CDN 35 | "*.mcdn.bilivideo.cn", 36 | 37 | // Windows Default LAN WorkGroup 38 | "WORKGROUP", 39 | } 40 | defaultFakeIPRange = "28.0.0.0/8" 41 | ) 42 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/log/SystemLogcat.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.log 2 | 3 | object SystemLogcat { 4 | private val command = arrayOf( 5 | "logcat", 6 | "-d", 7 | "-s", 8 | "Go", 9 | "DEBUG", 10 | "AndroidRuntime", 11 | "ClashMetaForAndroid", 12 | "LwIP", 13 | ) 14 | 15 | fun dumpCrash(): String { 16 | return try { 17 | val process = Runtime.getRuntime().exec(command) 18 | 19 | val result = process.inputStream.use { stream -> 20 | stream.reader().readLines() 21 | .filterNot { it.startsWith("------") } 22 | .joinToString("\n") 23 | } 24 | 25 | process.waitFor() 26 | 27 | result.trim() 28 | } catch (e: Exception) { 29 | "" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/preference/Category.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.PreferenceCategoryBinding 6 | import com.github.kr328.clash.design.util.layoutInflater 7 | 8 | fun PreferenceScreen.category( 9 | @StringRes text: Int, 10 | ) { 11 | val binding = PreferenceCategoryBinding 12 | .inflate(context.layoutInflater, root, false) 13 | 14 | binding.textView.text = context.getString(text) 15 | 16 | addElement(object : Preference { 17 | override val view: View 18 | get() = binding.root 19 | override var enabled: Boolean 20 | get() = binding.root.isEnabled 21 | set(value) { 22 | binding.root.isEnabled = value 23 | } 24 | }) 25 | } -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/util/Landscape.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.util 2 | 3 | import android.content.Context 4 | import com.github.kr328.clash.design.R 5 | import com.github.kr328.clash.design.ui.Insets 6 | 7 | fun Insets.landscape(context: Context): Insets { 8 | val displayMetrics = context.resources.displayMetrics 9 | val minWidth = context.getPixels(R.dimen.surface_landscape_min_width) 10 | 11 | val width = displayMetrics.widthPixels 12 | val height = displayMetrics.heightPixels 13 | 14 | return if (width > height && width > minWidth) { 15 | val expectedWidth = width.coerceAtMost(height.coerceAtLeast(minWidth)) 16 | 17 | val padding = (width - expectedWidth).coerceAtLeast(start + end) / 2 18 | 19 | copy(start = padding.coerceAtLeast(start), end = padding.coerceAtLeast(end)) 20 | } else { 21 | this 22 | } 23 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/remote/IProfileManager.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.remote 2 | 3 | import com.github.kr328.clash.service.model.Profile 4 | import com.github.kr328.kaidl.BinderInterface 5 | import java.util.* 6 | 7 | @BinderInterface 8 | interface IProfileManager { 9 | suspend fun create(type: Profile.Type, name: String, source: String = ""): UUID 10 | suspend fun clone(uuid: UUID): UUID 11 | suspend fun commit(uuid: UUID, callback: IFetchObserver? = null) 12 | suspend fun release(uuid: UUID) 13 | suspend fun delete(uuid: UUID) 14 | suspend fun patch(uuid: UUID, name: String, source: String, interval: Long) 15 | suspend fun update(uuid: UUID) 16 | suspend fun queryByUUID(uuid: UUID): Profile? 17 | suspend fun queryAll(): List 18 | suspend fun queryActive(): Profile? 19 | suspend fun setActive(profile: Profile) 20 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/util/Serializers.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.util 2 | 3 | import kotlinx.serialization.KSerializer 4 | import kotlinx.serialization.descriptors.PrimitiveKind 5 | import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor 6 | import kotlinx.serialization.descriptors.SerialDescriptor 7 | import kotlinx.serialization.encoding.Decoder 8 | import kotlinx.serialization.encoding.Encoder 9 | import java.util.* 10 | 11 | class UUIDSerializer : KSerializer { 12 | override val descriptor: SerialDescriptor = 13 | PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING) 14 | 15 | override fun deserialize(decoder: Decoder): UUID { 16 | return UUID.fromString(decoder.decodeString()) 17 | } 18 | 19 | override fun serialize(encoder: Encoder, value: UUID) { 20 | encoder.encodeString(value.toString()) 21 | } 22 | } -------------------------------------------------------------------------------- /common/src/main/java/com/github/kr328/clash/common/log/Log.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.common.log 2 | 3 | object Log { 4 | private const val TAG = "ClashMetaForAndroid" 5 | 6 | fun i(message: String, throwable: Throwable? = null) = 7 | android.util.Log.i(TAG, message, throwable) 8 | 9 | fun w(message: String, throwable: Throwable? = null) = 10 | android.util.Log.w(TAG, message, throwable) 11 | 12 | fun e(message: String, throwable: Throwable? = null) = 13 | android.util.Log.e(TAG, message, throwable) 14 | 15 | fun d(message: String, throwable: Throwable? = null) = 16 | android.util.Log.d(TAG, message, throwable) 17 | 18 | fun v(message: String, throwable: Throwable? = null) = 19 | android.util.Log.v(TAG, message, throwable) 20 | 21 | fun f(message: String, throwable: Throwable) = 22 | android.util.Log.wtf(message, throwable) 23 | } 24 | -------------------------------------------------------------------------------- /core/src/main/golang/native/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /* 4 | #cgo LDFLAGS: -llog 5 | 6 | #include "bridge.h" 7 | */ 8 | import "C" 9 | 10 | import ( 11 | "runtime" 12 | 13 | "cfa/native/config" 14 | "cfa/native/delegate" 15 | "cfa/native/tunnel" 16 | 17 | "github.com/Dreamacro/clash/log" 18 | ) 19 | 20 | func main() { 21 | panic("Stub!") 22 | } 23 | 24 | //export coreInit 25 | func coreInit(home, versionName C.c_string, sdkVersion C.int) { 26 | h := C.GoString(home) 27 | v := C.GoString(versionName) 28 | s := int(sdkVersion) 29 | 30 | delegate.Init(h, v, s) 31 | 32 | reset() 33 | } 34 | 35 | //export reset 36 | func reset() { 37 | config.LoadDefault() 38 | tunnel.ResetStatistic() 39 | tunnel.CloseAllConnections() 40 | 41 | runtime.GC() 42 | } 43 | 44 | //export forceGc 45 | func forceGc() { 46 | go func() { 47 | log.Infoln("[APP] request force GC") 48 | 49 | runtime.GC() 50 | }() 51 | } 52 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/ic_baseline_help_center.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/sideload/ExternalGeoip.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.sideload 2 | 3 | import android.content.Context 4 | import android.content.pm.PackageManager 5 | import com.github.kr328.clash.common.constants.Metadata 6 | import com.github.kr328.clash.common.log.Log 7 | import java.io.InputStream 8 | 9 | fun Context.readGeoipDatabaseFrom(packageName: String): ByteArray? { 10 | return try { 11 | val appInfo = packageManager.getApplicationInfo(packageName, PackageManager.GET_META_DATA) 12 | val path = appInfo.metaData.getString(Metadata.GEOIP_FILE_NAME) ?: return null 13 | 14 | createPackageContext(packageName, 0) 15 | .resources.assets.open(path).use(InputStream::readBytes) 16 | } catch (e: PackageManager.NameNotFoundException) { 17 | Log.w("Sideload geoip: $packageName not found", e) 18 | 19 | null 20 | } 21 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/PendingDao.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.* 4 | import java.util.* 5 | 6 | @Dao 7 | @TypeConverters(Converters::class) 8 | interface PendingDao { 9 | @Query("SELECT * FROM pending WHERE uuid = :uuid") 10 | suspend fun queryByUUID(uuid: UUID): Pending? 11 | 12 | @Query("DELETE FROM pending WHERE uuid = :uuid") 13 | suspend fun remove(uuid: UUID) 14 | 15 | @Query("SELECT EXISTS(SELECT 1 FROM pending WHERE uuid = :uuid)") 16 | suspend fun exists(uuid: UUID): Boolean 17 | 18 | @Query("SELECT uuid FROM pending ORDER BY createdAt") 19 | suspend fun queryAllUUIDs(): List 20 | 21 | @Insert(onConflict = OnConflictStrategy.REPLACE) 22 | suspend fun insert(pending: Pending) 23 | 24 | @Update(onConflict = OnConflictStrategy.REPLACE) 25 | suspend fun update(pending: Pending) 26 | } 27 | -------------------------------------------------------------------------------- /design/src/main/res/drawable/bg_bottom_sheet.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/02-feature-request-en.yml: -------------------------------------------------------------------------------- 1 | name: "[English] Feature Request" 2 | description: "Create a report to help us improve" 3 | title: "[Feature Request] " 4 | labels: ["enhancement"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this feature request! 10 | 11 | NOTE: Be sure to put a clear and concise title **AFTER** `[Feature Request]` in the text box above. 12 | 13 | 14 | - type: textarea 15 | id: "description" 16 | attributes: 17 | label: "Feature Description" 18 | description: | 19 | A clear and concise description of the feature. 20 | validations: 21 | required: true 22 | - type: textarea 23 | id: "additional" 24 | attributes: 25 | label: "Additional" 26 | description: | 27 | Add any other context or screenshots about the feature request here. 28 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/util/Toast.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.util 2 | 3 | import com.github.kr328.clash.design.Design 4 | import com.github.kr328.clash.design.R 5 | import com.github.kr328.clash.design.ui.ToastDuration 6 | import com.google.android.material.dialog.MaterialAlertDialogBuilder 7 | 8 | suspend fun Design<*>.showExceptionToast(message: CharSequence) { 9 | showToast(message, ToastDuration.Long) { 10 | setAction(R.string.detail) { 11 | MaterialAlertDialogBuilder(it.context) 12 | .setTitle(R.string.error) 13 | .setMessage(message) 14 | .setCancelable(true) 15 | .setPositiveButton(R.string.ok) { _, _ -> } 16 | .show() 17 | } 18 | } 19 | } 20 | 21 | suspend fun Design<*>.showExceptionToast(exception: Exception) { 22 | showExceptionToast(exception.message ?: "Unknown") 23 | } -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/ImportedDao.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.* 4 | import java.util.* 5 | 6 | @Dao 7 | @TypeConverters(Converters::class) 8 | interface ImportedDao { 9 | @Query("SELECT * FROM imported WHERE uuid = :uuid") 10 | suspend fun queryByUUID(uuid: UUID): Imported? 11 | 12 | @Query("SELECT uuid FROM imported ORDER BY createdAt") 13 | suspend fun queryAllUUIDs(): List 14 | 15 | @Insert(onConflict = OnConflictStrategy.ABORT) 16 | suspend fun insert(imported: Imported): Long 17 | 18 | @Update(onConflict = OnConflictStrategy.ABORT) 19 | suspend fun update(imported: Imported) 20 | 21 | @Query("DELETE FROM imported WHERE uuid = :uuid") 22 | suspend fun remove(uuid: UUID) 23 | 24 | @Query("SELECT EXISTS(SELECT 1 FROM imported WHERE uuid = :uuid)") 25 | suspend fun exists(uuid: UUID): Boolean 26 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/Imported.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.TypeConverters 6 | import com.github.kr328.clash.service.model.Profile 7 | import java.util.* 8 | 9 | @Entity(tableName = "imported", primaryKeys = ["uuid"]) 10 | @TypeConverters(Converters::class) 11 | data class Imported( 12 | @ColumnInfo(name = "uuid") val uuid: UUID, 13 | @ColumnInfo(name = "name") val name: String, 14 | @ColumnInfo(name = "type") val type: Profile.Type, 15 | @ColumnInfo(name = "source") val source: String, 16 | @ColumnInfo(name = "interval") val interval: Long, 17 | @ColumnInfo(name = "upload") val upload: Long, 18 | @ColumnInfo(name = "download") val download: Long, 19 | @ColumnInfo(name = "total") val total: Long, 20 | @ColumnInfo(name = "expire") val expire: Long, 21 | @ColumnInfo(name = "createdAt") val createdAt: Long, 22 | ) -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/data/Pending.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service.data 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.TypeConverters 6 | import com.github.kr328.clash.service.model.Profile 7 | import java.util.* 8 | 9 | @Entity(tableName = "pending", primaryKeys = ["uuid"]) 10 | @TypeConverters(Converters::class) 11 | data class Pending( 12 | @ColumnInfo(name = "uuid") val uuid: UUID, 13 | @ColumnInfo(name = "name") val name: String, 14 | @ColumnInfo(name = "type") val type: Profile.Type, 15 | @ColumnInfo(name = "source") val source: String, 16 | @ColumnInfo(name = "interval") val interval: Long, 17 | @ColumnInfo(name = "upload") val upload: Long, 18 | @ColumnInfo(name = "download") val download: Long, 19 | @ColumnInfo(name = "total") val total: Long, 20 | @ColumnInfo(name = "expire") val expire: Long, 21 | @ColumnInfo(name = "createdAt") val createdAt: Long = System.currentTimeMillis(), 22 | ) -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Clash 状态 4 | 正在运行 5 | 更新 %s 成功 6 | "更新 %1$s: %2$s " 7 | Clash Meta for Android 8 | 配置文件和外部资源 9 | 配置文件.yaml 10 | 外部资源文件列表 11 | 载入中 12 | 配置文件处理状态 13 | 更新成功 14 | 更新失败 15 | 配置更新服务 16 | 配置更新中 17 | 配置文件服务状态 18 | 配置文件处理结果 19 | -------------------------------------------------------------------------------- /service/src/main/res/values-zh-rHK/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Clash 狀態 4 | 正在運行 5 | 更新 %s 成功 6 | "更新 %1$s: %2$s " 7 | Clash Meta 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 Meta 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/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/res/values-ko-rKR/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Clash 상태 4 | 구성 파일 서비스 상태 5 | 구성 파일 처리 상태 6 | 구성 파일 처리 결과 7 | 업데이트 성공 8 | 업데이트 실패 9 | %s 업데이트 성공 10 | 업데이트 %1$s: %2$s 11 | 연결됨 12 | 로딩중 13 | Clash Meta for Android 14 | 구성 파일과 외부 리소스 15 | 구성 파일.yaml 16 | 외부 리소스 파일 17 | 구성 파일 업데이트 18 | 구성 파일 업데이트 중 19 | -------------------------------------------------------------------------------- /service/src/main/res/values-ja-rJP/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Clashステータス 4 | プロファイルサービスのステータス 5 | プロファイルの処理状況 6 | プロファイルプロセスの結果 7 | 正常に更新されました 8 | 更新に失敗しました 9 | %sを更新しました 10 | 更新 %1$s: %2$s 11 | 実行中 12 | 読み込み中 13 | Clash Meta for Android 14 | プロファイルと外部リソース 15 | コンフィグ.yaml 16 | 外部リソースファイル 17 | プロファイルの更新 18 | プロファイルを更新中 19 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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, StartMetaFeature, 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/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 | } -------------------------------------------------------------------------------- /design/src/main/res/layout/dialog_text_field.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 10 | 11 | 17 | 18 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /.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 | # Ignore builtin geofiles 31 | app/src/main/assets 32 | 33 | # KeyStore 34 | signing.properties 35 | *.keystore 36 | *.jks 37 | 38 | # clion cmake build 39 | cmake-build-* 40 | 41 | # local.properties 42 | local.properties 43 | 44 | 45 | # tracker 46 | tracker.properties 47 | 48 | # vscode 49 | .vscode 50 | 51 | # cxx 52 | .cxx 53 | 54 | *.hprof 55 | 56 | # firebase 57 | google-services.json 58 | 59 | # Dolphin 60 | .directory 61 | 62 | # logs 63 | *.log 64 | 65 | # MacOS 66 | .DS_Store 67 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /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 | implementation(platform("com.squareup.okhttp3:okhttp-bom:4.10.0")) 23 | 24 | // define any required OkHttp artifacts without version 25 | implementation("com.squareup.okhttp3:okhttp") 26 | implementation("com.squareup.okhttp3:logging-interceptor") 27 | } 28 | 29 | afterEvaluate { 30 | android { 31 | libraryVariants.forEach { 32 | sourceSets[it.name].kotlin.srcDir(buildDir.resolve("generated/ksp/${it.name}/kotlin")) 33 | sourceSets[it.name].java.srcDir(buildDir.resolve("generated/ksp/${it.name}/java")) 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /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/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 | SettingsDesign.Request.StartMetaFeature -> 28 | startActivity(MetaFeatureSettingsActivity::class.intent) 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /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 Meta for Android 16 | Profiles and Providers 17 | Configuration.yaml 18 | Provider Files 19 | Profile Updater 20 | Profile Updating 21 | 22 | -------------------------------------------------------------------------------- /service/src/main/res/values-ru/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | "%1$s↑\t%2$s↓" 4 | 5 | Статус Clash 6 | Статус сервиса профиля 7 | Статус обработки профиля 8 | Результат обработки профиля 9 | Успешно обновлено 10 | Не удалось обновить 11 | Обновление %s завершено 12 | Обновление %1$s: %2$s 13 | Работает 14 | Загружается 15 | Clash Meta для Android 16 | Профили и провайдеры 17 | Configuration.yaml 18 | Файлы провайдера 19 | Обновление профиля 20 | Профиль обновляется 21 | 22 | -------------------------------------------------------------------------------- /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/delegate/init.go: -------------------------------------------------------------------------------- 1 | package delegate 2 | 3 | import ( 4 | "errors" 5 | "syscall" 6 | 7 | "cfa/blob" 8 | 9 | "github.com/Dreamacro/clash/component/process" 10 | "github.com/Dreamacro/clash/log" 11 | 12 | "cfa/native/app" 13 | "cfa/native/platform" 14 | 15 | "github.com/Dreamacro/clash/component/dialer" 16 | "github.com/Dreamacro/clash/component/mmdb" 17 | "github.com/Dreamacro/clash/constant" 18 | ) 19 | 20 | var errBlocked = errors.New("blocked") 21 | 22 | func Init(home, versionName string, platformVersion int) { 23 | mmdb.LoadFromBytes(blob.GeoipDatabase) 24 | constant.SetHomeDir(home) 25 | app.ApplyVersionName(versionName) 26 | app.ApplyPlatformVersion(platformVersion) 27 | 28 | process.DefaultPackageNameResolver = func(metadata *constant.Metadata) (string, error) { 29 | src, dst := metadata.RawSrcAddr, metadata.RawDstAddr 30 | 31 | if src == nil || dst == nil { 32 | return "", process.ErrInvalidNetwork 33 | } 34 | 35 | uid := app.QuerySocketUid(metadata.RawSrcAddr, metadata.RawDstAddr) 36 | pkg := app.QueryAppByUid(uid) 37 | 38 | log.Debugln("[PKG] %s --> %s by %d[%s]", metadata.SourceAddress(), metadata.RemoteAddress(), uid, pkg) 39 | 40 | return pkg, nil 41 | } 42 | 43 | dialer.DefaultSocketHook = func(network, address string, conn syscall.RawConn) error { 44 | if platform.ShouldBlockConnection() { 45 | return errBlocked 46 | } 47 | 48 | return conn.Control(func(fd uintptr) { 49 | app.MarkSocket(int(fd)) 50 | }) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/adapter/ProxyAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.adapter 2 | 3 | import android.view.ViewGroup 4 | import androidx.recyclerview.widget.RecyclerView 5 | import com.github.kr328.clash.design.component.ProxyView 6 | import com.github.kr328.clash.design.component.ProxyViewConfig 7 | import com.github.kr328.clash.design.component.ProxyViewState 8 | 9 | class ProxyAdapter( 10 | private val config: ProxyViewConfig, 11 | private val clicked: (String) -> Unit, 12 | ) : RecyclerView.Adapter() { 13 | class Holder(val view: ProxyView) : RecyclerView.ViewHolder(view) 14 | 15 | var selectable: Boolean = false 16 | var states: List = emptyList() 17 | 18 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { 19 | return Holder(ProxyView(config.context, config)) 20 | } 21 | 22 | override fun onBindViewHolder(holder: Holder, position: Int) { 23 | val current = states[position] 24 | 25 | holder.view.apply { 26 | state = current 27 | 28 | setOnClickListener { 29 | clicked(current.proxy.name) 30 | } 31 | 32 | val isSelector = selectable 33 | 34 | isFocusable = isSelector 35 | isClickable = isSelector 36 | 37 | current.update(true) 38 | } 39 | } 40 | 41 | override fun getItemCount(): Int { 42 | return states.size 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /design/src/main/java/com/github/kr328/clash/design/model/ProfileProvider.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.design.model 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.graphics.drawable.Drawable 6 | import com.github.kr328.clash.common.compat.getDrawableCompat 7 | import com.github.kr328.clash.design.R 8 | 9 | sealed class ProfileProvider { 10 | class File(private val context: Context) : ProfileProvider() { 11 | override val name: String 12 | get() = context.getString(R.string.file) 13 | override val summary: String 14 | get() = context.getString(R.string.import_from_file) 15 | override val icon: Drawable? 16 | get() = context.getDrawableCompat(R.drawable.ic_baseline_attach_file) 17 | } 18 | 19 | class Url(private val context: Context) : ProfileProvider() { 20 | override val name: String 21 | get() = context.getString(R.string.url) 22 | override val summary: String 23 | get() = context.getString(R.string.import_from_url) 24 | override val icon: Drawable? 25 | get() = context.getDrawableCompat(R.drawable.ic_baseline_cloud_download) 26 | } 27 | 28 | class External( 29 | override val name: String, 30 | override val summary: String, 31 | override val icon: Drawable?, 32 | val intent: Intent, 33 | ) : ProfileProvider() 34 | 35 | abstract val name: String 36 | abstract val summary: String 37 | abstract val icon: Drawable? 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/util/Remote.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.util 2 | 3 | import android.os.DeadObjectException 4 | import com.github.kr328.clash.common.log.Log 5 | import com.github.kr328.clash.remote.Remote 6 | import com.github.kr328.clash.service.remote.IClashManager 7 | import com.github.kr328.clash.service.remote.IProfileManager 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.withContext 10 | import kotlin.coroutines.CoroutineContext 11 | 12 | suspend fun withClash( 13 | context: CoroutineContext = Dispatchers.IO, 14 | block: suspend IClashManager.() -> T 15 | ): T { 16 | while (true) { 17 | val remote = Remote.service.remote.get() 18 | val client = remote.clash() 19 | 20 | try { 21 | return withContext(context) { client.block() } 22 | } catch (e: DeadObjectException) { 23 | Log.w("Remote services panic") 24 | 25 | Remote.service.remote.reset(remote) 26 | } 27 | } 28 | } 29 | 30 | suspend fun withProfile( 31 | context: CoroutineContext = Dispatchers.IO, 32 | block: suspend IProfileManager.() -> T 33 | ): T { 34 | while (true) { 35 | val remote = Remote.service.remote.get() 36 | val client = remote.profile() 37 | 38 | try { 39 | return withContext(context) { client.block() } 40 | } catch (e: DeadObjectException) { 41 | Log.w("Remote services panic") 42 | 43 | Remote.service.remote.reset(remote) 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /service/src/main/java/com/github/kr328/clash/service/RemoteService.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.service 2 | 3 | import android.content.Intent 4 | import android.os.IBinder 5 | import com.github.kr328.clash.service.remote.IClashManager 6 | import com.github.kr328.clash.service.remote.IRemoteService 7 | import com.github.kr328.clash.service.remote.IProfileManager 8 | import com.github.kr328.clash.service.remote.wrap 9 | import com.github.kr328.clash.service.util.cancelAndJoinBlocking 10 | 11 | class RemoteService : BaseService(), IRemoteService { 12 | private val binder = this.wrap() 13 | 14 | private var clash: ClashManager? = null 15 | private var profile: ProfileManager? = null 16 | private var clashBinder: IClashManager? = null 17 | private var profileBinder: IProfileManager? = null 18 | 19 | override fun onCreate() { 20 | super.onCreate() 21 | 22 | clash = ClashManager(this) 23 | profile = ProfileManager(this) 24 | clashBinder = clash?.wrap() as IClashManager? 25 | profileBinder = profile?.wrap() as IProfileManager? 26 | } 27 | 28 | override fun onDestroy() { 29 | super.onDestroy() 30 | 31 | clash?.cancelAndJoinBlocking() 32 | profile?.cancelAndJoinBlocking() 33 | } 34 | 35 | override fun onBind(intent: Intent?): IBinder { 36 | return binder 37 | } 38 | 39 | override fun clash(): IClashManager { 40 | return clashBinder!! 41 | } 42 | 43 | override fun profile(): IProfileManager { 44 | return profileBinder!! 45 | } 46 | } -------------------------------------------------------------------------------- /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 | val upload: Long, 22 | var download: Long, 23 | val total: Long, 24 | val expire: Long, 25 | 26 | 27 | val updatedAt: Long, 28 | val imported: Boolean, 29 | val pending: Boolean, 30 | ) : Parcelable { 31 | enum class Type { 32 | File, Url, External 33 | } 34 | 35 | override fun writeToParcel(parcel: Parcel, flags: Int) { 36 | Parcelizer.encodeToParcel(serializer(), parcel, this) 37 | } 38 | 39 | override fun describeContents(): Int { 40 | return 0 41 | } 42 | 43 | companion object CREATOR : Parcelable.Creator { 44 | override fun createFromParcel(parcel: Parcel): Profile { 45 | return Parcelizer.decodeFromParcel(serializer(), parcel) 46 | } 47 | 48 | override fun newArray(size: Int): Array { 49 | return arrayOfNulls(size) 50 | } 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/github/kr328/clash/log/LogcatCache.kt: -------------------------------------------------------------------------------- 1 | package com.github.kr328.clash.log 2 | 3 | import androidx.collection.CircularArray 4 | import com.github.kr328.clash.core.model.LogMessage 5 | import kotlinx.coroutines.sync.Mutex 6 | import kotlinx.coroutines.sync.withLock 7 | 8 | class LogcatCache { 9 | data class Snapshot(val messages: List, val removed: Int, val appended: Int) 10 | 11 | private val array = CircularArray(CAPACITY) 12 | private val lock = Mutex() 13 | 14 | private var removed: Int = 0 15 | private var appended: Int = 0 16 | 17 | suspend fun append(msg: LogMessage) { 18 | lock.withLock { 19 | if (array.size() >= CAPACITY) { 20 | array.removeFromStart(1) 21 | 22 | removed++ 23 | appended-- 24 | } 25 | 26 | array.addLast(msg) 27 | 28 | appended++ 29 | } 30 | } 31 | 32 | suspend fun snapshot(full: Boolean): Snapshot? { 33 | return lock.withLock { 34 | if (!full && removed == 0 && appended == 0) { 35 | return@withLock null 36 | } 37 | 38 | Snapshot( 39 | List(array.size()) { array[it] }, 40 | removed, 41 | if (full) array.size() + appended else appended 42 | ).also { 43 | removed = 0 44 | appended = 0 45 | } 46 | } 47 | } 48 | 49 | companion object { 50 | const val CAPACITY = 128 51 | } 52 | } 53 | --------------------------------------------------------------------------------