Compare commits

..

55 Commits

Author SHA1 Message Date
Mykola Baibuz
f1ec9c5c75 use stop method for protocol disconnecect 2025-08-27 14:59:21 +03:00
Mykola Baibuz
f2a9940147 change disconnect from vpn order 2025-08-27 02:43:38 +03:00
Mykola Baibuz
4f17085c99 wait for response from service before object destroy 2025-08-27 00:28:23 +03:00
Mykola Baibuz
fdd2c12647 fix: add allow traffic rules on killswitch disable 2025-08-26 13:43:59 +03:00
Mykola Baibuz
066b74887e add disconnectSlots method 2025-08-24 15:25:58 +03:00
Mykola Baibuz
eb95ec7cbf disconnect signals on exit before VPN disconnect 2025-08-24 15:07:52 +03:00
Mykola Baibuz
e2492abb77 disconnect all signals from vpnconnection on exit 2025-08-24 14:54:02 +03:00
Mykola Baibuz
e0ecfc12a9 use checktimer only for iOS 2025-08-24 14:32:01 +03:00
Mykola Baibuz
b1b503b7c6 add interruption request on vpnConnectionThread 2025-08-24 14:14:11 +03:00
Mykola Baibuz
3d573d5977 disconnect all signals from vpnconnection on exit 2025-08-24 13:36:18 +03:00
Mykola Baibuz
fc99da1432 Revert "Don't terminate VPN thread on Linux"
This reverts commit 20e4ea2d4a.
2025-08-22 17:22:45 +03:00
Mykola Baibuz
20e4ea2d4a Don't terminate VPN thread on Linux 2025-08-22 17:05:31 +03:00
Mykola Baibuz
f0b3c16880 this object will be deleted at app close 2025-08-22 17:00:21 +03:00
Mykola Baibuz
2ac62027e8 fix: remove second disconnect from VPN on app close 2025-08-22 12:17:51 +03:00
Mykola Baibuz
43c3518f9e cleanup trace info 2025-08-21 20:56:02 +03:00
Mykola Baibuz
8b86c482d2 cleanup unused variable 2025-08-21 20:16:06 +03:00
Mykola Baibuz
b4efae8edd Refactor IpcClient::Interface access logic 2025-08-21 19:56:04 +03:00
Mykola Baibuz
03bc7d6293 set timelimit for flushDns 2025-08-21 16:51:03 +03:00
Mykola Baibuz
8390a270ca add more trace info 2025-08-21 13:40:25 +03:00
Mykola Baibuz
faa832b152 add trace info 2025-08-21 12:56:19 +03:00
Mykola Baibuz
ed228643cf fix: typo in VpnConnection destructor 2025-08-21 12:29:37 +03:00
Mykola Baibuz
ef901c2149 fix: app freeze on quit 2025-08-20 21:46:52 +03:00
Nethius
16d92ddb7c fix: UI fixes after merge with d20ed4a (#1779)
* fix: ui fixes after merge with d20ed4a

* update OpenVPN settings page

* chore: page settings dns margins

---------

Co-authored-by: Cyril Anisimov <CyAn84@gmail.com>
2025-08-11 13:40:28 +08:00
Cyril Anisimov
e9d4fd8482 fix checkbox switch (#1777) 2025-08-10 11:13:58 +08:00
Yaroslav
9fdcf5ab13 feat: macos with network extension Implementation (#1468)
* There's a common issue of building iOS apps on Qt 6.8 because of new introduced ffmpeg dependency in multimedia Qt package
ref: https://community.esri.com/t5/qt-maps-sdk-questions/build-failure-on-ios-with-qt-6-8/m-p/1548701#M5339

* Cmake related changes

* Source code changes

* Various entitlements

* Ci-cd config update

* Resources changes

* Submodules updated

* Remove me

* QtWidget exclusion omitted

* Distribution errors fixed

* Outdated files deleted

* macos_ne cmake fixed

* fix: update provisioning profile specifiers for macOS network extension

* fix: update provisioning profile specifiers and code sign flags for macOS build

* Revert me
(temporary 3rd-build commit pointer)

* fix: Welcome screen fix

* fix: ci/cd hanging forever fix

* fix: Fixed error popup on macos on file save

* refactor: rename networkextension target to AmneziaVPNNetworkExtension in macos build configuration

* feat: add autostart support for Mac App Store builds on macOS

Fixes: QA-8

* feat: add debug logging to Autostart functionality on macOS

* Revert "feat: add autostart support for Mac App Store builds on macOS"

This reverts commit 3bd25656fb.

* feat: add platform-specific close window behavior for macOS App Store build with Network Extension

Closes: QA-12

* When the application starts with "Start minimized" enabled on macOS (especially the
sandboxed App-Store build compiled with MACOS_NE), fully hiding the window prevents it
from being restored by clicking the Dock icon. The proper behaviour is to start the
window in the *minimized* state instead. That way the window is still part of the
window list and the system automatically brings it back when the user clicks the Dock
icon, replicating the native experience.

On the other platforms we keep the old behaviour (hide the window completely and rely
on the tray icon), therefore we switch at runtime by checking the current OS.

Closes: QA-7

Closes: QA-8

* Revert "When the application starts with "Start minimized" enabled on macOS (especially the"

This reverts commit 7b0d17987c.

* feat: MACOS_NE systray menu support

* feat: add macOS notification handler and install event filter on main window

* feat: implement custom close behavior for Amnezia application on different platforms

* fix: update provisioning profile specifiers for macos builds

* fix: Fatal error in logs

CLI-216

* fix: disabled unavailable on macos ne service logs

* fix: dock icon now hides only when window is closed; menubar icon shows always

Initial state of the docker icon to be presented follows "Start minimized" setting in app settings.

* temp-fix: temporary disable all OpenVPN options of VPN on MACOS_NE since it's not working yet.

* fix: build script updated

* feat: add macOS NE build workflow to GitHub Actions

* fix: Not working Auto start toggle is hidden

* fix: Log spamming during xray connection fixed

* 3rd-prebuild points to commit that stores macos_ne universal binaries.

* fix: missing native dependency on linking stage fixed

* chore: update link to submodule

---------

Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com>
2025-08-10 11:12:19 +08:00
serj95reg
a6e6de33c8 feat: updated xray version in dockerfile to 25.8.3 (#1771) 2025-08-08 10:34:51 +08:00
Mitternacht822
53c7fd4d81 fix: android build (#1768)
* added signal-slot connection between corecontroller and systemtraynofificationhandler updating websiteurl

* cleared up the commented lines

* fixed andorid includes for systemtraynotificationhandler
2025-08-07 11:12:09 +08:00
Nethius
2608ea4367 chore: fix typo (#1769) 2025-08-06 11:00:43 +08:00
Cyril Anisimov
d20ed4ad01 refactoring: improved stability of focus controller (#1464)
* change position view mode

* remove `parentFlickable` from `PageShare`

* replace `FlickableType` with `ListViewType` in `PageSettings`

* reorganize `PageSettingsAbout` for improved structure

* replace `Flickable` with `ListViewType` in drawer in `PageSettingsApiNativeConfigs`

* replace `FlickableType` with `ListViewType` in `PageSettingsApplication` and update layout structure

* replace `FlickableType` with `ListViewType` in `PageSettingsAppSplitTunneling` and adjust layout for better structure

* replace `FlickableType` with `ListViewType` in `PageSettingsBackup`

* replace `FlickableType` with `ListViewType` in `PageSettingsConnection`

* replace `FlickableType` with `ListViewType` in `PageSettingsDns`

* replace `FlickableType` with `ListViewType` in `PageSettingsLogging`

* replace `FlickableType` with `ListViewType` in `PageSettingsServerData`

* update structure of `PageSettingsServerProtocol`

* update `PageSettingsServersList`

* replace `ListView` with `ListViewType` in `PageSettingsSplitTunneling`

* replace `FlickableType` with `ListViewType` in `PageServiceDnsSettings`

* update `PageServiceSftpSettings`

* update `PageServiceSocksProxySettings`

* replace `FlickableType` with `ListViewType` in `PageServiceTorWebsiteSettings`

* replace `FlickableType` with `ListViewType` in `PageSetupWizardApiServiceInfo`

* update `PageSetupWizardApiServicesList`

* replace `ListView` with `ListViewType` in `PageSetupWizardConfigSource`

* replace `ListView` with `ListViewType` in `PageSetupWizardCredentials`

* replace `FlickableType` with `ListViewType` in `PageSetupWizardEasy`

* replace `FlickableType` with `ListViewType` in `PageSetupWizardInstalling`

* replace `ListView` with `ListViewType` in `PageSetupWizardProtocols`

* replace `FlickableType` with `ListViewType` in `PageSetupWizardProtocolSettings`

* replace `FlickableType` with `ListViewType` in `PageSetupWizardTextKey`

* replace `FlickableType` with `ListViewType` in `PageSetupWizardViewConfig`

* update `PageProtocolAwgClientSettings`

* update `PageProtocolAwgSettings`

* replace `FlickableType` with `ListViewType` in `PageProtocolCloakSettings`

* replace `FlickableType` with `ListViewType` in `PageProtocolRaw`

* replace `FlickableType` with `ListViewType` in `PageProtocolShadowSocksSettings`

* replace `FlickableType` with `ListViewType` in `PageProtocolWireGuardClientSettings`

* replace `FlickableType` with `ListViewType` in `PageProtocolWireGuardSettings`

* replace `FlickableType` with `ListViewType` in `PageProtocolXraySettings`

* replace `FlickableType` with `ListViewType` in `PageShareFullAccess`

* replace `FlickableType` with `ListViewType` in `PageDeinstalling`

* update `PageDevMenu`

* remove `Flickable` references in `LabelWithButtonType`

* remove useless key navigation handlers from `ListViewType`

* replace `ListView` with `ListViewType` in `ListViewWithRadioButtonType.qml` and remove unnecessary properties

* remove references to `Flickable` in `TextAreaType.qml`

* remove references to `Flickable` in `TextAreaWithFooterType`

* remove references to `FlickableType` in `TextFieldWithHeaderType`

* remove references to `FlickableType` in `SwitcherType`

* remove references to `FlickableType` in `CheckBoxType`

* remove references to `FlickableType` in `CardWithIconsType.qml`

* remove references to `FlickableType` in `BasicButtonType.qml`

* update `ServersListView`

* update `SettingsContainersListView`

* update `InstalledAppsDrawer`

* update `SelectLanguageDrawer`

* update `HomeContainersListView`

* update `HomeSplitTunnelingDrawer`

* fix `PageSetupWizardApiServicesList`

---------

Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com>
2025-08-06 10:35:51 +08:00
KsZnak
eae2936449 Update README links.md [no ci]
Update README links.md
2025-08-04 19:35:45 +01:00
KsZnak
da8ad1f6ba UTM added.md [no ci]
Update README_RU.md
2025-08-04 19:34:12 +01:00
Mitternacht822
5472347969 feature: added warning label when config files have changed in premium configuration files menu (#1718)
* added warning label when config files have changed in premium configuration files display

* moved warning display from PageSettingsApiNativeConfigs.qml to PageSettingsApiServerInfo.qml
2025-08-04 14:13:22 +08:00
Mitternacht822
a43f7a6926 feat: added vpn key to subscription settings page (#1717)
* added subscription key display element to subscription management page

* refactrored KeySubscription item to a new page

* minor fix

* changed PageShareDrawer into PageShareConnection

* added back button

* Removed deprecated ShareConnectionDrawer and migrated to PageShareConnection

* fixed issue when show-connection settings button was not working

* deleted empty lines

* minor fix
2025-08-01 21:02:12 +08:00
Cyril Anisimov
47f917de0b refactoring: change logs time to UTC (#1578)
* update logger to show utc

* add logger to `FocusController`

* add utc timestamps to android logs
2025-08-01 11:56:16 +08:00
Cyril Anisimov
dbeb7edd7a refactor: update ScrollBar policy to use AsNeeded for better usability (#1579)
* refactor: update `ScrollBarType` policy to use
`AsNeeded` for better usability

* add selecting of location settings with Enter

* add handlers to enter push
2025-08-01 11:56:02 +08:00
Mitternacht822
6cede712f5 fix: backup contains platform specific variables (#1646)
* fixed issue with restoring wrong platform specific variables in backup

* fixed wrong line

* fixed issue when restong app split tunneling mode not intended for windows platform

* added field containing application platform to backup file, added feature to clear appsSplitTunneling list from backup file if backup was made on other platform
2025-08-01 11:54:58 +08:00
Mitternacht822
d328739192 fix: add update model after clear profile (#1674)
* fixed issue when ui was not getting update about clearing profile cache right after it

* fixed the problem of not clearing the profile

* refactored reload function in protolocolsModel

* refactored the issue with signal connect in corecontroller
2025-08-01 11:52:11 +08:00
Mitternacht822
d15c0bd962 fix: fixed system tray open site link (#1686)
* added signal-slot connection between corecontroller and systemtraynofificationhandler updating websiteurl

* cleared up the commented lines
2025-08-01 11:50:31 +08:00
Mitternacht822
d53c794936 fix: fixed language load after settings reset (#1735) 2025-08-01 11:47:43 +08:00
Mitternacht822
e5dcb25a4a fix: removed the ability to change location while making connection (#1736) 2025-08-01 11:45:19 +08:00
Mitternacht822
f9002b4f43 refactoring: made start-minimized-option available only when autostart-option is truned on (#1740) 2025-08-01 11:38:36 +08:00
Nethius
0531508a75 feat: added 'clear site list' button (#1747) (#1753)
* feat: added 'clear site list' button (#1747)

* chore: rename 'Export/Import Sites' to 'Additional options'

---------

Co-authored-by: MrMirDan <58086007+MrMirDan@users.noreply.github.com>
2025-08-01 11:37:56 +08:00
Mitternacht822
174e85a20a fix: not restoring parameters for open vpn after scanning server (#1759)
* added lines for restoring settings when scanning server for OpenVPN, OpenVPN over Cloak and OpenVPN over SS protocols

* minor fix

* added functionality to restore config for multiprotocol configsCloak and Shadowsocks
2025-08-01 11:36:52 +08:00
MrMirDan
e9abb6f1e2 fix: mirror links (#1760)
* Instructions links

* amnezia free feature link

* trying fix api instructions page issue

* androidTV link fix

* tv link fix 2
2025-08-01 11:36:30 +08:00
Nethius
5be44f9596 chore: bump version (#1757)
* chore: update link to submodule

* chore: bump version
2025-07-29 12:20:43 +08:00
vladimir.kuznetsov
90efaaff92 chore: bump version 2025-07-29 12:19:54 +08:00
vladimir.kuznetsov
99b554e7c3 chore: update link to submodule 2025-07-29 12:19:27 +08:00
Nethius
ac0ce8a6f6 chore: bump version (#1746) 2025-07-25 23:21:18 +08:00
Yaroslav
9f9da885b7 fix: bundle version added, icon returned (#1745) 2025-07-25 23:03:11 +08:00
Nethius
f51fd2bf3e chore: update link to submodule (#1738) 2025-07-24 10:13:14 +08:00
Nethius
c8378fd32d chore: update link to submodule (#1733) 2025-07-22 19:50:57 +08:00
Nethius
d767214f10 chore: fixed amneziavpn-service version (#1726) 2025-07-17 15:22:21 +08:00
Nethius
e027c504ae chore: bump version and add version to amneziavpn-service (#1725) 2025-07-16 13:49:29 +08:00
MrMirDan
669a95d975 chore: updated amnezia_ru_RU.ts (#1720)
* Update amneziavpn_ru_RU.ts

* Update amneziavpn_ru_RU.ts

* Update amneziavpn_ru_RU.ts

* Deleted corrupted ru translation

* Updated amneziavpn_ru_RU.ts

* Saved amneziavpn_ru_RU.ts

* Rewrite some back on english

* Rewrite small issues

* Rewrite another small issues

* Create deploy_mod.yml

Modificated deploy.yml - removed Linux, IOS and MacOS jobs
Made just for test and learning

* some changes

* deleted my uneccessary file

* new translations
2025-07-16 13:26:49 +08:00
Nethius
a96df5d518 fix: temporarily removed vless for api native configs (#1724) 2025-07-16 13:26:19 +08:00
176 changed files with 8763 additions and 7438 deletions

View File

@@ -358,7 +358,7 @@ jobs:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '15.4.0'
xcode-version: '16.2.0'
- name: 'Install Qt'
uses: jurplel/install-qt-action@v3
@@ -402,6 +402,67 @@ jobs:
path: deploy/build/client/AmneziaVPN.app
retention-days: 7
Build-MacOS-NE:
runs-on: macos-latest
env:
QT_VERSION: 6.8.3
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '16.2.0'
- name: 'Install Qt'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'mac'
target: 'desktop'
arch: 'clang_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Get sources'
uses: actions/checkout@v4
with:
submodules: 'true'
fetch-depth: 10
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
run: |
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
bash deploy/build_macos_ne.sh
- name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_MacOS_unpacked
path: deploy/build/client/AmneziaVPN.app
retention-days: 7
# ------------------------------------------------------
Build-Android:

View File

@@ -1,8 +1,9 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.8.9.2)
project(${PROJECT} VERSION 4.8.8.3
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
DESCRIPTION "AmneziaVPN"
HOMEPAGE_URL "https://amnezia.org/"
)
@@ -11,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2089)
set(APP_ANDROID_VERSION_CODE 2092)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")
@@ -31,13 +32,19 @@ set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(APPLE AND NOT IOS)
set(CMAKE_OSX_ARCHITECTURES "x86_64")
if(APPLE)
if(IOS)
set(CMAKE_OSX_ARCHITECTURES "arm64")
elseif(MACOS_NE)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64")
else()
set(CMAKE_OSX_ARCHITECTURES "x86_64")
endif()
endif()
add_subdirectory(client)
if(NOT IOS AND NOT ANDROID)
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
add_subdirectory(service)
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)

View File

@@ -9,17 +9,17 @@
### [English]([https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md](https://github.com/amnezia-vpn/amnezia-client/tree/dev?tab=readme-ov-file#)) | [Русский](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md)
[Amnezia](https://amnezia.org) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
[Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org)
### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
### [Website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
> [!TIP]
> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org ).
> If the [Amnezia website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror).
<a href="https://amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
<a href="https://amnezia.org/en/downloads?utm_source=github&utm_campaign=amnezia_button-readme-en"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/en/downloads&utm_source=github&utm_campaign=amnezia_button-readme-en-mirrow"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)

View File

@@ -6,16 +6,16 @@
[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
[AmneziaVPN](https://amnezia.org) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
[AmneziaVPN](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org)
### [Сайт](https://amnezia.org) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
### [Сайт](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
> [!TIP]
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org).
> Если [сайт Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror).
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/ru/downloads&utm_source=github&utm_campaign=amnezia_button-readme-ru-mirror"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)

View File

@@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
project(${PROJECT})
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen")
set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen")
@@ -53,6 +52,9 @@ endif()
qt_standard_project_setup()
qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
target_include_directories(${PROJECT} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
)
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
@@ -110,6 +112,15 @@ include_directories(
${CMAKE_CURRENT_BINARY_DIR}
)
if(MACOS_NE)
message("MACOS_NE is ON")
add_definitions(-DQ_OS_MAC)
add_definitions(-DMACOS_NE)
message("Add macros for MacOS Network Extension")
else()
message("MACOS_NE is OFF")
endif()
include_directories(mozilla)
include_directories(mozilla/shared)
include_directories(mozilla/models)
@@ -139,7 +150,7 @@ if(WIN32)
endif()
if(APPLE)
cmake_policy(SET CMP0099 OLD)
cmake_policy(SET CMP0099 NEW)
cmake_policy(SET CMP0114 NEW)
if(NOT BUILD_OSX_APP_IDENTIFIER)
@@ -158,7 +169,6 @@ if(APPLE)
set(CMAKE_XCODE_GENERATE_SCHEME FALSE)
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${BUILD_VPN_DEVELOPMENT_TEAM})
set(CMAKE_XCODE_ATTRIBUTE_GROUP_ID_IOS ${BUILD_IOS_GROUP_IDENTIFIER})
endif()
if(LINUX AND NOT ANDROID)
@@ -166,8 +176,7 @@ if(LINUX AND NOT ANDROID)
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux)
endif()
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
message("Client desktop build")
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
add_compile_definitions(AMNEZIA_DESKTOP)
endif()
@@ -178,7 +187,9 @@ endif()
if(IOS)
include(cmake/ios.cmake)
include(cmake/ios-arch-fixup.cmake)
elseif(APPLE AND NOT IOS)
elseif(APPLE AND MACOS_NE)
include(cmake/macos_ne.cmake)
elseif(APPLE)
include(cmake/osxtools.cmake)
include(cmake/macos.cmake)
endif()
@@ -199,7 +210,7 @@ elseif(APPLE AND NOT IOS)
set(DEPLOY_PLATFORM_PATH "macos")
endif()
if(NOT IOS AND NOT ANDROID)
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
add_custom_command(
TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
@@ -214,7 +225,6 @@ if(NOT IOS AND NOT ANDROID)
$<TARGET_FILE_DIR:${PROJECT}>
COMMAND_EXPAND_LISTS
)
endif()
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})

View File

@@ -12,6 +12,7 @@
#include <QTextDocument>
#include <QTimer>
#include <QTranslator>
#include <QEvent>
#include "logger.h"
#include "ui/controllers/pageController.h"
@@ -21,6 +22,8 @@
#include "platforms/ios/QRCodeReaderBase.h"
#include "protocols/qml_register_protocols.h"
#include <QtQuick/QQuickWindow> // for QQuickWindow
#include <QWindow> // for qobject_cast<QWindow*>
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
{
@@ -48,8 +51,19 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_C
AmneziaApplication::~AmneziaApplication()
{
if (m_vpnConnection) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::QueuedConnection);
QThread::msleep(2000);
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectSlots", Qt::QueuedConnection);
}
m_vpnConnectionThread.requestInterruption();
m_vpnConnectionThread.quit();
m_vpnConnectionThread.wait(3000);
if (!m_vpnConnectionThread.wait(3000)) {
m_vpnConnectionThread.terminate();
m_vpnConnectionThread.wait(500);
}
if (m_engine) {
QObject::disconnect(m_engine, 0, 0, 0);
@@ -63,15 +77,28 @@ void AmneziaApplication::init()
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
QObject::connect(
m_engine, &QQmlApplicationEngine::objectCreated, this,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
},
Qt::QueuedConnection);
m_engine, &QQmlApplicationEngine::objectCreated, this,
[this, url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl) {
QCoreApplication::exit(-1);
return;
}
// install filter on main window
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
win->installEventFilter(this);
win->show();
}
},
Qt::QueuedConnection);
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
#ifdef MACOS_NE
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", true);
#else
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", false);
#endif
m_vpnConnection.reset(new VpnConnection(m_settings));
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();
@@ -167,7 +194,7 @@ bool AmneziaApplication::parseCommands()
QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" };
m_parser.addOption(c_cleanup);
m_parser.process(*this);
if (m_parser.isSet(c_cleanup)) {
@@ -179,9 +206,8 @@ bool AmneziaApplication::parseCommands()
return true;
}
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
void AmneziaApplication::startLocalServer()
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
void AmneziaApplication::startLocalServer() {
const QString serverName("AmneziaVPNInstance");
QLocalServer::removeServer(serverName);
@@ -198,6 +224,22 @@ void AmneziaApplication::startLocalServer()
}
#endif
bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::Close) {
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
quit();
#else
if (m_coreController && m_coreController->pageController()) {
m_coreController->pageController()->hideMainWindow();
}
#endif
return true; // eat the close
}
// call base QObject::eventFilter
return QObject::eventFilter(watched, event);
}
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
{
return m_engine;

View File

@@ -7,9 +7,9 @@
#include <QQmlContext>
#include <QThread>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication>
#include <QGuiApplication>
#else
#include <QApplication>
#include <QApplication>
#endif
#include <QClipboard>
@@ -20,9 +20,9 @@
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#define AMNEZIA_BASE_CLASS QGuiApplication
#define AMNEZIA_BASE_CLASS QGuiApplication
#else
#define AMNEZIA_BASE_CLASS QApplication
#define AMNEZIA_BASE_CLASS QApplication
#endif
class AmneziaApplication : public AMNEZIA_BASE_CLASS
@@ -37,7 +37,7 @@ public:
void loadFonts();
bool parseCommands();
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
void startLocalServer();
#endif
@@ -60,6 +60,8 @@ private:
QThread m_vpnConnectionThread;
QNetworkAccessManager *m_nam;
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
};
#endif // AMNEZIA_APPLICATION_H

View File

@@ -10,6 +10,8 @@ import java.nio.channels.FileChannel
import java.nio.channels.FileLock
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.ZonedDateTime
import java.time.ZoneOffset
import java.util.concurrent.locks.ReentrantLock
import org.amnezia.vpn.util.Log.Priority.D
import org.amnezia.vpn.util.Log.Priority.E
@@ -135,8 +137,8 @@ object Log {
}
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
val date = LocalDateTime.now().format(dateTimeFormat)
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
val utcDate = ZonedDateTime.now(ZoneOffset.UTC).format(dateTimeFormat)
return "${utcDate}Z ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
"$tag: $msg\n"
}

View File

@@ -27,9 +27,15 @@ if(WIN32)
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libcrypto.lib")
endif()
elseif(APPLE AND NOT IOS)
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
if(MACOS_NE)
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libssh.a")
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libz.a")
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/universal2")
else()
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
endif()
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/macos/include")
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libssl.a")
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a")

View File

@@ -14,11 +14,15 @@ set(LIBS ${LIBS}
${FW_SECURITY}
${FW_COREWLAN}
${FW_NETWORK}
${FW_USERNOTIFICATIONS}
${FW_USER_NOTIFICATIONS}
${FW_NETWORK_EXTENSION}
)
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
set_target_properties(${PROJECT} PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_SHORT_VERSION_STRING "${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}"
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
)
set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE)
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
@@ -31,6 +35,8 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.mm
)
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
set(MACOSX_BUNDLE_ICON_FILE app.icns)
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
@@ -49,4 +55,3 @@ execute_process(
)
message("OSX_SDK_PATH is: ${OSX_SDK_PATH}")

168
client/cmake/macos_ne.cmake Normal file
View File

@@ -0,0 +1,168 @@
message("Client ==> MacOS NE build")
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
enable_language(OBJC)
enable_language(Swift)
find_package(Qt6 REQUIRED COMPONENTS ShaderTools Widgets)
# Link Qt Widgets for QWidget, QMenu, QAction etc.
set(LIBS ${LIBS} Qt6::ShaderTools Qt6::Widgets)
find_library(FW_AUTHENTICATIONSERVICES AuthenticationServices)
find_library(FW_AVFOUNDATION AVFoundation)
find_library(FW_FOUNDATION Foundation)
find_library(FW_STOREKIT StoreKit)
find_library(FW_SERVICEMGMT ServiceManagement)
find_library(FW_USERNOTIFICATIONS UserNotifications)
find_library(FW_NETWORKEXTENSION NetworkExtension)
set(LIBS ${LIBS}
${FW_AUTHENTICATIONSERVICES}
${FW_AVFOUNDATION}
${FW_FOUNDATION}
${FW_STOREKIT}
${FW_SERVICEMGMT}
${FW_USERNOTIFICATIONS}
${FW_NETWORKEXTENSION}
)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
)
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
)
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
set(MACOSX_BUNDLE_ICON_FILE app.icns)
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
set(SOURCES ${SOURCES} ${ICON_FILE})
target_include_directories(${PROJECT} PRIVATE
${Qt6Gui_PRIVATE_INCLUDE_DIRS}
${Qt6Widgets_PRIVATE_INCLUDE_DIRS}
)
set_target_properties(${PROJECT} PROPERTIES
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Info.plist.in
MACOSX_BUNDLE_ICON_FILE "AppIcon"
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN"
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/macos/app/app.entitlements"
XCODE_ATTRIBUTE_MARKETING_VERSION "${APPLE_PROJECT_VERSION}"
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPN"
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "AmneziaVPN"
XCODE_GENERATE_SCHEME TRUE
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY "NO"
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY "YES"
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "11.0"
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../Frameworks"
XCODE_EMBED_APP_EXTENSIONS AmneziaVPNNetworkExtension
)
if(DEPLOY)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr macos.org.amnezia.AmneziaVPN"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev macos.org.amnezia.AmneziaVPN"
)
else()
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
endif()
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "AmneziaVPN-Swift.h"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTEROP_MODE "objcxx"
)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
)
target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_compile_options(${PROJECT} PRIVATE
-DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\"
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
)
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources)
target_sources(${PROJECT} PRIVATE
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
${CLIENT_ROOT_DIR}/platforms/ios/LogController.swift
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
)
target_sources(${PROJECT} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
add_subdirectory(macos/networkextension)
add_dependencies(${PROJECT} AmneziaVPNNetworkExtension)
get_target_property(QtCore_location Qt6::Core LOCATION)
message("QtCore_location")
message(${QtCore_location})
get_filename_component(QT_BIN_DIR_DETECTED "${QtCore_location}/../../../../../bin" ABSOLUTE)
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework"
)
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos)
target_link_libraries("AmneziaVPNNetworkExtension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework")
add_custom_command(TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks
COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete
COMMAND /usr/bin/codesign --force --sign "Apple Distribution"
"$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework/Versions/Current/OpenVPNAdapter"
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Signing OpenVPNAdapter framework"
)

View File

@@ -39,7 +39,7 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
)
if(NOT IOS)
if(NOT IOS AND NOT MACOS_NE)
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.h
)
@@ -89,12 +89,26 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
)
if(NOT IOS)
if(NOT IOS AND NOT MACOS_NE)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.cpp
)
endif()
# Include native macOS platform helpers (dock/status-item)
if(APPLE AND NOT IOS)
list(APPEND HEADERS
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.h
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.h
${CLIENT_ROOT_DIR}/ui/macos_util.h
)
list(APPEND SOURCES
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.mm
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.mm
${CLIENT_ROOT_DIR}/ui/macos_util.mm
)
endif()
if(NOT ANDROID)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/ui/notificationhandler.cpp

View File

@@ -131,7 +131,7 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
// no redirect-gateway
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
// Prevent ipv6 leak
#endif

View File

@@ -8,7 +8,7 @@
#include <QTemporaryFile>
#include <QThread>
#include <qtimer.h>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
#include <QGuiApplication>
#else
#include <QApplication>
@@ -24,7 +24,7 @@ SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, const QShar
QString SshConfigurator::convertOpenSShKey(const QString &key)
{
#ifndef Q_OS_IOS
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels);
@@ -67,9 +67,10 @@ QString SshConfigurator::convertOpenSShKey(const QString &key)
#endif
}
// DEAD CODE.
void SshConfigurator::openSshTerminal(const ServerCredentials &credentials)
{
#ifndef Q_OS_IOS
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
QProcess *p = new QProcess();
p->setProcessChannelMode(QProcess::SeparateChannels);
@@ -101,7 +102,7 @@ QProcessEnvironment SshConfigurator::prepareEnv()
pathEnvVar.clear();
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;");
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;");
#elif defined(Q_OS_MACX)
#elif defined(Q_OS_MACX) && !defined(MACOS_NE)
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
#endif

View File

@@ -261,6 +261,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
return true;
#elif defined(Q_OS_IOS)
// Standard iOS build (without Network Extension limitations)
switch (c) {
case DockerContainer::WireGuard: return true;
case DockerContainer::OpenVpn: return true;
@@ -269,7 +270,23 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
case DockerContainer::Cloak: return true;
case DockerContainer::SSXray: return true;
// case DockerContainer::ShadowSocks: return true;
default: return false;
default:
return false;
}
#elif defined(MACOS_NE)
// macOS build using Network Extension hide OpenVPN-based containers
switch (c) {
case DockerContainer::WireGuard: return true;
case DockerContainer::Awg: return true;
case DockerContainer::Xray: return true;
case DockerContainer::SSXray: return true;
case DockerContainer::OpenVpn:
case DockerContainer::Cloak:
case DockerContainer::ShadowSocks:
return false;
default:
return false;
}
#elif defined(Q_OS_MAC)
switch (c) {

View File

@@ -120,6 +120,9 @@ void CoreController::initControllers()
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
&ConnectionController::onCurrentContainerUpdated); // TODO remove this
connect(m_installController.get(), &InstallController::profileCleared,
m_protocolsModel.get(), &ProtocolsModel::updateModel);
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
@@ -242,7 +245,10 @@ void CoreController::initNotificationHandler()
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
&ConnectionController::closeConnection);
connect(this, &CoreController::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
#endif
auto* trayHandler = qobject_cast<SystemTrayNotificationHandler*>(m_notificationHandler.get());
connect(this, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
#endif
}
void CoreController::updateTranslator(const QLocale &locale)
@@ -279,6 +285,7 @@ void CoreController::updateTranslator(const QLocale &locale)
m_engine->retranslate();
emit translationsUpdated();
emit websiteUrlChanged(m_languageModel->getCurrentSiteUrl());
}
void CoreController::initErrorMessagesHandler()

View File

@@ -5,6 +5,10 @@
#include <QQmlContext>
#include <QThread>
#ifndef Q_OS_ANDROID
#include "ui/systemtray_notificationhandler.h"
#endif
#include "ui/controllers/api/apiConfigsController.h"
#include "ui/controllers/api/apiSettingsController.h"
#include "ui/controllers/api/apiPremV1MigrationController.h"
@@ -61,6 +65,7 @@ public:
signals:
void translationsUpdated();
void websiteUrlChanged(const QString &newUrl);
private:
void initModels();

View File

@@ -85,8 +85,9 @@ bool IpcClient::init(IpcClient *instance)
}
qDebug() << "IpcClient::init succeed";
instance->m_isSocketConnected = (Instance()->m_ipcClient->isReplicaValid() && Instance()->m_Tun2SocksClient->isReplicaValid());
return (Instance()->m_ipcClient->isReplicaValid() && Instance()->m_Tun2SocksClient->isReplicaValid());
return Instance()->isSocketConnected();
}
QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()

View File

@@ -23,7 +23,7 @@
#include <sys/socket.h>
#include <unistd.h>
#endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/socket.h>
@@ -390,7 +390,7 @@ QString NetworkUtilities::getGatewayAndIface()
close(sock);
return gateway_address;
#endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
QString gateway;
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY};
int afinet_type[] = {AF_INET, AF_INET6};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 B

After

Width:  |  Height:  |  Size: 682 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 593 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 593 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1,6 +1,68 @@
{
"info" : {
"author" : "xcode",
"version" : 1
"images": [
{
"idiom": "mac",
"size": "16x16",
"scale": "1x",
"filename": "16.png"
},
{
"idiom": "mac",
"size": "16x16",
"scale": "2x",
"filename": "16@2x.png"
},
{
"idiom": "mac",
"size": "32x32",
"scale": "1x",
"filename": "32.png"
},
{
"idiom": "mac",
"size": "32x32",
"scale": "2x",
"filename": "32@2x.png"
},
{
"idiom": "mac",
"size": "128x128",
"scale": "1x",
"filename": "128.png"
},
{
"idiom": "mac",
"size": "128x128",
"scale": "2x",
"filename": "128@2x.png"
},
{
"idiom": "mac",
"size": "256x256",
"scale": "1x",
"filename": "256.png"
},
{
"idiom": "mac",
"size": "256x256",
"scale": "2x",
"filename": "256@2x.png"
},
{
"idiom": "mac",
"size": "512x512",
"scale": "1x",
"filename": "512.png"
},
{
"idiom": "mac",
"size": "512x512",
"scale": "2x",
"filename": "512@2x.png"
}
],
"info": {
"version": 1,
"author": "xcode"
}
}

View File

@@ -1,50 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleAllowMixedLocalizations</key>
<true/>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>LSMultipleInstancesProhibited</key>
<true/>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleAllowMixedLocalizations</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>${QT_INTERNAL_DOLLAR_VAR}{PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>com.wireguard.ios.app_group_id</key>
<string>group.org.amnezia.AmneziaVPN</string>
<key>NSCameraUsageDescription</key>
<string>Amnezia VPN needs access to the camera for reading QR-codes.</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>CFBundleIcons</key>
<dict/>
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Amnezia VPN config</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.amnezia.AmneziaVPN.amnezia-config</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>vpn</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/plain</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>WireGuard config</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.amnezia.AmneziaVPN.wireguard-config</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>conf</string>
<string>cfg</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/plain</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>OpenVPN config</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.amnezia.AmneziaVPN.openvpn-config</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>ovpn</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/plain</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>AmneziaVPN backup file</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.amnezia.AmneziaVPN.backup-config</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>backup</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/plain</string>
</array>
</dict>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>Amnezia VPN config</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>org.amnezia.AmneziaVPN.amnezia-config</string>
<string>org.amnezia.AmneziaVPN.wireguard-config</string>
<string>org.amnezia.AmneziaVPN.openvpn-config</string>
<string>org.amnezia.AmneziaVPN.backup-config</string>
</array>
</dict>
</array>
<key>NSExtensions</key>
<array>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.networkextension.packet-tunnel</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict>
</array>
</dict>
</plist>

View File

@@ -2,34 +2,40 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.application-identifier</key>
<string>$(DEVELOPMENT_TEAM).$(APP_ID_MACOS)</string>
<key>com.apple.developer.networking.custom-protocol</key>
<true/>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>app-proxy-provider</string>
<string>packet-tunnel-provider</string>
<string>dns-settings</string>
<string>relay</string>
<string>content-filter-provider</string>
<string>dns-proxy</string>
</array>
<key>com.apple.developer.system-extension.install</key>
<true/>
<key>com.apple.developer.networking.vpn.api</key>
<array>
<string>allow-vpn</string>
</array>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.org.amnezia.AmneziaVPN</string>
</array>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(DEVELOPMENT_TEAM).*</string>
</array>
<key>com.apple.developer.team-identifier</key>
<string>$(DEVELOPMENT_TEAM)</string>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>$(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS)</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>

View File

@@ -2,41 +2,30 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.application-identifier</key>
<string>$(DEVELOPMENT_TEAM).$(NETEXT_ID_MACOS)</string>
<key>com.apple.developer.networking.custom-protocol</key>
<true/>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>dns-settings</string>
<string>relay</string>
<string>packet-tunnel-provider</string>
<string>content-filter-provider</string>
<string>dns-proxy</string>
<string>app-proxy-provider</string>
</array>
<key>keychain-access-groups</key>
<key>com.apple.developer.networking.vpn.api</key>
<array>
<string>$(DEVELOPMENT_TEAM).*</string>
<string>allow-vpn</string>
</array>
<key>com.apple.developer.team-identifier</key>
<string>$(DEVELOPMENT_TEAM)</string>
<key>com.apple.developer.system-extension.install</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>$(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS)</string>
<string>group.org.amnezia.AmneziaVPN</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.private.network.socket-delegate</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,138 @@
enable_language(Swift)
message("Client message >> macos build >> AmneziaVPNNetworkExtension")
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
add_executable(AmneziaVPNNetworkExtension)
message("executable_path is: @executable_path/../../Frameworks")
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_PRODUCT_TYPE com.apple.product-type.app-extension
# MACOSX_BUNDLE YES
BUNDLE_EXTENSION appex
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
MACOSX_BUNDLE_INFO_STRING "AmneziaVPNNetworkExtension"
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPNNetworkExtension"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_NAME "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${CMAKE_CURRENT_SOURCE_DIR}/AmneziaVPNNetworkExtension.entitlements
XCODE_ATTRIBUTE_MARKETING_VERSION "${APP_MAJOR_VERSION}"
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${BUILD_ID}"
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPNNetworkExtension"
XCODE_ATTRIBUTE_APPLICATION_EXTENSION_API_ONLY "YES"
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "11.0"
XCODE_ATTRIBUTE_INFOPLIST_FILE ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../../../Frameworks @loader_path/../../../../Frameworks"
)
if(DEPLOY)
message("DEPLOY is ON")
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr macos.org.amnezia.amneziaVPN.NE"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev macos.org.amnezia.amneziaVPN.NE"
)
else()
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
endif()
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/WireGuardNetworkExtension-Bridging-Header.h"
XCODE_ATTRIBUTE_SWIFT_OPTIMIZATION_LEVEL "-Onone"
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
)
set_target_properties("AmneziaVPNNetworkExtension" PROPERTIES
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
)
find_library(FW_ASSETS_LIBRARY AssetsLibrary)
find_library(FW_MOBILE_CORE MobileCoreServices)
find_library(FW_UI_KIT UIKit)
find_library(FW_LIBRESOLV libresolv.9.tbd)
# Set the root directory
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${FW_LIBRESOLV})
target_compile_options(AmneziaVPNNetworkExtension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\")
target_compile_options(AmneziaVPNNetworkExtension PRIVATE -DNETWORK_EXTENSION=1)
set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/amneziawg-apple/Sources)
message("WG_APPLE_SOURCE_DIR is: ${WG_APPLE_SOURCE_DIR}")
message("CLIENT_ROOT_DIR is: ${CLIENT_ROOT_DIR}")
target_sources(AmneziaVPNNetworkExtension PRIVATE
${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PacketTunnelSettingsGenerator.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSResolver.swift
${WG_APPLE_SOURCE_DIR}/WireGuardNetworkExtension/ErrorNotifier.swift
${WG_APPLE_SOURCE_DIR}/Shared/Keychain.swift
${WG_APPLE_SOURCE_DIR}/Shared/Model/TunnelConfiguration+WgQuickConfig.swift
${WG_APPLE_SOURCE_DIR}/Shared/Model/NETunnelProviderProtocol+Extension.swift
${WG_APPLE_SOURCE_DIR}/Shared/Model/String+ArrayConversion.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/TunnelConfiguration.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddressRange.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/Endpoint.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSServer.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/InterfaceConfiguration.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PeerConfiguration.swift
${WG_APPLE_SOURCE_DIR}/Shared/FileManager+Extension.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
${WG_APPLE_SOURCE_DIR}/WireGuardKit/Array+ConcurrentMap.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddress+AddrInfo.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PrivateKey.swift
${CLIENT_ROOT_DIR}/platforms/ios/HevSocksTunnel.swift
${CLIENT_ROOT_DIR}/platforms/ios/NELogController.swift
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+WireGuard.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPN.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+Xray.swift
${CLIENT_ROOT_DIR}/platforms/ios/WGConfig.swift
${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm
${CLIENT_ROOT_DIR}/platforms/ios/XrayConfig.swift
)
target_sources(AmneziaVPNNetworkExtension PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
)
set_property(TARGET AmneziaVPNNetworkExtension APPEND PROPERTY RESOURCE
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
)
## Build wireguard-go-version.h
execute_process(
COMMAND go list -m golang.zx2c4.com/wireguard
WORKING_DIRECTORY ${CLIENT_ROOT_DIR}/3rd/wireguard-apple/Sources/WireGuardKitGo
OUTPUT_VARIABLE WG_VERSION_FULL
)
string(REGEX REPLACE ".*v\([0-9.]*\).*" "\\1" WG_VERSION_STRING 1.1.1)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/wireguard-go-version.h.in
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
target_sources(AmneziaVPNNetworkExtension PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR})
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/wireguard/macos/universal2/libwg-go.a)
message(${CLIENT_ROOT_DIR})
message(${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/libhev-socks5-tunnel.a)
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/libhev-socks5-tunnel.a)
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/Headers)

View File

@@ -3,27 +3,32 @@
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>AmneziaVPNNetworkExtension</string>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<string>AmneziaVPNNetworkExtension</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<string>org.amnezia.AmneziaVPN.network-extension</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<string>AmneziaVPNNetworkExtension</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<string>${APPLE_PROJECT_VERSION}</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<string>${CMAKE_PROJECT_VERSION_TWEAK}</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
<key>CFBundleDisplayName</key>
<string>AmneziaVPNNetworkExtension</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
@@ -31,5 +36,11 @@
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict>
<key>com.wireguard.ios.app_group_id</key>
<string>group.org.amnezia.AmneziaVPN</string>
<key>com.wireguard.macos.app_group_id</key>
<string>${BUILD_VPN_DEVELOPMENT_TEAM}.group.org.amnezia.AmneziaVPN</string>
</dict>
</plist>

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>1C8F.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View File

@@ -1,10 +1,10 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "macos/gobridge/wireguard.h"
#include "wireguard-go-version.h"
#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include "3rd/amneziawg-apple/Sources/WireGuardKitGo/wireguard.h"
#include "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include <stdbool.h>
#include <stdint.h>
@@ -23,3 +23,8 @@ bool key_from_hex(uint8_t key[WG_KEY_LEN], const char* hex);
bool key_eq(const uint8_t key1[WG_KEY_LEN], const uint8_t key2[WG_KEY_LEN]);
void write_msg_to_log(const char* tag, const char* msg);
// init function definition in C
void hev_socks5_tunnel_quit(void);
// Updated function definition in C
int hev_socks5_tunnel_main(const char* configFile, int fd);

View File

@@ -0,0 +1,3 @@
#ifndef WIREGUARD_GO_VERSION
#define WIREGUARD_GO_VERSION "@WG_VERSION_STRING@"
#endif // WIREGUARD_GO_VERSION

View File

@@ -15,7 +15,7 @@
#include "platforms/ios/QtAppDelegate-C-Interface.h"
#endif
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
bool isAnotherInstanceRunning()
{
QLocalSocket socket;
@@ -45,7 +45,7 @@ int main(int argc, char *argv[])
AmneziaApplication app(argc, argv);
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
if (isAnotherInstanceRunning()) {
QTimer::singleShot(1000, &app, [&]() { app.quit(); });
return app.exec();

View File

@@ -73,7 +73,7 @@ extension PacketTunnelProvider {
startHandler = completionHandler
ovpnAdapter?.connect(using: packetFlow)
}
func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
let bytesin = ovpnAdapter?.transportStatistics.bytesIn

View File

@@ -112,9 +112,19 @@ extension PacketTunnelProvider {
}
}
let lastHandshakeString = settingsDictionary["last_handshake_time_sec"]
let lastHandshake: Int64
if let lastHandshakeValue = lastHandshakeString, let handshakeValue = Int64(lastHandshakeValue) {
lastHandshake = handshakeValue
} else {
lastHandshake = -2 // Return an error if there is no value for `last_handshake_time_sec`
}
let response: [String: Any] = [
"rx_bytes": settingsDictionary["rx_bytes"] ?? "0",
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0"
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0",
"last_handshake_time_sec": lastHandshake
]
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))

View File

@@ -1,3 +1,4 @@
#if !MACOS_NE
#include "QRCodeReaderBase.h"
#import <UIKit/UIKit.h>
@@ -108,3 +109,19 @@ void QRCodeReader::startReading() {
void QRCodeReader::stopReading() {
[m_qrCodeReader stopReading];
}
#else
#include "QRCodeReaderBase.h"
QRCodeReader::QRCodeReader()
{
}
QRect QRCodeReader::cameraSize() {
return QRect();
}
void QRCodeReader::startReading() {}
void QRCodeReader::stopReading() {}
void QRCodeReader::setCameraSize(QRect) {}
#endif

View File

@@ -1,5 +1,6 @@
#if !MACOS_NE
#import <UIKit/UIKit.h>
#endif
@interface QIOSApplicationDelegate
@end

View File

@@ -5,7 +5,7 @@
@implementation QIOSApplicationDelegate (AmneziaVPNDelegate)
#if !MACOS_NE
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[application setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum];
@@ -57,5 +57,5 @@
}
return NO;
}
#endif
@end

View File

@@ -1,3 +1,13 @@
#if MACOS_NE
public func toggleScreenshots(_ isEnabled: Bool) {
}
class ScreenProtection {
}
#else
import UIKit
public func toggleScreenshots(_ isEnabled: Bool) {
@@ -90,3 +100,4 @@ struct ProtectionPair {
textField.removeFromSuperview()
}
}
#endif

View File

@@ -46,6 +46,7 @@ public:
void disconnectVpn();
void vpnStatusDidChange(void *pNotification);
void vpnConfigurationDidChange(void *pNotification);
void getBackendLogs(std::function<void(const QString &)> &&callback);

View File

@@ -27,6 +27,7 @@ const char* MessageKey::isOnDemand = "is-on-demand";
const char* MessageKey::SplitTunnelType = "SplitTunnelType";
const char* MessageKey::SplitTunnelSites = "SplitTunnelSites";
#if !MACOS_NE
static UIViewController* getViewController() {
NSArray *windows = [[UIApplication sharedApplication]windows];
for (UIWindow *window in windows) {
@@ -36,6 +37,7 @@ static UIViewController* getViewController() {
}
return nil;
}
#endif
Vpn::ConnectionState iosStatusToState(NEVPNStatus status) {
switch (status) {
@@ -249,6 +251,21 @@ void IosController::checkStatus()
sendVpnExtensionMessage(message, [&](NSDictionary* response){
uint64_t txBytes = [response[@"tx_bytes"] intValue];
uint64_t rxBytes = [response[@"rx_bytes"] intValue];
uint64_t last_handshake_time_sec = 0;
#if !MACOS_NE
if (response[@"last_handshake_time_sec"] && ![response[@"last_handshake_time_sec"] isKindOfClass:[NSNull class]]) {
last_handshake_time_sec = [response[@"last_handshake_time_sec"] intValue];
} else {
qDebug() << "Key last_handshake_time_sec is missing or null";
}
if (last_handshake_time_sec < 0) {
disconnectVpn();
qDebug() << "Invalid handshake time, disconnecting VPN.";
}
#endif
emit bytesChanged(rxBytes - m_rxBytes, txBytes - m_txBytes);
m_rxBytes = rxBytes;
m_txBytes = txBytes;
@@ -803,14 +820,14 @@ bool IosController::shareText(const QStringList& filesToSend) {
NSURL *logFileUrl = [[NSURL alloc] initFileURLWithPath:filesToSend[i].toNSString()];
[sharingItems addObject:logFileUrl];
}
#if !MACOS_NE
UIViewController *qtController = getViewController();
if (!qtController) return;
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil];
#endif
__block bool isAccepted = false;
#if !MACOS_NE
[activityController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
isAccepted = completed;
emit finished();
@@ -823,6 +840,7 @@ bool IosController::shareText(const QStringList& filesToSend) {
popController.sourceRect = CGRectMake(100, 100, 100, 100);
}
#endif
QEventLoop wait;
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
wait.exec();
@@ -831,6 +849,7 @@ bool IosController::shareText(const QStringList& filesToSend) {
}
QString IosController::openFile() {
#if !MACOS_NE
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen];
DocumentPickerDelegate *documentPickerDelegate = [[DocumentPickerDelegate alloc] init];
@@ -841,8 +860,9 @@ QString IosController::openFile() {
[qtController presentViewController:documentPicker animated:YES completion:nil];
#endif
__block QString filePath;
#if !MACOS_NE
documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) {
if (path) {
filePath = QString::fromUtf8(path.UTF8String);
@@ -851,7 +871,7 @@ QString IosController::openFile() {
}
emit finished();
};
#endif
QEventLoop wait;
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
wait.exec();

View File

@@ -1,7 +1,11 @@
#import <NetworkExtension/NetworkExtension.h>
#import <NetworkExtension/NETunnelProviderSession.h>
#import <Foundation/Foundation.h>
#if !MACOS_NE
#include <UIKit/UIKit.h>
#endif
#include <Security/Security.h>
class IosController;
@@ -17,9 +21,10 @@ class IosController;
@end
typedef void (^DocumentPickerClosedCallback)(NSString *path);
#if !MACOS_NE
@interface DocumentPickerDelegate : NSObject <UIDocumentPickerDelegate>
@property (nonatomic, copy) DocumentPickerClosedCallback documentPickerClosedCallback;
@end
#endif

View File

@@ -26,7 +26,8 @@
@end
@implementation DocumentPickerDelegate
#if !MACOS_NE
@implementation DocumentPickerDelegate
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
for (NSURL *url in urls) {
@@ -42,4 +43,5 @@
}
}
@end
@end
#endif

View File

@@ -6,6 +6,8 @@
#import <UserNotifications/UserNotifications.h>
#import <Foundation/Foundation.h>
#if !MACOS_NE
#import <UIKit/UIKit.h>
@interface IOSNotificationDelegate
@@ -87,3 +89,86 @@ void IOSNotificationHandler::notify(NotificationHandler::Message type, const QSt
}
}];
}
#else
// Removed the UIResponder and UIApplicationDelegate references as these are not available in macOS
@interface IOSNotificationDelegate
: NSObject <UNUserNotificationCenterDelegate> {
IOSNotificationHandler* m_iosNotificationHandler;
}
@end
@implementation IOSNotificationDelegate
- (id)initWithObject:(IOSNotificationHandler*)notification {
self = [super init]; // Removed `super init` as it refers to UIResponder, which is iOS specific
if (self) {
m_iosNotificationHandler = notification;
}
return self;
}
- (void)userNotificationCenter:(UNUserNotificationCenter*)center
willPresentNotification:(UNNotification*)notification
withCompletionHandler:
(void (^)(UNNotificationPresentationOptions options))completionHandler {
Q_UNUSED(center)
completionHandler(UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner);
}
- (void)userNotificationCenter:(UNUserNotificationCenter*)center
didReceiveNotificationResponse:(UNNotificationResponse*)response
withCompletionHandler:(void (^)())completionHandler {
Q_UNUSED(center)
Q_UNUSED(response)
completionHandler();
}
@end
IOSNotificationHandler::IOSNotificationHandler(QObject* parent) : NotificationHandler(parent) {
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert |
UNAuthorizationOptionBadge)
completionHandler:^(BOOL granted, NSError* _Nullable error) {
Q_UNUSED(granted);
if (!error) {
m_delegate = [[IOSNotificationDelegate alloc] initWithObject:this];
}
}];
}
IOSNotificationHandler::~IOSNotificationHandler() { }
void IOSNotificationHandler::notify(NotificationHandler::Message type, const QString& title,
const QString& message, int timerMsec) {
Q_UNUSED(type);
if (!m_delegate) {
return;
}
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
content.title = title.toNSString();
content.body = message.toNSString();
content.sound = [UNNotificationSound defaultSound];
int timerSec = timerMsec / 1000;
UNTimeIntervalNotificationTrigger* trigger =
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:timerSec repeats:NO];
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"amneziavpn"
content:content
trigger:trigger];
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = (id<UNUserNotificationCenterDelegate>)m_delegate;
[center addNotificationRequest:request
withCompletionHandler:^(NSError* _Nullable error) {
if (error) {
NSLog(@"Local Notification failed");
}
}];
}
#endif

View File

@@ -56,7 +56,8 @@ void OpenVpnProtocol::stop()
}
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
IpcClient::Interface()->disableKillSwitch();
QRemoteObjectPendingReply<bool> disableKillSwitchResp = IpcClient::Interface()->disableKillSwitch();
disableKillSwitchResp.waitForFinished(1000);
#endif
setConnectionState(Vpn::ConnectionState::Disconnected);

View File

@@ -192,7 +192,7 @@ namespace amnezia
constexpr char defaultPort[] = "51820";
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
constexpr char defaultMtu[] = "1280";
#else
constexpr char defaultMtu[] = "1376";
@@ -212,7 +212,7 @@ namespace amnezia
namespace awg
{
constexpr char defaultPort[] = "55424";
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
constexpr char defaultMtu[] = "1280";
#else
constexpr char defaultMtu[] = "1376";

View File

@@ -4,7 +4,7 @@
#include "core/errorstrings.h"
#include "vpnprotocol.h"
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
#include "openvpnovercloakprotocol.h"
#include "openvpnprotocol.h"
#include "shadowsocksvpnprotocol.h"
@@ -109,7 +109,7 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &
#if defined(Q_OS_WINDOWS)
case DockerContainer::Ipsec: return new Ikev2Protocol(configuration);
#endif
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
case DockerContainer::OpenVpn: return new OpenVpnProtocol(configuration);
case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration);
case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration);

View File

@@ -167,8 +167,10 @@ ErrorCode XrayProtocol::startTun2Sock()
void XrayProtocol::stop()
{
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
IpcClient::Interface()->disableKillSwitch();
IpcClient::Interface()->StartRoutingIpv6();
QRemoteObjectPendingReply<bool> disableKillSwitchResp = IpcClient::Interface()->disableKillSwitch();
disableKillSwitchResp.waitForFinished(1000);
QRemoteObjectPendingReply<bool> StartRoutingIpv6Resp = IpcClient::Interface()->StartRoutingIpv6();
StartRoutingIpv6Resp.waitForFinished(1000);
#endif
qDebug() << "XrayProtocol::stop()";
m_xrayProcess.disconnect();
@@ -176,6 +178,7 @@ void XrayProtocol::stop()
m_xrayProcess.waitForFinished(3000);
if (m_t2sProcess) {
m_t2sProcess->stop();
QThread::msleep(200);
}
setConnectionState(Vpn::ConnectionState::Disconnected);

View File

@@ -127,7 +127,7 @@
<file>ui/qml/Components/SelectLanguageDrawer.qml</file>
<file>ui/qml/Components/ServersListView.qml</file>
<file>ui/qml/Components/SettingsContainersListView.qml</file>
<file>ui/qml/Components/ShareConnectionDrawer.qml</file>
<file>ui/qml/Components/TransportProtoSelector.qml</file>
<file>ui/qml/Components/AddSitePanel.qml</file>
<file>ui/qml/Config/GlobalConfig.qml</file>
@@ -228,6 +228,7 @@
<file>ui/qml/Pages2/PageSetupWizardViewConfig.qml</file>
<file>ui/qml/Pages2/PageShare.qml</file>
<file>ui/qml/Pages2/PageShareFullAccess.qml</file>
<file>ui/qml/Pages2/PageShareConnection.qml</file>
<file>ui/qml/Pages2/PageStart.qml</file>
<file>ui/qml/Components/RenameServerDrawer.qml</file>
<file>ui/qml/Controls2/ListViewType.qml</file>
@@ -240,6 +241,7 @@
<file>ui/qml/Components/ApiPremV1SubListDrawer.qml</file>
<file>ui/qml/Components/OtpCodeDrawer.qml</file>
<file>ui/qml/Components/AwgTextField.qml</file>
<file>ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml</file>
</qresource>
<qresource prefix="/countriesFlags">
<file>images/flagKit/ZW.svg</file>

View File

@@ -1,7 +1,7 @@
FROM alpine:3.15
LABEL maintainer="AmneziaVPN"
ARG XRAY_RELEASE="v1.8.6"
ARG XRAY_RELEASE="v25.8.3"
RUN apk add --no-cache curl unzip bash openssl netcat-openbsd dumb-init rng-tools xz
RUN apk --update upgrade --no-cache

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
#include "connectionController.h"
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
#include <QGuiApplication>
#else
#include <QApplication>
@@ -32,8 +32,9 @@ ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &s
void ConnectionController::openConnection()
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true)) {
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
if (!Utils::processIsRunning(Utils::executable(SERVICE_NAME, false), true))
{
emit connectionErrorOccurred(ErrorCode::AmneziaServiceNotRunning);
return;
}

View File

@@ -4,6 +4,12 @@
#include <QQmlApplicationEngine>
#include <QQuickWindow>
#include "logger.h"
namespace {
Logger logger("FocusController");
}
FocusController::FocusController(QQmlApplicationEngine *engine, QObject *parent)
: QObject { parent },
m_engine { engine },
@@ -85,7 +91,7 @@ void FocusController::dropRootObject(QObject *object)
dropListView();
setFocusOnDefaultItem();
} else {
qWarning() << "===>> TRY TO DROP WRONG ROOT OBJECT: " << m_rootObjects.top() << " SHOULD BE: " << object;
logger.warning() << "TRY TO DROP WRONG ROOT OBJECT: " << m_rootObjects.top() << " SHOULD BE: " << object;
}
}
@@ -101,7 +107,7 @@ void FocusController::reload(Direction direction)
QObject *rootObject = (m_rootObjects.empty() ? m_engine->rootObjects().value(0) : m_rootObjects.top());
if (!rootObject) {
qCritical() << "No ROOT OBJECT found!";
logger.error() << "No ROOT OBJECT found!";
resetRootObject();
dropListView();
return;
@@ -113,7 +119,7 @@ void FocusController::reload(Direction direction)
direction == Direction::Forward ? FocusControl::isLess : FocusControl::isMore);
if (m_focusChain.empty()) {
qWarning() << "Focus chain is empty!";
logger.warning() << "Focus chain is empty!";
resetRootObject();
dropListView();
return;
@@ -131,7 +137,7 @@ void FocusController::nextItem(Direction direction)
}
if (m_focusChain.empty()) {
qWarning() << "There are no items to navigate";
logger.warning() << "There are no items to navigate";
setFocusOnDefaultItem();
return;
}
@@ -149,7 +155,7 @@ void FocusController::nextItem(Direction direction)
const auto focusedItem = qobject_cast<QQuickItem *>(m_focusChain.at(focusedItemIndex));
if (focusedItem == nullptr) {
qWarning() << "Failed to get item to focus on. Setting focus on default";
logger.warning() << "Failed to get item to focus on. Setting focus on default";
setFocusOnDefaultItem();
return;
}

View File

@@ -19,7 +19,7 @@
#ifdef Q_OS_ANDROID
#include "platforms/android/android_controller.h"
#endif
#ifdef Q_OS_IOS
#if defined(Q_OS_IOS) || defined(MACOS_NE)
#include <CoreFoundation/CoreFoundation.h>
#endif
@@ -595,7 +595,7 @@ void ImportController::startDecodingQr()
m_totalQrCodeChunksCount = 0;
m_receivedQrCodeChunksCount = 0;
#if defined Q_OS_IOS
#if defined(Q_OS_IOS) || defined(MACOS_NE)
m_isQrCodeProcessed = true;
#endif
#if defined Q_OS_ANDROID

View File

@@ -403,9 +403,19 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
QJsonObject config;
Proto mainProto = ContainerProps::defaultProtocol(container);
const auto &protocols = ContainerProps::protocolsForContainer(container);
for (const auto &protocol : protocols) {
QJsonObject containerConfig;
if (protocol == mainProto) {
// for Multiprotocols (OpenVPN over SS, OpenVPN over Cloak)
bool shouldProcessProtocol = false;
if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) {
shouldProcessProtocol = true;
} else {
shouldProcessProtocol = (protocol == mainProto);
}
if (shouldProcessProtocol) {
containerConfig.insert(config_key::port, port);
containerConfig.insert(config_key::transport_proto, transportProto);
@@ -550,14 +560,97 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
qDebug() << siteName;
containerConfig.insert(config_key::site, siteName);
} else if (protocol == Proto::OpenVpn) {
QString serverConfig = serverController->getTextFileFromContainer(container, credentials,
protocols::openvpn::serverConfigPath, errorCode);
QMap<QString, QString> serverConfigMap;
auto serverConfigLines = serverConfig.split("\n");
for (auto &line : serverConfigLines) {
auto trimmedLine = line.trimmed();
if (trimmedLine.startsWith("#") || trimmedLine.isEmpty()) {
continue;
} else {
QStringList parts = trimmedLine.split(" ");
if (parts.count() >= 2) {
QString key = parts[0];
QString value = parts.mid(1).join(" ");
serverConfigMap.insert(key, value);
}
}
}
QString serverValue = serverConfigMap.value("server");
if (!serverValue.isEmpty()) {
QStringList serverParts = serverValue.split(" ");
if (serverParts.count() >= 1) {
containerConfig[config_key::subnet_address] = serverParts[0];
}
}
bool ncpDisable = serverConfig.contains("ncp-disable");
containerConfig[config_key::ncp_disable] = ncpDisable;
bool tlsAuth = serverConfig.contains("tls-auth");
containerConfig[config_key::tls_auth] = tlsAuth;
bool blockOutsideDns = serverConfig.contains("block-outside-dns");
containerConfig[config_key::block_outside_dns] = blockOutsideDns;
QString cipher = serverConfigMap.value("cipher");
if (!cipher.isEmpty()) {
containerConfig[config_key::cipher] = cipher;
}
QString hash = serverConfigMap.value("auth");
if (!hash.isEmpty()) {
containerConfig[config_key::hash] = hash;
}
} else if (protocol == Proto::Cloak) {
QString cloakConfig = serverController->getTextFileFromContainer(container, credentials,
"/opt/amnezia/cloak/ck-config.json", errorCode);
QJsonDocument doc = QJsonDocument::fromJson(cloakConfig.toUtf8());
if (!doc.isNull() && doc.isObject()) {
QJsonObject cloakConfigObj = doc.object();
QString site = cloakConfigObj.value("RedirAddr").toString();
if (!site.isEmpty()) {
containerConfig[config_key::site] = site;
}
} else {
qDebug() << "Failed to parse main loop Cloak JSON config";
}
} else if (protocol == Proto::ShadowSocks) {
QString shadowsocksConfig = serverController->getTextFileFromContainer(container, credentials,
"/opt/amnezia/shadowsocks/ss-config.json", errorCode);
QJsonDocument doc = QJsonDocument::fromJson(shadowsocksConfig.toUtf8());
if (!doc.isNull() && doc.isObject()) {
QJsonObject ssConfigObj = doc.object();
QString cipher = ssConfigObj.value("method").toString();
if (!cipher.isEmpty()) {
containerConfig[config_key::cipher] = cipher;
}
} else {
qDebug() << "Failed to parse main loop Shadowsocks JSON config";
}
}
config.insert(config_key::container, ContainerProps::containerToString(container));
}
config.insert(ProtocolProps::protoToString(protocol), containerConfig);
if (shouldProcessProtocol) {
config.insert(ProtocolProps::protoToString(protocol), containerConfig);
}
}
installedContainers.insert(container, config);
}
const static QRegularExpression torOrDnsRegExp("(amnezia-(?:torwebsite|dns)).*?([0-9]*)/(udp|tcp).*");
QRegularExpressionMatch torOrDnsRegMatch = torOrDnsRegExp.match(containerInfo);
if (torOrDnsRegMatch.hasMatch()) {
@@ -721,6 +814,8 @@ void InstallController::clearCachedProfile(QSharedPointer<ServerController> serv
m_clientManagementModel->revokeClient(containerConfig, container, serverCredentials, serverIndex, serverController);
emit cachedProfileCleared(tr("%1 cached profile cleared").arg(ContainerProps::containerHumanNames().value(container)));
QJsonObject updatedConfig = m_settings->containerConfig(serverIndex, container);
emit profileCleared(updatedConfig);
}
QRegularExpression InstallController::ipAddressPortRegExp()

View File

@@ -83,6 +83,8 @@ signals:
void noInstalledContainers();
void profileCleared(const QJsonObject &config);
private:
void installServer(const DockerContainer container, const QMap<DockerContainer, QJsonObject> &installedContainers,
const ServerCredentials &serverCredentials, const QSharedPointer<ServerController> &serverController,

View File

@@ -37,7 +37,7 @@ void ListViewFocusController::viewAtCurrentIndex() const
}
case Section::Delegate: {
QMetaObject::invokeMethod(m_listView, "positionViewAtIndex", Q_ARG(int, m_delegateIndex), // Index
Q_ARG(int, 2)); // PositionMode (0 = Visible)
Q_ARG(int, 6)); // PositionMode (0 = Beginning; 1 = Center; 2 = End; 3 = Visible; 4 = Contain; 5 = SnapPosition)
break;
}
case Section::Footer: {

View File

@@ -1,8 +1,11 @@
#include "pageController.h"
#include "utils/converter.h"
#include "core/errorstrings.h"
#if defined(MACOS_NE)
#include "platforms/ios/ios_controller.h"
#endif
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
#include <QGuiApplication>
#else
#include <QApplication>
@@ -25,8 +28,12 @@ PageController::PageController(const QSharedPointer<ServersModel> &serversModel,
#endif
#if defined Q_OS_MACX
connect(this, &PageController::raiseMainWindow, []() { setDockIconVisible(true); });
connect(this, &PageController::hideMainWindow, []() { setDockIconVisible(false); });
connect(this, &PageController::raiseMainWindow, []() {
setDockIconVisible(true);
});
connect(this, &PageController::hideMainWindow, []() {
setDockIconVisible(false);
});
#endif
connect(this, qOverload<ErrorCode>(&PageController::showErrorMessage), this, &PageController::onShowErrorMessage);
@@ -56,14 +63,11 @@ QString PageController::getPagePath(PageLoader::PageEnum page)
void PageController::closeWindow()
{
#ifdef Q_OS_ANDROID
// On mobile platforms, quit app on close; on desktop, just hide window
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
qApp->quit();
#else
if (m_serversModel->getServersCount() == 0) {
qApp->quit();
} else {
emit hideMainWindow();
}
emit hideMainWindow();
#endif
}
@@ -114,7 +118,7 @@ void PageController::showOnStartup()
} else {
#if defined(Q_OS_WIN) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
emit hideMainWindow();
#elif defined Q_OS_MACX
#elif defined(Q_OS_MACX)
setDockIconVisible(false);
#endif
}

View File

@@ -38,6 +38,7 @@ namespace PageLoader
PageSettingsApiInstructions,
PageSettingsApiNativeConfigs,
PageSettingsApiDevices,
PageSettingsApiSubscriptionKey,
PageSettingsKillSwitchExceptions,
PageServiceSftpSettings,
@@ -71,6 +72,7 @@ namespace PageLoader
PageProtocolAwgClientSettings,
PageShareFullAccess,
PageShareConnection,
PageDevMenu
};

View File

@@ -10,7 +10,7 @@
#include "platforms/android/android_controller.h"
#endif
#ifdef Q_OS_IOS
#if defined(Q_OS_IOS) || defined(MACOS_NE)
#include <AmneziaVPN-Swift.h>
#endif
@@ -35,6 +35,23 @@ SettingsController::SettingsController(const QSharedPointer<ServersModel> &serve
#endif
}
QString getPlatformName()
{
#if defined(Q_OS_WINDOWS)
return "Windows";
#elif defined(Q_OS_ANDROID)
return "Android";
#elif defined(Q_OS_LINUX)
return "Linux";
#elif defined(Q_OS_MACX)
return "MacOS";
#elif defined(Q_OS_IOS)
return "iOS";
#else
return "Unknown";
#endif
}
void SettingsController::toggleAmneziaDns(bool enable)
{
m_settings->setUseAmneziaDns(enable);
@@ -76,7 +93,7 @@ bool SettingsController::isLoggingEnabled()
void SettingsController::toggleLogging(bool enable)
{
m_settings->setSaveLogs(enable);
#ifdef Q_OS_IOS
#if defined(Q_OS_IOS)
AmneziaVPN::toggleLogging(enable);
#endif
if (enable == true) {
@@ -130,15 +147,22 @@ void SettingsController::backupAppConfig(const QString &fileName)
QJsonDocument doc = QJsonDocument::fromJson(data);
QJsonObject config = doc.object();
config["AppPlatform"] = getPlatformName();
config["Conf/autoStart"] = Autostart::isAutostart();
config["Conf/killSwitchEnabled"] = isKillSwitchEnabled();
config["Conf/strictKillSwitchEnabled"] = isStrictKillSwitchEnabled();
SystemController::saveFile(fileName, QJsonDocument(config).toJson());
}
void SettingsController::restoreAppConfig(const QString &fileName)
{
QByteArray data;
SystemController::readFile(fileName, data);
QFile file(fileName);
file.open(QIODevice::ReadOnly);
QByteArray data = file.readAll();
restoreAppConfigFromData(data);
}
@@ -155,21 +179,41 @@ void SettingsController::restoreAppConfigFromData(const QByteArray &data)
}
toggleAutoStart(autoStart);
#endif
m_serversModel->resetModel();
m_languageModel->changeLanguage(
static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageModel->getCurrentLanguageIndex()));
#if defined(Q_OS_WINDOWS) || defined(Q_OS_ANDROID)
int appSplitTunnelingRouteMode = newConfigData.value("Conf/appsRouteMode").toInt();
bool appSplittunnelingEnabled = newConfigData.value("Conf/appsSplitTunnelingEnabled").toBool();
bool appSplittunnelingEnabled = newConfigData.value("Conf/appsSplitTunnelingEnabled").toString().toLower() == "true";
m_appSplitTunnelingModel->setRouteMode(appSplitTunnelingRouteMode);
#if defined(Q_OS_WINDOWS)
m_appSplitTunnelingModel->setRouteMode(static_cast<int>(Settings::AppsRouteMode::VpnAllExceptApps));
#endif
if (newConfigData.contains("AppPlatform")) { //if backup is from a new version
if (newConfigData.value("AppPlatform").toString() != getPlatformName()) {
m_appSplitTunnelingModel->clearAppsList();
}
}
m_appSplitTunnelingModel->toggleSplitTunneling(appSplittunnelingEnabled);
#endif
int siteSplitTunnelingRouteMode = newConfigData.value("Conf/routeMode").toInt();
bool siteSplittunnelingEnabled = newConfigData.value("Conf/sitesSplitTunnelingEnabled").toBool();
bool siteSplittunnelingEnabled = newConfigData.value("Conf/sitesSplitTunnelingEnabled").toString().toLower() == "true";
m_sitesModel->setRouteMode(siteSplitTunnelingRouteMode);
m_sitesModel->toggleSplitTunneling(siteSplittunnelingEnabled);
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
m_settings->setAutoConnect(false);
m_settings->setStartMinimized(false);
m_settings->setKillSwitchEnabled(false);
m_settings->setStrictKillSwitchEnabled(false);
#endif
emit restoreBackupFinished();
} else {
emit changeSettingsErrorOccurred(tr("Backup file is corrupted"));
@@ -185,8 +229,7 @@ void SettingsController::clearSettings()
{
m_settings->clearSettings();
m_serversModel->resetModel();
m_languageModel->changeLanguage(
static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageModel->getCurrentLanguageIndex()));
m_languageModel->changeLanguage(m_languageModel->getSystemLanguageEnum());
m_sitesModel->setRouteMode(Settings::RouteMode::VpnOnlyForwardSites);
m_sitesModel->toggleSplitTunneling(false);
@@ -198,7 +241,7 @@ void SettingsController::clearSettings()
emit changeSettingsFinished(tr("All settings have been reset to default values"));
#ifdef Q_OS_IOS
#if defined(Q_OS_IOS) || defined(MACOS_NE)
AmneziaVPN::clearSettings();
#endif
}

View File

@@ -136,6 +136,8 @@ private:
QString m_appVersion;
QString getPlatform();
QDateTime m_loggingDisableDate;
bool m_isDevModeEnabled = false;

View File

@@ -78,6 +78,13 @@ void SitesController::removeSite(int index)
emit finished(tr("Site removed: %1").arg(hostname));
}
void SitesController::removeSites()
{
m_sitesModel->removeSites();
emit finished(tr("Site list cleared!"));
}
void SitesController::importSites(const QString &fileName, bool replaceExisting)
{
QByteArray jsonData;

View File

@@ -19,6 +19,7 @@ public slots:
void addSite(QString hostname);
void removeSite(int index);
void removeSites();
void importSites(const QString &fileName, bool replaceExisting);
void exportSites(const QString &fileName);

View File

@@ -14,7 +14,7 @@
#include "platforms/android/android_controller.h"
#endif
#ifdef Q_OS_IOS
#if defined(Q_OS_IOS) || defined(MACOS_NE)
#include "platforms/ios/ios_controller.h"
#include <CoreFoundation/CoreFoundation.h>
#endif
@@ -58,8 +58,10 @@ void SystemController::saveFile(const QString &fileName, const QString &data)
const auto url = fi.absoluteDir().absolutePath();
#endif
#ifndef MACOS_NE
QDesktopServices::openUrl(url);
#endif
#endif
}
bool SystemController::readFile(const QString &fileName, QByteArray &data)

View File

@@ -112,7 +112,7 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
} else {
return tr("VPN will open only popular sites blocked in your region, such as Instagram, Facebook, Twitter and others. "
"Other sites will be opened from your real IP address, "
"<a href=\"%1/free\" style=\"color: #FBB26A;\">more details on the website.</a>");
"<a href=\"%1\" style=\"color: #FBB26A;\">more details on the website.</a>");
}
}
case PriceRole: {

View File

@@ -26,12 +26,12 @@ QVariant AppSplitTunnelingModel::data(const QModelIndex &index, int role) const
return QVariant();
switch (role) {
case AppPathRole: {
return m_apps.at(index.row()).appName;
}
default: {
return true;
}
case AppPathRole: {
return m_apps.at(index.row()).appName;
}
default: {
return true;
}
}
return QVariant();
@@ -59,6 +59,13 @@ void AppSplitTunnelingModel::removeApp(QModelIndex index)
endRemoveRows();
}
void AppSplitTunnelingModel::clearAppsList() {
beginResetModel();
m_apps.clear();
m_settings->setVpnApps(m_currentRouteMode, m_apps);
endResetModel();
}
int AppSplitTunnelingModel::getRouteMode()
{
return m_currentRouteMode;

View File

@@ -29,6 +29,7 @@ public:
public slots:
bool addApp(const InstalledAppInfo &appInfo);
void removeApp(QModelIndex index);
void clearAppsList();
int getRouteMode();
void setRouteMode(int routeMode);

View File

@@ -101,6 +101,23 @@ QString LanguageModel::getCurrentLanguageName()
return m_availableLanguages[getCurrentLanguageIndex()].name;
}
LanguageSettings::AvailableLanguageEnum LanguageModel::getSystemLanguageEnum()
{
QLocale locale = QLocale::system();
switch (locale.language()) {
case QLocale::Russian: return LanguageSettings::AvailableLanguageEnum::Russian;
case QLocale::Chinese: return LanguageSettings::AvailableLanguageEnum::China_cn;
case QLocale::Ukrainian: return LanguageSettings::AvailableLanguageEnum::Ukrainian;
case QLocale::Persian: return LanguageSettings::AvailableLanguageEnum::Persian;
case QLocale::Arabic: return LanguageSettings::AvailableLanguageEnum::Arabic;
case QLocale::Burmese: return LanguageSettings::AvailableLanguageEnum::Burmese;
case QLocale::Urdu: return LanguageSettings::AvailableLanguageEnum::Urdu;
case QLocale::Hindi: return LanguageSettings::AvailableLanguageEnum::Hindi;
case QLocale::English: return LanguageSettings::AvailableLanguageEnum::English;
default: return LanguageSettings::AvailableLanguageEnum::English;
}
}
QString LanguageModel::getCurrentSiteUrl(const QString &path)
{
auto language = static_cast<LanguageSettings::AvailableLanguageEnum>(getCurrentLanguageIndex());
@@ -110,3 +127,13 @@ QString LanguageModel::getCurrentSiteUrl(const QString &path)
default: return QString("https://amnezia.org") + (path.isEmpty() ? "" : (QString("/%1").arg(path)));
}
}
QString LanguageModel::getCurrentDocsUrl(const QString &path)
{
auto language = static_cast<LanguageSettings::AvailableLanguageEnum>(getCurrentLanguageIndex());
switch (language) {
case LanguageSettings::AvailableLanguageEnum::Russian:
return "https://storage.googleapis.com/amnezia/docs" + (path.isEmpty() ? "" : (QString("?m-path=/%1").arg(path)));
default: return QString("https://docs.amnezia.org") + (path.isEmpty() ? "" : (QString("/%1").arg(path)));
}
}

View File

@@ -46,6 +46,7 @@ public:
};
LanguageModel(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
LanguageSettings::AvailableLanguageEnum getSystemLanguageEnum();
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
@@ -60,6 +61,7 @@ public slots:
int getLineHeightAppend();
QString getCurrentLanguageName();
QString getCurrentSiteUrl(const QString &path = "");
QString getCurrentDocsUrl(const QString &path = "");
signals:
void updateTranslations(const QLocale &locale);

View File

@@ -4,7 +4,7 @@
#include "core/controllers/serverController.h"
#include "core/networkUtilities.h"
#ifdef Q_OS_IOS
#if defined(Q_OS_IOS) || defined(MACOS_NE)
#include <AmneziaVPN-Swift.h>
#endif
@@ -782,7 +782,7 @@ void ServersModel::removeApiConfig(const int serverIndex)
{
auto serverConfig = getServerConfig(serverIndex);
#ifdef Q_OS_IOS
#if defined(Q_OS_IOS) || defined(MACOS_NE)
QString vpncName = QString("%1 (%2) %3")
.arg(serverConfig[config_key::description].toString())
.arg(serverConfig[config_key::hostName].toString())

View File

@@ -83,6 +83,16 @@ void SitesModel::removeSite(QModelIndex index)
endRemoveRows();
}
void SitesModel::removeSites()
{
beginResetModel();
m_settings->removeAllVpnSites(m_currentRouteMode);
fillSites();
endResetModel();
}
int SitesModel::getRouteMode()
{
return m_currentRouteMode;

View File

@@ -28,6 +28,7 @@ public slots:
bool addSite(const QString &hostname, const QString &ip);
void addSites(const QMap<QString, QString> &sites, bool replaceExisting);
void removeSite(QModelIndex index);
void removeSites();
int getRouteMode();
void setRouteMode(int routeMode);

View File

@@ -0,0 +1,36 @@
#ifndef NE_NOTIFICATION_HANDLER_H
#define NE_NOTIFICATION_HANDLER_H
#include "notificationhandler.h"
#include <QMenu>
#include <QAction>
class MacOSStatusIcon;
class NEStatusBarNotificationHandler : public NotificationHandler {
Q_OBJECT
public:
explicit NEStatusBarNotificationHandler(QObject* parent);
~NEStatusBarNotificationHandler() override;
void setConnectionState(Vpn::ConnectionState state) override;
void onTranslationsUpdated() override;
protected:
void notify(Message type, const QString& title,
const QString& message, int timerMsec) override;
private:
void buildMenu();
QMenu m_menu;
MacOSStatusIcon* m_statusIcon;
QAction* m_actionShow;
QAction* m_actionConnect;
QAction* m_actionDisconnect;
QAction* m_actionVisitWebsite;
QAction* m_actionQuit;
};
#endif // NE_NOTIFICATION_HANDLER_H

View File

@@ -11,18 +11,12 @@
# include "systemtray_notificationhandler.h"
#endif
// static
NotificationHandler* NotificationHandler::create(QObject* parent) {
#if defined(Q_OS_IOS)
return new IOSNotificationHandler(parent);
#else
# if defined(Q_OS_LINUX)
//if (LinuxSystemTrayNotificationHandler::requiredCustomImpl()) {
// return new LinuxSystemTrayNotificationHandler(parent);
//}
# endif
return new SystemTrayNotificationHandler(parent);
#endif
}

View File

@@ -10,8 +10,7 @@ import ProtocolEnum 1.0
import "../Controls2"
import "../Controls2/TextTypes"
ListView {
ListViewType {
id: menuContent
property var rootWidth
@@ -21,13 +20,6 @@ ListView {
anchors.top: parent.top
anchors.bottom: parent.bottom
clip: true
snapMode: ListView.SnapToItem
ScrollBar.vertical: ScrollBarType {}
property bool isFocusable: true
ButtonGroup {
id: containersRadioButtonGroup
}

View File

@@ -1,97 +1,97 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import PageEnum 1.0
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
DrawerType2 {
id: root
property bool isAppSplitTinnelingEnabled: Qt.platform.os === "windows" || Qt.platform.os === "android"
anchors.fill: parent
expandedHeight: parent.height * 0.9
expandedStateContent: ColumnLayout {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
spacing: 0
Header2Type {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: 16
headerText: qsTr("Split tunneling")
descriptionText: qsTr("Allows you to connect to some sites or applications through a VPN connection and bypass others")
}
LabelWithButtonType {
id: splitTunnelingSwitch
Layout.fillWidth: true
Layout.topMargin: 16
visible: ServersModel.isDefaultServerDefaultContainerHasSplitTunneling
text: qsTr("Split tunneling on the server")
descriptionText: qsTr("Enabled \nCan't be disabled for current server")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsSplitTunneling)
root.closeTriggered()
}
}
DividerType {
visible: ServersModel.isDefaultServerDefaultContainerHasSplitTunneling
}
LabelWithButtonType {
id: siteBasedSplitTunnelingSwitch
Layout.fillWidth: true
Layout.topMargin: 16
text: qsTr("Site-based split tunneling")
descriptionText: enabled && SitesModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsSplitTunneling)
root.closeTriggered()
}
}
DividerType {
}
LabelWithButtonType {
id: appSplitTunnelingSwitch
visible: isAppSplitTinnelingEnabled
Layout.fillWidth: true
text: qsTr("App-based split tunneling")
descriptionText: AppSplitTunnelingModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling)
root.closeTriggered()
}
}
DividerType {
visible: isAppSplitTinnelingEnabled
}
}
}
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import PageEnum 1.0
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
DrawerType2 {
id: root
property bool isAppSplitTinnelingEnabled: Qt.platform.os === "windows" || Qt.platform.os === "android"
anchors.fill: parent
expandedHeight: parent.height * 0.9
expandedStateContent: ColumnLayout {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
spacing: 0
Header2Type {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: 16
headerText: qsTr("Split tunneling")
descriptionText: qsTr("Allows you to connect to some sites or applications through a VPN connection and bypass others")
}
LabelWithButtonType {
id: splitTunnelingSwitch
Layout.fillWidth: true
Layout.topMargin: 16
visible: ServersModel.isDefaultServerDefaultContainerHasSplitTunneling
text: qsTr("Split tunneling on the server")
descriptionText: qsTr("Enabled \nCan't be disabled for current server")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsSplitTunneling)
root.closeTriggered()
}
}
DividerType {
visible: ServersModel.isDefaultServerDefaultContainerHasSplitTunneling
}
LabelWithButtonType {
id: siteBasedSplitTunnelingSwitch
Layout.fillWidth: true
Layout.topMargin: 16
text: qsTr("Site-based split tunneling")
descriptionText: enabled && SitesModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsSplitTunneling)
root.closeTriggered()
}
}
DividerType {
}
LabelWithButtonType {
id: appSplitTunnelingSwitch
visible: isAppSplitTinnelingEnabled
Layout.fillWidth: true
text: qsTr("App-based split tunneling")
descriptionText: AppSplitTunnelingModel.isTunnelingEnabled ? qsTr("Enabled") : qsTr("Disabled")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
PageController.goToPage(PageEnum.PageSettingsAppSplitTunneling)
root.closeTriggered()
}
}
DividerType {
visible: isAppSplitTinnelingEnabled
}
}
}

View File

@@ -57,7 +57,7 @@ DrawerType2 {
headerText: qsTr("Choose application")
}
ListView {
ListViewType {
id: listView
Layout.fillWidth: true
@@ -66,11 +66,6 @@ DrawerType2 {
Layout.rightMargin: 16
Layout.leftMargin: 16
clip: true
interactive: true
property bool isFocusable: true
model: SortFilterProxyModel {
id: proxyInstalledAppsModel
sourceModel: installedAppsModel
@@ -81,44 +76,35 @@ DrawerType2 {
}
}
ScrollBar.vertical: ScrollBarType {}
ButtonGroup {
id: buttonGroup
}
delegate: Item {
implicitWidth: root.width
implicitHeight: delegateContent.implicitHeight
delegate: ColumnLayout {
width: listView.width
ColumnLayout {
id: delegateContent
RowLayout {
CheckBoxType {
Layout.fillWidth: true
anchors.fill: parent
RowLayout {
CheckBoxType {
Layout.fillWidth: true
text: appName
checked: isAppSelected
onCheckedChanged: {
installedAppsModel.selectedStateChanged(proxyInstalledAppsModel.mapToSource(index), checked)
}
}
Image {
source: "image://installedAppImage/" + appIcon
sourceSize.width: 24
sourceSize.height: 24
Layout.rightMargin: 48
text: appName
checked: isAppSelected
onCheckedChanged: {
installedAppsModel.selectedStateChanged(proxyInstalledAppsModel.mapToSource(index), checked)
}
}
DividerType {}
Image {
source: "image://installedAppImage/" + appIcon
sourceSize.width: 24
sourceSize.height: 24
Layout.rightMargin: 48
}
}
DividerType {}
}
}
}

View File

@@ -49,7 +49,7 @@ DrawerType2 {
}
}
ListView {
ListViewType {
id: listView
anchors.top: backButtonLayout.bottom
@@ -57,14 +57,8 @@ DrawerType2 {
anchors.right: parent.right
anchors.bottom: parent.bottom
property bool isFocusable: true
property int selectedIndex: LanguageModel.currentLanguageIndex
clip: true
reuseItems: true
ScrollBar.vertical: ScrollBarType {}
model: LanguageModel
ButtonGroup {

View File

@@ -15,7 +15,7 @@ import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
ListView {
ListViewType {
id: root
property int selectedIndex: ServersModel.defaultIndex
@@ -28,10 +28,6 @@ ListView {
model: ServersModel
ScrollBar.vertical: ScrollBarType {}
property bool isFocusable: true
Connections {
target: ServersModel
function onDefaultServerIndexChanged(serverIndex) {
@@ -39,9 +35,6 @@ ListView {
}
}
clip: true
reuseItems: true
delegate: Item {
id: menuContentDelegate
objectName: "menuContentDelegate"

View File

@@ -13,78 +13,64 @@ import "../Controls2"
import "../Controls2/TextTypes"
ListView {
ListViewType {
id: root
width: parent.width
height: root.contentItem.height
anchors.fill: parent
clip: true
reuseItems: true
delegate: ColumnLayout {
width: root.width
property bool isFocusable: false
LabelWithButtonType {
Layout.fillWidth: true
delegate: Item {
implicitWidth: root.width
implicitHeight: delegateContent.implicitHeight
text: name
descriptionText: description
rightImageSource: isInstalled ? "qrc:/images/controls/chevron-right.svg" : "qrc:/images/controls/download.svg"
ColumnLayout {
id: delegateContent
clickedFunction: function() {
if (isInstalled) {
var containerIndex = root.model.mapToSource(index)
ContainersModel.setProcessedContainerIndex(containerIndex)
anchors.fill: parent
LabelWithButtonType {
id: containerRadioButton
implicitWidth: parent.width
text: name
descriptionText: description
rightImageSource: isInstalled ? "qrc:/images/controls/chevron-right.svg" : "qrc:/images/controls/download.svg"
clickedFunction: function() {
if (isInstalled) {
var containerIndex = root.model.mapToSource(index)
ContainersModel.setProcessedContainerIndex(containerIndex)
if (serviceType !== ProtocolEnum.Other) {
if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) {
ProtocolsModel.updateModel(config)
PageController.goToPage(PageEnum.PageProtocolRaw)
return
}
}
switch (containerIndex) {
case ContainerEnum.Ipsec: {
if (serviceType !== ProtocolEnum.Other) {
if (config[ContainerProps.containerTypeToString(containerIndex)]["isThirdPartyConfig"]) {
ProtocolsModel.updateModel(config)
PageController.goToPage(PageEnum.PageProtocolRaw)
break
return
}
case ContainerEnum.Dns: {
PageController.goToPage(PageEnum.PageServiceDnsSettings)
break
}
default: {
ProtocolsModel.updateModel(config)
PageController.goToPage(PageEnum.PageSettingsServerProtocol)
}
}
} else {
ContainersModel.setProcessedContainerIndex(root.model.mapToSource(index))
InstallController.setShouldCreateServer(false)
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: false
switch (containerIndex) {
case ContainerEnum.Ipsec: {
ProtocolsModel.updateModel(config)
PageController.goToPage(PageEnum.PageProtocolRaw)
break
}
case ContainerEnum.Dns: {
PageController.goToPage(PageEnum.PageServiceDnsSettings)
break
}
default: {
ProtocolsModel.updateModel(config)
PageController.goToPage(PageEnum.PageSettingsServerProtocol)
}
}
} else {
ContainersModel.setProcessedContainerIndex(root.model.mapToSource(index))
InstallController.setShouldCreateServer(false)
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)
}
}
DividerType {}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: false
}
}
DividerType {}
}
}

View File

@@ -1,375 +0,0 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Dialogs
import QtCore
import SortFilterProxyModel 0.2
import PageEnum 1.0
import ContainerProps 1.0
import Style 1.0
import "./"
import "../Controls2"
import "../Controls2/TextTypes"
import "../Config"
import "../Components"
DrawerType2 {
id: root
property string headerText
property string configContentHeaderText
property string shareButtonText: qsTr("Share")
property string copyButtonText: qsTr("Copy")
property bool isSelfHostedConfig: true
property string configExtension: ".vpn"
property string configCaption: qsTr("Save AmneziaVPN config")
property string configFileName: "amnezia_config"
expandedHeight: parent.height * 0.9
onClosed: {
configExtension = ".vpn"
configCaption = qsTr("Save AmneziaVPN config")
configFileName = "amnezia_config"
}
expandedStateContent: Item {
implicitHeight: root.expandedHeight
Header2Type {
id: header
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 20
anchors.leftMargin: 16
anchors.rightMargin: 16
headerText: root.headerText
}
ListView {
id: listView
anchors.top: header.bottom
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
property bool isFocusable: true
ScrollBar.vertical: ScrollBarType {}
model: 1
clip: true
reuseItems: true
header: ColumnLayout {
width: listView.width
BasicButtonType {
id: shareButton
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
text: root.shareButtonText
leftImageSource: "qrc:/images/controls/share-2.svg"
clickedFunc: function() {
var fileName = ""
if (GC.isMobile()) {
fileName = configFileName + configExtension
} else {
fileName = SystemController.getFileName(configCaption,
qsTr("Config files (*" + configExtension + ")"),
StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/" + configFileName,
true,
configExtension)
}
if (fileName !== "") {
PageController.showBusyIndicator(true)
ExportController.exportConfig(fileName)
PageController.showBusyIndicator(false)
}
}
}
BasicButtonType {
id: copyConfigTextButton
Layout.fillWidth: true
Layout.topMargin: 8
Layout.leftMargin: 16
Layout.rightMargin: 16
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
disabledColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.paleGray
borderWidth: 1
text: root.copyButtonText
leftImageSource: "qrc:/images/controls/copy.svg"
Keys.onReturnPressed: { copyConfigTextButton.clicked() }
Keys.onEnterPressed: { copyConfigTextButton.clicked() }
}
BasicButtonType {
id: copyNativeConfigStringButton
Layout.fillWidth: true
Layout.topMargin: 8
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: false
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
disabledColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.paleGray
borderWidth: 1
text: qsTr("Copy config string")
leftImageSource: "qrc:/images/controls/copy.svg"
KeyNavigation.tab: showSettingsButton
}
BasicButtonType {
id: showSettingsButton
Layout.fillWidth: true
Layout.topMargin: 24
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: root.isSelfHostedConfig
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
disabledColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.paleGray
borderWidth: 1
text: qsTr("Show connection settings")
clickedFunc: function() {
configContentDrawer.openTriggered()
}
}
DrawerType2 {
id: configContentDrawer
parent: root.parent
anchors.fill: parent
expandedHeight: parent.height * 0.9
expandedStateContent: Item {
id: configContentContainer
implicitHeight: configContentDrawer.expandedHeight
Connections {
target: copyNativeConfigStringButton
function onClicked() {
nativeConfigString.selectAll()
nativeConfigString.copy()
nativeConfigString.select(0, 0)
PageController.showNotificationMessage(qsTr("Copied"))
}
}
Connections {
target: copyConfigTextButton
function onClicked() {
configText.selectAll()
configText.copy()
configText.select(0, 0)
PageController.showNotificationMessage(qsTr("Copied"))
header.forceActiveFocus()
}
}
BackButtonType {
id: backButton
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 16
backButtonFunction: function() { configContentDrawer.closeTriggered() }
}
FlickableType {
anchors.top: backButton.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
contentHeight: configContent.implicitHeight + configContent.anchors.topMargin + configContent.anchors.bottomMargin
ColumnLayout {
id: configContent
anchors.fill: parent
anchors.rightMargin: 16
anchors.leftMargin: 16
Header2Type {
id: configContentHeader
Layout.fillWidth: true
Layout.topMargin: 16
headerText: root.configContentHeaderText
}
TextField {
id: nativeConfigString
visible: false
text: ExportController.nativeConfigString
onTextChanged: {
copyNativeConfigStringButton.visible = nativeConfigString.text !== ""
}
}
TextArea {
id: configText
Layout.fillWidth: true
Layout.topMargin: 16
Layout.bottomMargin: 16
padding: 0
leftPadding: 0
height: 24
readOnly: true
activeFocusOnTab: false
color: AmneziaStyle.color.paleGray
selectionColor: AmneziaStyle.color.richBrown
selectedTextColor: AmneziaStyle.color.paleGray
font.pixelSize: 16
font.weight: Font.Medium
font.family: "PT Root UI VF"
text: ExportController.config
wrapMode: Text.Wrap
background: Rectangle {
color: AmneziaStyle.color.transparent
}
}
}
}
}
}
}
delegate: ColumnLayout {
width: listView.width
property bool isQrCodeVisible: root.isSelfHostedConfig ? ExportController.qrCodesCount > 0 : ApiConfigsController.qrCodesCount > 0
Rectangle {
id: qrCodeContainer
Layout.fillWidth: true
Layout.preferredHeight: width
Layout.topMargin: 20
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: isQrCodeVisible
color: "white"
Image {
anchors.fill: parent
smooth: false
source: root.isSelfHostedConfig ? (isQrCodeVisible ? ExportController.qrCodes[0] : "") :
(isQrCodeVisible ? ApiConfigsController.qrCodes[0] : "")
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
Timer {
property int index: 0
interval: 1000
running: isQrCodeVisible
repeat: true
onTriggered: {
if (isQrCodeVisible) {
index++
let qrCodesCount = root.isSelfHostedConfig ? ExportController.qrCodesCount : ApiConfigsController.qrCodesCount
if (index >= qrCodesCount) {
index = 0
}
parent.source = root.isSelfHostedConfig ? ExportController.qrCodes[index] : ApiConfigsController.qrCodes[index]
}
}
}
Behavior on source {
PropertyAnimation { duration: 200 }
}
}
}
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.bottomMargin: 32
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: isQrCodeVisible
horizontalAlignment: Text.AlignHCenter
text: qsTr("To read the QR code in the Amnezia app, select \"Add server\" → \"I have data to connect\" → \"QR code, key or settings file\"")
}
}
}
}
}

View File

@@ -1,220 +1,210 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Style 1.0
import "TextTypes"
Button {
id: root
property string hoveredColor: AmneziaStyle.color.lightGray
property string defaultColor: AmneziaStyle.color.paleGray
property string disabledColor: AmneziaStyle.color.charcoalGray
property string pressedColor: AmneziaStyle.color.mutedGray
property string textColor: AmneziaStyle.color.midnightBlack
property string borderColor: AmneziaStyle.color.paleGray
property string borderFocusedColor: AmneziaStyle.color.paleGray
property int borderWidth: 0
property int borderFocusedWidth: 1
property string leftImageSource
property string rightImageSource
property string leftImageColor: textColor
property bool changeLeftImageSize: true
property bool squareLeftSide: false
property FlickableType parentFlickable
property var clickedFunc
property alias buttonTextLabel: buttonText
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
implicitHeight: 56
hoverEnabled: true
onFocusChanged: {
if (root.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(this)
}
}
}
background: Rectangle {
id: focusBorder
color: AmneziaStyle.color.transparent
border.color: root.activeFocus ? root.borderFocusedColor : AmneziaStyle.color.transparent
border.width: root.activeFocus ? root.borderFocusedWidth : 0
anchors.fill: parent
radius: 16
Rectangle {
id: background
anchors.fill: focusBorder
anchors.margins: root.activeFocus ? 2 : 0
radius: root.activeFocus ? 14 : 16
color: {
if (root.enabled) {
if (root.pressed) {
return pressedColor
}
return root.hovered ? hoveredColor : defaultColor
} else {
return disabledColor
}
}
border.color: borderColor
border.width: borderWidth
Behavior on color {
PropertyAnimation { duration: 200 }
}
Rectangle {
visible: root.squareLeftSide
z: 1
width: parent.radius
height: parent.radius
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
color: {
if (root.enabled) {
if (root.pressed) {
return pressedColor
}
return root.hovered ? hoveredColor : defaultColor
} else {
return disabledColor
}
}
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
}
}
MouseArea {
anchors.fill: focusBorder
enabled: false
cursorShape: Qt.PointingHandCursor
}
contentItem: Item {
anchors.fill: focusBorder
implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight
RowLayout {
id: content
anchors.centerIn: parent
Image {
id: leftImage
source: root.leftImageSource
visible: root.leftImageSource === "" ? false : true
layer {
enabled: leftImageColor !== "" ? true : false
effect: ColorOverlay {
color: leftImageColor
}
}
Component.onCompleted: {
if (root.changeLeftImageSize) {
leftImage.Layout.preferredHeight = 20
leftImage.Layout.preferredWidth = 20
}
}
}
ButtonTextType {
id: buttonText
color: root.textColor
text: root.text
visible: root.text === "" ? false : true
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
}
Image {
Layout.preferredHeight: 20
Layout.preferredWidth: 20
source: root.rightImageSource
visible: root.rightImageSource === "" ? false : true
layer {
enabled: true
effect: ColorOverlay {
color: textColor
}
}
}
}
}
Keys.onEnterPressed: {
if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc()
}
}
Keys.onReturnPressed: {
if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc()
}
}
onClicked: {
if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc()
}
}
}
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Style 1.0
import "TextTypes"
Button {
id: root
property string hoveredColor: AmneziaStyle.color.lightGray
property string defaultColor: AmneziaStyle.color.paleGray
property string disabledColor: AmneziaStyle.color.charcoalGray
property string pressedColor: AmneziaStyle.color.mutedGray
property string textColor: AmneziaStyle.color.midnightBlack
property string borderColor: AmneziaStyle.color.paleGray
property string borderFocusedColor: AmneziaStyle.color.paleGray
property int borderWidth: 0
property int borderFocusedWidth: 1
property string leftImageSource
property string rightImageSource
property string leftImageColor: textColor
property bool changeLeftImageSize: true
property bool squareLeftSide: false
property var clickedFunc
property alias buttonTextLabel: buttonText
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
implicitHeight: 56
hoverEnabled: true
background: Rectangle {
id: focusBorder
color: AmneziaStyle.color.transparent
border.color: root.activeFocus ? root.borderFocusedColor : AmneziaStyle.color.transparent
border.width: root.activeFocus ? root.borderFocusedWidth : 0
anchors.fill: parent
radius: 16
Rectangle {
id: background
anchors.fill: focusBorder
anchors.margins: root.activeFocus ? 2 : 0
radius: root.activeFocus ? 14 : 16
color: {
if (root.enabled) {
if (root.pressed) {
return pressedColor
}
return root.hovered ? hoveredColor : defaultColor
} else {
return disabledColor
}
}
border.color: borderColor
border.width: borderWidth
Behavior on color {
PropertyAnimation { duration: 200 }
}
Rectangle {
visible: root.squareLeftSide
z: 1
width: parent.radius
height: parent.radius
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
color: {
if (root.enabled) {
if (root.pressed) {
return pressedColor
}
return root.hovered ? hoveredColor : defaultColor
} else {
return disabledColor
}
}
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
}
}
MouseArea {
anchors.fill: focusBorder
enabled: false
cursorShape: Qt.PointingHandCursor
}
contentItem: Item {
anchors.fill: focusBorder
implicitWidth: content.implicitWidth
implicitHeight: content.implicitHeight
RowLayout {
id: content
anchors.centerIn: parent
Image {
id: leftImage
source: root.leftImageSource
visible: root.leftImageSource === "" ? false : true
layer {
enabled: leftImageColor !== "" ? true : false
effect: ColorOverlay {
color: leftImageColor
}
}
Component.onCompleted: {
if (root.changeLeftImageSize) {
leftImage.Layout.preferredHeight = 20
leftImage.Layout.preferredWidth = 20
}
}
}
ButtonTextType {
id: buttonText
color: root.textColor
text: root.text
visible: root.text === "" ? false : true
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
}
Image {
Layout.preferredHeight: 20
Layout.preferredWidth: 20
source: root.rightImageSource
visible: root.rightImageSource === "" ? false : true
layer {
enabled: true
effect: ColorOverlay {
color: textColor
}
}
}
}
}
Keys.onEnterPressed: {
if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc()
}
}
Keys.onReturnPressed: {
if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc()
}
}
onClicked: {
if (root.clickedFunc && typeof root.clickedFunc === "function") {
root.clickedFunc()
}
}
}

View File

@@ -1,203 +1,185 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Style 1.0
import "TextTypes"
Button {
id: root
property string headerText
property string bodyText
property string footerText
property string hoveredColor: AmneziaStyle.color.slateGray
property string defaultColor: AmneziaStyle.color.onyxBlack
property string textColor: AmneziaStyle.color.midnightBlack
property string rightImageSource
property string rightImageColor: AmneziaStyle.color.paleGray
property string leftImageSource
property real textOpacity: 1.0
property alias focusItem: rightImage
property FlickableType parentFlickable
hoverEnabled: true
background: Rectangle {
id: backgroundRect
anchors.fill: parent
radius: 16
color: defaultColor
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
function ensureVisible(item) {
if (item.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(root)
}
}
}
onFocusChanged: {
ensureVisible(root)
}
focusItem.onFocusChanged: {
root.ensureVisible(focusItem)
}
contentItem: Item {
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: content.implicitHeight
RowLayout {
id: content
anchors.fill: parent
Image {
id: leftImage
source: leftImageSource
visible: leftImageSource !== ""
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.topMargin: 24
Layout.bottomMargin: 24
Layout.leftMargin: 24
}
ColumnLayout {
ListItemTitleType {
text: root.headerText
visible: text !== ""
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.topMargin: 16
Layout.bottomMargin: root.bodyText !== "" ? 0 : 16
opacity: root.textOpacity
}
CaptionTextType {
text: root.bodyText
visible: text !== ""
color: AmneziaStyle.color.mutedGray
textFormat: Text.RichText
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: root.footerText !== "" ? 0 : 16
opacity: root.textOpacity
}
ButtonTextType {
text: root.footerText
visible: text !== ""
color: AmneziaStyle.color.mutedGray
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.topMargin: 16
Layout.bottomMargin: 16
opacity: root.textOpacity
}
}
ImageButtonType {
id: rightImage
implicitWidth: 40
implicitHeight: 40
hoverEnabled: false
image: rightImageSource
imageColor: rightImageColor
visible: rightImageSource ? true : false
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.topMargin: 16
Layout.bottomMargin: 16
Layout.rightMargin: 16
Rectangle {
id: rightImageBackground
anchors.fill: parent
radius: 12
color: "transparent"
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
onClicked: {
root.clicked()
}
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
enabled: root.enabled
onEntered: {
backgroundRect.color = root.hoveredColor
if (rightImageSource) {
rightImageBackground.color = rightImage.hoveredColor
}
root.textOpacity = 0.8
}
onExited: {
backgroundRect.color = root.defaultColor
if (rightImageSource) {
rightImageBackground.color = rightImage.defaultColor
}
root.textOpacity = 1
}
onPressedChanged: {
if (rightImageSource) {
rightImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor
}
root.textOpacity = 0.7
}
onClicked: {
root.clicked()
}
}
}
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Style 1.0
import "TextTypes"
Button {
id: root
property string headerText
property string bodyText
property string footerText
property string hoveredColor: AmneziaStyle.color.slateGray
property string defaultColor: AmneziaStyle.color.onyxBlack
property string textColor: AmneziaStyle.color.midnightBlack
property string rightImageSource
property string rightImageColor: AmneziaStyle.color.paleGray
property string leftImageSource
property real textOpacity: 1.0
property alias focusItem: rightImage
hoverEnabled: true
background: Rectangle {
id: backgroundRect
anchors.fill: parent
radius: 16
color: defaultColor
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
contentItem: Item {
anchors.left: parent.left
anchors.right: parent.right
implicitHeight: content.implicitHeight
RowLayout {
id: content
anchors.fill: parent
Image {
id: leftImage
source: leftImageSource
visible: leftImageSource !== ""
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.topMargin: 24
Layout.bottomMargin: 24
Layout.leftMargin: 24
}
ColumnLayout {
ListItemTitleType {
text: root.headerText
visible: text !== ""
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.topMargin: 16
Layout.bottomMargin: root.bodyText !== "" ? 0 : 16
opacity: root.textOpacity
}
CaptionTextType {
text: root.bodyText
visible: text !== ""
color: AmneziaStyle.color.mutedGray
textFormat: Text.RichText
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: root.footerText !== "" ? 0 : 16
opacity: root.textOpacity
}
ButtonTextType {
text: root.footerText
visible: text !== ""
color: AmneziaStyle.color.mutedGray
Layout.fillWidth: true
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.topMargin: 16
Layout.bottomMargin: 16
opacity: root.textOpacity
}
}
ImageButtonType {
id: rightImage
implicitWidth: 40
implicitHeight: 40
hoverEnabled: false
image: rightImageSource
imageColor: rightImageColor
visible: rightImageSource ? true : false
Layout.alignment: Qt.AlignRight | Qt.AlignTop
Layout.topMargin: 16
Layout.bottomMargin: 16
Layout.rightMargin: 16
Rectangle {
id: rightImageBackground
anchors.fill: parent
radius: 12
color: "transparent"
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
onClicked: {
root.clicked()
}
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
enabled: root.enabled
onEntered: {
backgroundRect.color = root.hoveredColor
if (rightImageSource) {
rightImageBackground.color = rightImage.hoveredColor
}
root.textOpacity = 0.8
}
onExited: {
backgroundRect.color = root.defaultColor
if (rightImageSource) {
rightImageBackground.color = rightImage.defaultColor
}
root.textOpacity = 1
}
onPressedChanged: {
if (rightImageSource) {
rightImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor
}
root.textOpacity = 0.7
}
onClicked: {
root.clicked()
}
}
}

View File

@@ -1,170 +1,189 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Style 1.0
import "TextTypes"
CheckBox {
id: root
property string descriptionText
property string descriptionTextColor: AmneziaStyle.color.mutedGray
property string descriptionTextDisabledColor: AmneziaStyle.color.charcoalGray
property string textColor: AmneziaStyle.color.paleGray
property string textDisabledColor: AmneziaStyle.color.mutedGray
property string hoveredColor: AmneziaStyle.color.barelyTranslucentWhite
property string defaultColor: AmneziaStyle.color.transparent
property string pressedColor: AmneziaStyle.color.barelyTranslucentWhite
property string defaultBorderColor: AmneziaStyle.color.paleGray
property string checkedBorderColor: AmneziaStyle.color.goldenApricot
property string checkedBorderDisabledColor: AmneziaStyle.color.deepBrown
property string borderFocusedColor: AmneziaStyle.color.paleGray
property string checkedImageColor: AmneziaStyle.color.goldenApricot
property string pressedImageColor: AmneziaStyle.color.burntOrange
property string defaultImageColor: AmneziaStyle.color.transparent
property string checkedDisabledImageColor: AmneziaStyle.color.mutedBrown
property string imageSource: "qrc:/images/controls/check.svg"
property var parentFlickable
onFocusChanged: {
if (root.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(root)
}
}
}
hoverEnabled: enabled ? true : false
focusPolicy: Qt.NoFocus
background: Rectangle {
color: AmneziaStyle.color.transparent
border.color: root.focus ? borderFocusedColor : AmneziaStyle.color.transparent
border.width: 1
radius: 16
}
indicator: Rectangle {
id: background
anchors.verticalCenter: parent.verticalCenter
implicitWidth: 56
implicitHeight: 56
radius: 16
color: {
if (root.hovered) {
return hoveredColor
}
return defaultColor
}
Behavior on color {
PropertyAnimation { duration: 200 }
}
Rectangle {
id: imageBorder
anchors.centerIn: parent
width: 24
height: 24
color: AmneziaStyle.color.transparent
border.color: root.checked ?
(root.enabled ?
checkedBorderColor :
checkedBorderDisabledColor) :
defaultBorderColor
border.width: 1
radius: 4
Image {
anchors.centerIn: parent
source: root.pressed ? imageSource : root.checked ? imageSource : ""
layer {
enabled: true
effect: ColorOverlay {
color: {
if (root.pressed) {
return root.pressedImageColor
} else if (root.checked) {
if (root.enabled) {
return root.checkedImageColor
} else {
return root.checkedDisabledImageColor
}
} else {
return root.defaultImageColor
}
}
}
}
}
}
}
contentItem: Item {
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 8 + background.width
implicitHeight: content.implicitHeight
ColumnLayout {
id: content
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 4
ListItemTitleType {
Layout.fillWidth: true
text: root.text
color: root.enabled ? root.textColor : root.textDisabledColor
}
CaptionTextType {
id: description
Layout.fillWidth: true
text: root.descriptionText
color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor
visible: root.descriptionText !== ""
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: false
}
Keys.onEnterPressed: {
root.checked = !root.checked
}
Keys.onReturnPressed: {
root.checked = !root.checked
}
}
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Style 1.0
import "TextTypes"
CheckBox {
id: root
property string descriptionText
property string descriptionTextColor: AmneziaStyle.color.mutedGray
property string descriptionTextDisabledColor: AmneziaStyle.color.charcoalGray
property string textColor: AmneziaStyle.color.paleGray
property string textDisabledColor: AmneziaStyle.color.mutedGray
property string hoveredColor: AmneziaStyle.color.barelyTranslucentWhite
property string defaultColor: AmneziaStyle.color.transparent
property string pressedColor: AmneziaStyle.color.barelyTranslucentWhite
property string defaultBorderColor: AmneziaStyle.color.paleGray
property string checkedBorderColor: AmneziaStyle.color.goldenApricot
property string checkedBorderDisabledColor: AmneziaStyle.color.deepBrown
property string borderFocusedColor: AmneziaStyle.color.paleGray
property string checkedImageColor: AmneziaStyle.color.goldenApricot
property string pressedImageColor: AmneziaStyle.color.burntOrange
property string defaultImageColor: AmneziaStyle.color.transparent
property string checkedDisabledImageColor: AmneziaStyle.color.mutedBrown
property string imageSource: "qrc:/images/controls/check.svg"
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
hoverEnabled: enabled ? true : false
focusPolicy: Qt.NoFocus
background: Rectangle {
color: AmneziaStyle.color.transparent
border.color: root.focus ? borderFocusedColor : AmneziaStyle.color.transparent
border.width: 1
radius: 16
}
indicator: Rectangle {
id: background
anchors.verticalCenter: parent.verticalCenter
implicitWidth: 56
implicitHeight: 56
radius: 16
color: {
if (root.hovered) {
return hoveredColor
}
return defaultColor
}
Behavior on color {
PropertyAnimation { duration: 200 }
}
Rectangle {
id: imageBorder
anchors.centerIn: parent
width: 24
height: 24
color: AmneziaStyle.color.transparent
border.color: root.checked ?
(root.enabled ?
checkedBorderColor :
checkedBorderDisabledColor) :
defaultBorderColor
border.width: 1
radius: 4
Image {
anchors.centerIn: parent
source: root.pressed ? imageSource : root.checked ? imageSource : ""
layer {
enabled: true
effect: ColorOverlay {
color: {
if (root.pressed) {
return root.pressedImageColor
} else if (root.checked) {
if (root.enabled) {
return root.checkedImageColor
} else {
return root.checkedDisabledImageColor
}
} else {
return root.defaultImageColor
}
}
}
}
}
}
}
contentItem: Item {
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 8 + background.width
implicitHeight: content.implicitHeight
ColumnLayout {
id: content
anchors.left: parent.left
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: 4
ListItemTitleType {
Layout.fillWidth: true
text: root.text
color: root.enabled ? root.textColor : root.textDisabledColor
}
CaptionTextType {
id: description
Layout.fillWidth: true
text: root.descriptionText
color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor
visible: root.descriptionText !== ""
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: false
}
Keys.onEnterPressed: event => handleSwitch(event)
Keys.onReturnPressed: event => handleSwitch(event)
Keys.onSpacePressed: event => handleSwitch(event)
function handleSwitch(event) {
if (!event.isAutoRepeat) {
root.checked = !root.checked
root.checkedChanged()
}
event.accepted = true
}
}

View File

@@ -27,6 +27,5 @@ Flickable {
ScrollBar.vertical: ScrollBarType {
id: scrollBar
policy: fl.height >= fl.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn
}
}

View File

@@ -1,328 +1,309 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Style 1.0
import "TextTypes"
Item {
id: root
property string text
property int textMaximumLineCount: 2
property int textElide: Qt.ElideRight
property string descriptionText
property var clickedFunction
property string buttonImageSource
property string rightImageSource
property string leftImageSource
property bool isLeftImageHoverEnabled: true
property bool isSmallLeftImage: false
property alias rightButton: rightImage
property alias eyeButton: eyeImage
property FlickableType parentFlickable
property string textColor: AmneziaStyle.color.paleGray
property string textDisabledColor: AmneziaStyle.color.mutedGray
property string descriptionColor: AmneziaStyle.color.mutedGray
property string descriptionDisabledColor: AmneziaStyle.color.charcoalGray
property real textOpacity: 1.0
property string borderFocusedColor: AmneziaStyle.color.paleGray
property int borderFocusedWidth: 1
property string rightImageColor: AmneziaStyle.color.paleGray
property bool descriptionOnTop: false
property bool hideDescription: true
property bool isFocusable: !(eyeImage.visible || rightImage.visible) // TODO: this component already has focusable items
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
implicitWidth: content.implicitWidth + content.anchors.topMargin + content.anchors.bottomMargin
implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin
onFocusChanged: {
if (root.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(root)
}
}
}
Connections {
target: rightImage
function onFocusChanged() {
if (rightImage.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(root)
}
}
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: root.enabled
onEntered: {
if (rightImageSource) {
rightImageBackground.color = rightImage.hoveredColor
} else if (leftImageSource) {
leftImageBackground.color = rightImage.hoveredColor
}
root.textOpacity = 0.8
}
onExited: {
if (rightImageSource) {
rightImageBackground.color = rightImage.defaultColor
} else if (leftImageSource) {
leftImageBackground.color = rightImage.defaultColor
}
root.textOpacity = 1
}
onPressedChanged: {
if (rightImageSource) {
rightImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor
} else if (leftImageSource) {
leftImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor
}
root.textOpacity = 0.7
}
onClicked: {
if (clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
}
}
RowLayout {
id: content
anchors.fill: parent
anchors.leftMargin: 16
anchors.rightMargin: 16
anchors.topMargin: 16
anchors.bottomMargin: 16
Rectangle {
id: leftImageBackground
visible: leftImageSource ? true : false
Layout.preferredHeight: (rightImageSource || !isLeftImageHoverEnabled || isSmallLeftImage) ? 40 : 56
Layout.preferredWidth: (rightImageSource || !isLeftImageHoverEnabled || isSmallLeftImage)? 40 : 56
Layout.rightMargin: isSmallLeftImage ? 8 : (rightImageSource || !isLeftImageHoverEnabled) ? 16 : 0
radius: 12
color: AmneziaStyle.color.transparent
Behavior on color {
PropertyAnimation { duration: 200 }
}
Image {
id: leftImage
anchors.centerIn: parent
source: leftImageSource
}
}
ColumnLayout {
property real textLineHeight: 21.6
property real descriptionTextLineHeight: 16
property int textPixelSize: 18
property int descriptionTextSize: 13
ListItemTitleType {
text: root.text
color: {
if (root.enabled) {
return root.descriptionOnTop ? root.descriptionColor : root.textColor
} else {
return root.descriptionOnTop ? root.descriptionDisabledColor : root.textDisabledColor
}
}
maximumLineCount: root.textMaximumLineCount
elide: root.textElide
opacity: root.textOpacity
Layout.fillWidth: true
lineHeight: root.descriptionOnTop ? parent.descriptionTextLineHeight : parent.textLineHeight
font.pixelSize: root.descriptionOnTop ? parent.descriptionTextSize : parent.textPixelSize
font.letterSpacing: root.descriptionOnTop ? 0.02 : 0
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
Behavior on opacity {
PropertyAnimation { duration: 200 }
}
}
CaptionTextType {
id: description
text: (eyeImage.visible && hideDescription) ? replaceWithAsterisks(root.descriptionText) : root.descriptionText
color: {
if (root.enabled) {
return root.descriptionOnTop ? root.textColor : root.descriptionColor
} else {
return root.descriptionOnTop ? root.textDisabledColor : root.descriptionDisabledColor
}
}
opacity: root.textOpacity
visible: root.descriptionText !== ""
Layout.fillWidth: true
lineHeight: root.descriptionOnTop ? parent.textLineHeight : parent.descriptionTextLineHeight
font.pixelSize: root.descriptionOnTop ? parent.textPixelSize : parent.descriptionTextSize
font.letterSpacing: root.descriptionOnTop ? 0 : 0.02
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
Behavior on opacity {
PropertyAnimation { duration: 200 }
}
function replaceWithAsterisks(input) {
return '*'.repeat(input.length)
}
}
}
ImageButtonType {
id: eyeImage
visible: buttonImageSource !== ""
implicitWidth: 40
implicitHeight: 40
hoverEnabled: true
image: buttonImageSource
imageColor: rightImageColor
Layout.alignment: Qt.AlignRight
Rectangle {
id: eyeImageBackground
anchors.fill: parent
radius: 12
color: AmneziaStyle.color.transparent
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
onClicked: {
hideDescription = !hideDescription
}
Keys.onEnterPressed: {
clicked()
}
Keys.onReturnPressed: {
clicked()
}
}
ImageButtonType {
id: rightImage
implicitWidth: 40
implicitHeight: 40
hoverEnabled: false
image: rightImageSource
imageColor: rightImageColor
visible: rightImageSource ? true : false
Layout.alignment: Qt.AlignRight
Rectangle {
id: rightImageBackground
anchors.fill: parent
radius: 12
color: AmneziaStyle.color.transparent
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
onClicked: {
if (clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
}
}
}
Rectangle {
id: background
anchors.fill: root
color: AmneziaStyle.color.transparent
border.color: root.activeFocus ? root.borderFocusedColor : AmneziaStyle.color.transparent
border.width: root.activeFocus ? root.borderFocusedWidth : 0
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
Keys.onEnterPressed: {
if (clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
}
Keys.onReturnPressed: {
if (clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
}
}
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Style 1.0
import "TextTypes"
Item {
id: root
// property alias focusObjectName: eyeImage.objectName
property string text
property int textMaximumLineCount: 2
property int textElide: Qt.ElideRight
property string descriptionText
property var clickedFunction
property string buttonImageSource
property string rightImageSource
property string leftImageSource
property bool isLeftImageHoverEnabled: true
property bool isSmallLeftImage: false
property alias rightButton: rightImage
property alias eyeButton: eyeImage
property string textColor: AmneziaStyle.color.paleGray
property string textDisabledColor: AmneziaStyle.color.mutedGray
property string descriptionColor: AmneziaStyle.color.mutedGray
property string descriptionDisabledColor: AmneziaStyle.color.charcoalGray
property real textOpacity: 1.0
property string borderFocusedColor: AmneziaStyle.color.paleGray
property int borderFocusedWidth: 1
property string rightImageColor: AmneziaStyle.color.paleGray
property bool descriptionOnTop: false
property bool hideDescription: true
property bool isFocusable: !(eyeImage.visible || rightImage.visible) // TODO: this component already has focusable items
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
implicitWidth: content.implicitWidth + content.anchors.topMargin + content.anchors.bottomMargin
implicitHeight: content.implicitHeight + content.anchors.leftMargin + content.anchors.rightMargin
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
hoverEnabled: root.enabled
onEntered: {
if (rightImageSource) {
rightImageBackground.color = rightImage.hoveredColor
} else if (leftImageSource) {
leftImageBackground.color = rightImage.hoveredColor
}
root.textOpacity = 0.8
}
onExited: {
if (rightImageSource) {
rightImageBackground.color = rightImage.defaultColor
} else if (leftImageSource) {
leftImageBackground.color = rightImage.defaultColor
}
root.textOpacity = 1
}
onPressedChanged: {
if (rightImageSource) {
rightImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor
} else if (leftImageSource) {
leftImageBackground.color = pressed ? rightImage.pressedColor : entered ? rightImage.hoveredColor : rightImage.defaultColor
}
root.textOpacity = 0.7
}
onClicked: {
if (clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
}
}
RowLayout {
id: content
anchors.fill: parent
anchors.leftMargin: 16
anchors.rightMargin: 16
anchors.topMargin: 16
anchors.bottomMargin: 16
Rectangle {
id: leftImageBackground
visible: leftImageSource ? true : false
Layout.preferredHeight: (rightImageSource || !isLeftImageHoverEnabled || isSmallLeftImage) ? 40 : 56
Layout.preferredWidth: (rightImageSource || !isLeftImageHoverEnabled || isSmallLeftImage)? 40 : 56
Layout.rightMargin: isSmallLeftImage ? 8 : (rightImageSource || !isLeftImageHoverEnabled) ? 16 : 0
radius: 12
color: AmneziaStyle.color.transparent
Behavior on color {
PropertyAnimation { duration: 200 }
}
Image {
id: leftImage
anchors.centerIn: parent
source: leftImageSource
}
}
ColumnLayout {
property real textLineHeight: 21.6
property real descriptionTextLineHeight: 16
property int textPixelSize: 18
property int descriptionTextSize: 13
ListItemTitleType {
text: root.text
color: {
if (root.enabled) {
return root.descriptionOnTop ? root.descriptionColor : root.textColor
} else {
return root.descriptionOnTop ? root.descriptionDisabledColor : root.textDisabledColor
}
}
maximumLineCount: root.textMaximumLineCount
elide: root.textElide
opacity: root.textOpacity
Layout.fillWidth: true
lineHeight: root.descriptionOnTop ? parent.descriptionTextLineHeight : parent.textLineHeight
font.pixelSize: root.descriptionOnTop ? parent.descriptionTextSize : parent.textPixelSize
font.letterSpacing: root.descriptionOnTop ? 0.02 : 0
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
Behavior on opacity {
PropertyAnimation { duration: 200 }
}
}
CaptionTextType {
id: description
text: (eyeImage.visible && hideDescription) ? replaceWithAsterisks(root.descriptionText) : root.descriptionText
color: {
if (root.enabled) {
return root.descriptionOnTop ? root.textColor : root.descriptionColor
} else {
return root.descriptionOnTop ? root.textDisabledColor : root.descriptionDisabledColor
}
}
opacity: root.textOpacity
visible: root.descriptionText !== ""
Layout.fillWidth: true
lineHeight: root.descriptionOnTop ? parent.textLineHeight : parent.descriptionTextLineHeight
font.pixelSize: root.descriptionOnTop ? parent.textPixelSize : parent.descriptionTextSize
font.letterSpacing: root.descriptionOnTop ? 0 : 0.02
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
Behavior on opacity {
PropertyAnimation { duration: 200 }
}
function replaceWithAsterisks(input) {
return '*'.repeat(input.length)
}
}
}
ImageButtonType {
id: eyeImage
visible: buttonImageSource !== ""
implicitWidth: 40
implicitHeight: 40
hoverEnabled: true
image: buttonImageSource
imageColor: rightImageColor
Layout.alignment: Qt.AlignRight
Rectangle {
id: eyeImageBackground
anchors.fill: parent
radius: 12
color: AmneziaStyle.color.transparent
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
onClicked: {
hideDescription = !hideDescription
}
Keys.onEnterPressed: {
clicked()
}
Keys.onReturnPressed: {
clicked()
}
}
ImageButtonType {
id: rightImage
implicitWidth: 40
implicitHeight: 40
hoverEnabled: false
image: rightImageSource
imageColor: rightImageColor
visible: rightImageSource ? true : false
Layout.alignment: Qt.AlignRight
Rectangle {
id: rightImageBackground
anchors.fill: parent
radius: 12
color: AmneziaStyle.color.transparent
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
onClicked: {
if (clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
}
}
}
Rectangle {
id: background
anchors.fill: root
color: AmneziaStyle.color.transparent
border.color: root.activeFocus ? root.borderFocusedColor : AmneziaStyle.color.transparent
border.width: root.activeFocus ? root.borderFocusedWidth : 0
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
Keys.onEnterPressed: {
if (clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
}
Keys.onReturnPressed: {
if (clickedFunction && typeof clickedFunction === "function") {
clickedFunction()
}
}
}

View File

@@ -6,33 +6,16 @@ ListView {
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
ScrollBar.vertical: ScrollBarType {}
clip: true
reuseItems: true
snapMode: ListView.SnapToItem
function findChildWithObjectName(items, name) {
for (var i = 0; i < items.length; ++i) {
if (items[i].objectName === name)
return items[i];
}
return null;
}
}

View File

@@ -6,7 +6,7 @@ import Style 1.0
import "TextTypes"
ListView {
ListViewType {
id: root
property var rootWidth
@@ -25,13 +25,6 @@ ListView {
width: rootWidth
height: root.contentItem.height
clip: true
reuseItems: true
property bool isFocusable: true
ScrollBar.vertical: ScrollBarType {}
ButtonGroup {
id: buttonGroup
}

View File

@@ -7,5 +7,5 @@ import "../Controls2"
ScrollBar {
id: root
policy: parent.height >= parent.contentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn
policy: ScrollBar.AsNeeded
}

View File

@@ -1,172 +1,162 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Style 1.0
import "TextTypes"
Switch {
id: root
property alias descriptionText: description.text
property string descriptionTextColor: AmneziaStyle.color.mutedGray
property string descriptionTextDisabledColor: AmneziaStyle.color.charcoalGray
property string textColor: AmneziaStyle.color.paleGray
property string textDisabledColor: AmneziaStyle.color.mutedGray
property string checkedIndicatorColor: AmneziaStyle.color.richBrown
property string defaultIndicatorColor: AmneziaStyle.color.transparent
property string checkedDisabledIndicatorColor: AmneziaStyle.color.deepBrown
property string borderFocusedColor: AmneziaStyle.color.paleGray
property int borderFocusedWidth: 1
property string checkedIndicatorBorderColor: AmneziaStyle.color.richBrown
property string defaultIndicatorBorderColor: AmneziaStyle.color.charcoalGray
property string checkedDisabledIndicatorBorderColor: AmneziaStyle.color.deepBrown
property string checkedInnerCircleColor: AmneziaStyle.color.goldenApricot
property string defaultInnerCircleColor: AmneziaStyle.color.paleGray
property string checkedDisabledInnerCircleColor: AmneziaStyle.color.mutedBrown
property string defaultDisabledInnerCircleColor: AmneziaStyle.color.charcoalGray
property string hoveredIndicatorBackgroundColor: AmneziaStyle.color.translucentWhite
property string defaultIndicatorBackgroundColor: AmneziaStyle.color.transparent
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
hoverEnabled: enabled ? true : false
focusPolicy: Qt.TabFocus
property FlickableType parentFlickable: null
onFocusChanged: {
if (root.activeFocus) {
if (root.parentFlickable) {
root.parentFlickable.ensureVisible(root)
}
}
}
indicator: Rectangle {
id: switcher
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
implicitWidth: 52
implicitHeight: 32
radius: 16
color: root.checked ? (root.enabled ? root.checkedIndicatorColor : root.checkedDisabledIndicatorColor)
: root.defaultIndicatorColor
border.color: root.activeFocus ? root.borderFocusedColor : (root.checked ? (root.enabled ? root.checkedIndicatorBorderColor : root.checkedDisabledIndicatorBorderColor)
: root.defaultIndicatorBorderColor)
Behavior on color {
PropertyAnimation { duration: 200 }
}
Behavior on border.color {
PropertyAnimation { duration: 200 }
}
Rectangle {
id: innerCircle
anchors.verticalCenter: parent.verticalCenter
x: root.checked ? parent.width - width - 4 : 8
width: root.checked ? 24 : 16
height: root.checked ? 24 : 16
radius: 23
color: root.checked ? (root.enabled ? root.checkedInnerCircleColor : root.checkedDisabledInnerCircleColor)
: (root.enabled ? root.defaultInnerCircleColor : root.defaultDisabledInnerCircleColor)
Behavior on x {
PropertyAnimation { duration: 200 }
}
}
Rectangle {
anchors.centerIn: innerCircle
width: 40
height: 40
radius: 23
color: root.hovered ? root.hoveredIndicatorBackgroundColor : root.defaultIndicatorBackgroundColor
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
}
contentItem: ColumnLayout {
id: content
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
ListItemTitleType {
Layout.fillWidth: true
rightPadding: indicator.width
text: root.text
color: root.enabled ? root.textColor : root.textDisabledColor
}
CaptionTextType {
id: description
Layout.fillWidth: true
rightPadding: indicator.width
color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor
visible: text !== ""
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: false
}
Keys.onEnterPressed: event => handleSwitch(event)
Keys.onReturnPressed: event => handleSwitch(event)
Keys.onSpacePressed: event => handleSwitch(event)
function handleSwitch(event) {
if (!event.isAutoRepeat) {
root.checked = !root.checked
root.checkedChanged()
}
event.accepted = true
}
}
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Style 1.0
import "TextTypes"
Switch {
id: root
property alias descriptionText: description.text
property string descriptionTextColor: AmneziaStyle.color.mutedGray
property string descriptionTextDisabledColor: AmneziaStyle.color.charcoalGray
property string textColor: AmneziaStyle.color.paleGray
property string textDisabledColor: AmneziaStyle.color.mutedGray
property string checkedIndicatorColor: AmneziaStyle.color.richBrown
property string defaultIndicatorColor: AmneziaStyle.color.transparent
property string checkedDisabledIndicatorColor: AmneziaStyle.color.deepBrown
property string borderFocusedColor: AmneziaStyle.color.paleGray
property int borderFocusedWidth: 1
property string checkedIndicatorBorderColor: AmneziaStyle.color.richBrown
property string defaultIndicatorBorderColor: AmneziaStyle.color.charcoalGray
property string checkedDisabledIndicatorBorderColor: AmneziaStyle.color.deepBrown
property string checkedInnerCircleColor: AmneziaStyle.color.goldenApricot
property string defaultInnerCircleColor: AmneziaStyle.color.paleGray
property string checkedDisabledInnerCircleColor: AmneziaStyle.color.mutedBrown
property string defaultDisabledInnerCircleColor: AmneziaStyle.color.charcoalGray
property string hoveredIndicatorBackgroundColor: AmneziaStyle.color.translucentWhite
property string defaultIndicatorBackgroundColor: AmneziaStyle.color.transparent
property bool isFocusable: true
Keys.onTabPressed: {
FocusController.nextKeyTabItem()
}
Keys.onBacktabPressed: {
FocusController.previousKeyTabItem()
}
Keys.onUpPressed: {
FocusController.nextKeyUpItem()
}
Keys.onDownPressed: {
FocusController.nextKeyDownItem()
}
Keys.onLeftPressed: {
FocusController.nextKeyLeftItem()
}
Keys.onRightPressed: {
FocusController.nextKeyRightItem()
}
hoverEnabled: enabled ? true : false
focusPolicy: Qt.TabFocus
indicator: Rectangle {
id: switcher
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
implicitWidth: 52
implicitHeight: 32
radius: 16
color: root.checked ? (root.enabled ? root.checkedIndicatorColor : root.checkedDisabledIndicatorColor)
: root.defaultIndicatorColor
border.color: root.activeFocus ? root.borderFocusedColor : (root.checked ? (root.enabled ? root.checkedIndicatorBorderColor : root.checkedDisabledIndicatorBorderColor)
: root.defaultIndicatorBorderColor)
Behavior on color {
PropertyAnimation { duration: 200 }
}
Behavior on border.color {
PropertyAnimation { duration: 200 }
}
Rectangle {
id: innerCircle
anchors.verticalCenter: parent.verticalCenter
x: root.checked ? parent.width - width - 4 : 8
width: root.checked ? 24 : 16
height: root.checked ? 24 : 16
radius: 23
color: root.checked ? (root.enabled ? root.checkedInnerCircleColor : root.checkedDisabledInnerCircleColor)
: (root.enabled ? root.defaultInnerCircleColor : root.defaultDisabledInnerCircleColor)
Behavior on x {
PropertyAnimation { duration: 200 }
}
}
Rectangle {
anchors.centerIn: innerCircle
width: 40
height: 40
radius: 23
color: root.hovered ? root.hoveredIndicatorBackgroundColor : root.defaultIndicatorBackgroundColor
Behavior on color {
PropertyAnimation { duration: 200 }
}
}
}
contentItem: ColumnLayout {
id: content
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
ListItemTitleType {
Layout.fillWidth: true
rightPadding: indicator.width
text: root.text
color: root.enabled ? root.textColor : root.textDisabledColor
}
CaptionTextType {
id: description
Layout.fillWidth: true
rightPadding: indicator.width
color: root.enabled ? root.descriptionTextColor : root.descriptionTextDisabledColor
visible: text !== ""
}
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
enabled: false
}
Keys.onEnterPressed: event => handleSwitch(event)
Keys.onReturnPressed: event => handleSwitch(event)
Keys.onSpacePressed: event => handleSwitch(event)
function handleSwitch(event) {
if (!event.isAutoRepeat) {
root.checked = !root.checked
root.checkedChanged()
}
event.accepted = true
}
}

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