Repository: MatsuriDayo/nekoray Branch: main Commit: adef6cd4af7d Files: 230 Total size: 1.1 MB Directory structure: gitextract_ivvmk2ct/ ├── .clang-format ├── .clang-tidy ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report-en.md │ │ ├── bug-report-zh_cn.md │ │ ├── feature_request-en.md │ │ └── feature_request-zh_cn.md │ └── workflows/ │ ├── build-nekoray-cmake.yml │ └── update-pkgbuild.yml ├── .gitignore ├── .gitmodules ├── 3rdparty/ │ ├── QThreadCreateThread.hpp │ ├── QtExtKeySequenceEdit.cpp │ ├── QtExtKeySequenceEdit.h │ ├── RunGuard.hpp │ ├── VT100Parser.hpp │ ├── WinCommander.cpp │ ├── WinCommander.hpp │ ├── ZxingQtReader.hpp │ ├── base64.cpp │ ├── base64.h │ ├── fix_old_qt.h │ ├── qrcodegen.cpp │ ├── qrcodegen.hpp │ ├── qscopeguard.h │ └── qv2ray/ │ ├── v2/ │ │ ├── components/ │ │ │ └── proxy/ │ │ │ ├── QvProxyConfigurator.cpp │ │ │ └── QvProxyConfigurator.hpp │ │ └── ui/ │ │ ├── QvAutoCompleteTextEdit.cpp │ │ ├── QvAutoCompleteTextEdit.hpp │ │ └── widgets/ │ │ ├── common/ │ │ │ ├── QJsonModel.cpp │ │ │ └── QJsonModel.hpp │ │ └── editors/ │ │ ├── w_JsonEditor.cpp │ │ ├── w_JsonEditor.hpp │ │ └── w_JsonEditor.ui │ ├── v3/ │ │ └── components/ │ │ └── GeositeReader/ │ │ ├── GeositeReader.cpp │ │ ├── GeositeReader.hpp │ │ ├── picoproto.cpp │ │ └── picoproto.hpp │ └── wrapper.hpp ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake/ │ ├── linux/ │ │ └── linux.cmake │ ├── myproto.cmake │ ├── nkr.cmake │ ├── print.cmake │ └── windows/ │ ├── VersionInfo.in │ ├── VersionResource.rc │ ├── generate_product_version.cmake │ └── windows.cmake ├── db/ │ ├── ConfigBuilder.cpp │ ├── ConfigBuilder.hpp │ ├── Database.cpp │ ├── Database.hpp │ ├── Group.hpp │ ├── ProfileFilter.cpp │ ├── ProfileFilter.hpp │ ├── ProxyEntity.hpp │ └── traffic/ │ ├── TrafficData.hpp │ ├── TrafficLooper.cpp │ └── TrafficLooper.hpp ├── docs/ │ ├── Build_Core.md │ ├── Build_Linux.md │ ├── Build_Windows.md │ ├── RunFlags.md │ ├── Run_Linux.md │ └── readme.md ├── fmt/ │ ├── AbstractBean.cpp │ ├── AbstractBean.hpp │ ├── Bean2CoreObj_box.cpp │ ├── Bean2External.cpp │ ├── Bean2Link.cpp │ ├── ChainBean.hpp │ ├── CustomBean.hpp │ ├── Link2Bean.cpp │ ├── NaiveBean.hpp │ ├── Preset.hpp │ ├── QUICBean.hpp │ ├── ShadowSocksBean.hpp │ ├── SocksHttpBean.hpp │ ├── TrojanVLESSBean.hpp │ ├── V2RayStreamSettings.hpp │ ├── VMessBean.hpp │ └── includes.h ├── go/ │ ├── .gitignore │ ├── cmd/ │ │ ├── nekobox_core/ │ │ │ ├── core_box.go │ │ │ ├── go.mod │ │ │ ├── go.sum │ │ │ ├── grpc_box.go │ │ │ └── main.go │ │ └── updater/ │ │ ├── .gitignore │ │ ├── go.mod │ │ ├── go.sum │ │ ├── launcher.go │ │ ├── launcher_linux.go │ │ ├── main.go │ │ ├── msgbox.go │ │ ├── msgbox_windows.go │ │ └── updater.go │ └── grpc_server/ │ ├── auth/ │ │ └── auth.go │ ├── fulltest.go │ ├── gen/ │ │ ├── libcore.pb.go │ │ ├── libcore.proto │ │ ├── libcore_grpc.pb.go │ │ └── update_proto.sh │ ├── go.mod │ ├── go.sum │ ├── grpc.go │ └── update.go ├── libs/ │ ├── .gitignore │ ├── build_deps_all.sh │ ├── build_go.sh │ ├── build_public_res.sh │ ├── deploy_linux64.sh │ ├── deploy_windows64.sh │ ├── download_qtsdk_win.sh │ ├── env_deploy.sh │ ├── env_qtsdk.sh │ ├── format_cpp.sh │ ├── get_source.sh │ ├── get_source_env.sh │ ├── package_appimage.sh │ └── package_debian.sh ├── main/ │ ├── Const.hpp │ ├── GuiUtils.hpp │ ├── HTTPRequestHelper.cpp │ ├── HTTPRequestHelper.hpp │ ├── NekoGui.cpp │ ├── NekoGui.hpp │ ├── NekoGui_ConfigItem.hpp │ ├── NekoGui_DataStore.hpp │ ├── NekoGui_Utils.cpp │ ├── NekoGui_Utils.hpp │ └── main.cpp ├── nekoray_version.txt ├── res/ │ ├── dashboard-notice.html │ ├── neko.css │ ├── neko.qrc │ ├── public/ │ │ └── qtbase_zh_CN.qm │ ├── theme/ │ │ └── feiyangqingyun/ │ │ ├── qss/ │ │ │ ├── blacksoft.css │ │ │ ├── flatgray.css │ │ │ └── lightblue.css │ │ └── qss.qrc │ └── vpn/ │ ├── sing-box-vpn.json │ └── vpn-run-root.sh ├── rpc/ │ ├── gRPC.cpp │ └── gRPC.h ├── sub/ │ ├── GroupUpdater.cpp │ └── GroupUpdater.hpp ├── sys/ │ ├── AutoRun.cpp │ ├── AutoRun.hpp │ ├── ExternalProcess.cpp │ ├── ExternalProcess.hpp │ ├── linux/ │ │ ├── LinuxCap.cpp │ │ └── LinuxCap.h │ └── windows/ │ ├── MiniDump.cpp │ ├── MiniDump.h │ ├── guihelper.cpp │ └── guihelper.h ├── test/ │ ├── test-qt512-sdk-build.sh │ └── test-qt6-build.sh ├── translations/ │ ├── fa_IR.ts │ ├── ru_RU.ts │ ├── translations.qrc │ └── zh_CN.ts └── ui/ ├── GroupSort.hpp ├── Icon.cpp ├── Icon.hpp ├── ThemeManager.cpp ├── ThemeManager.hpp ├── dialog_basic_settings.cpp ├── dialog_basic_settings.h ├── dialog_basic_settings.ui ├── dialog_hotkey.cpp ├── dialog_hotkey.h ├── dialog_hotkey.ui ├── dialog_manage_groups.cpp ├── dialog_manage_groups.h ├── dialog_manage_groups.ui ├── dialog_manage_routes.cpp ├── dialog_manage_routes.h ├── dialog_manage_routes.ui ├── dialog_vpn_settings.cpp ├── dialog_vpn_settings.h ├── dialog_vpn_settings.ui ├── edit/ │ ├── dialog_edit_group.cpp │ ├── dialog_edit_group.h │ ├── dialog_edit_group.ui │ ├── dialog_edit_profile.cpp │ ├── dialog_edit_profile.h │ ├── dialog_edit_profile.ui │ ├── edit_chain.cpp │ ├── edit_chain.h │ ├── edit_chain.ui │ ├── edit_custom.cpp │ ├── edit_custom.h │ ├── edit_custom.ui │ ├── edit_naive.cpp │ ├── edit_naive.h │ ├── edit_naive.ui │ ├── edit_quic.cpp │ ├── edit_quic.h │ ├── edit_quic.ui │ ├── edit_shadowsocks.cpp │ ├── edit_shadowsocks.h │ ├── edit_shadowsocks.ui │ ├── edit_socks_http.cpp │ ├── edit_socks_http.h │ ├── edit_socks_http.ui │ ├── edit_trojan_vless.cpp │ ├── edit_trojan_vless.h │ ├── edit_trojan_vless.ui │ ├── edit_vmess.cpp │ ├── edit_vmess.h │ ├── edit_vmess.ui │ └── profile_editor.h ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui ├── mainwindow_grpc.cpp ├── mainwindow_interface.h └── widget/ ├── FloatCheckBox.h ├── GroupItem.cpp ├── GroupItem.h ├── GroupItem.ui ├── MessageBoxTimer.h ├── MyLineEdit.h ├── MyTableWidget.h ├── ProxyItem.cpp ├── ProxyItem.h └── ProxyItem.ui ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ BasedOnStyle: Google ColumnLimit: 0 IndentWidth: 4 SortIncludes: Never SpacesBeforeTrailingComments: 1 NamespaceIndentation: All AccessModifierOffset: -4 SpaceAfterCStyleCast: true SpaceAfterTemplateKeyword: false SpaceBeforeRangeBasedForLoopColon: false ================================================ FILE: .clang-tidy ================================================ # Generated from CLion Inspection settings --- Checks: '-*, bugprone-argument-comment, bugprone-assert-side-effect, bugprone-bad-signal-to-kill-thread, bugprone-branch-clone, bugprone-copy-constructor-init, bugprone-dangling-handle, bugprone-dynamic-static-initializers, bugprone-fold-init-type, bugprone-forward-declaration-namespace, bugprone-forwarding-reference-overload, bugprone-inaccurate-erase, bugprone-incorrect-roundings, bugprone-integer-division, bugprone-lambda-function-name, bugprone-macro-parentheses, bugprone-macro-repeated-side-effects, bugprone-misplaced-operator-in-strlen-in-alloc, bugprone-misplaced-pointer-arithmetic-in-alloc, bugprone-misplaced-widening-cast, bugprone-move-forwarding-reference, bugprone-multiple-statement-macro, bugprone-no-escape, bugprone-not-null-terminated-result, bugprone-parent-virtual-call, bugprone-posix-return, bugprone-reserved-identifier, bugprone-sizeof-container, bugprone-sizeof-expression, bugprone-spuriously-wake-up-functions, bugprone-string-constructor, bugprone-string-integer-assignment, bugprone-string-literal-with-embedded-nul, bugprone-suspicious-enum-usage, bugprone-suspicious-include, bugprone-suspicious-memory-comparison, bugprone-suspicious-memset-usage, bugprone-suspicious-missing-comma, bugprone-suspicious-semicolon, bugprone-suspicious-string-compare, bugprone-swapped-arguments, bugprone-terminating-continue, bugprone-throw-keyword-missing, bugprone-too-small-loop-variable, bugprone-undefined-memory-manipulation, bugprone-undelegated-constructor, bugprone-unhandled-self-assignment, bugprone-unused-raii, bugprone-unused-return-value, bugprone-use-after-move, bugprone-virtual-near-miss, cert-dcl21-cpp, cert-dcl58-cpp, cert-err34-c, cert-err52-cpp, cert-err60-cpp, cert-flp30-c, cert-msc50-cpp, cert-msc51-cpp, cert-str34-c, cppcoreguidelines-interfaces-global-init, cppcoreguidelines-narrowing-conversions, cppcoreguidelines-pro-type-member-init, cppcoreguidelines-pro-type-static-cast-downcast, cppcoreguidelines-slicing, google-default-arguments, google-explicit-constructor, google-runtime-operator, hicpp-exception-baseclass, hicpp-multiway-paths-covered, misc-misplaced-const, misc-new-delete-overloads, misc-no-recursion, misc-non-copyable-objects, misc-throw-by-value-catch-by-reference, misc-unconventional-assign-operator, misc-uniqueptr-reset-release, modernize-avoid-bind, modernize-concat-nested-namespaces, modernize-deprecated-headers, modernize-deprecated-ios-base-aliases, modernize-loop-convert, modernize-make-shared, modernize-make-unique, modernize-pass-by-value, modernize-raw-string-literal, modernize-redundant-void-arg, modernize-replace-auto-ptr, modernize-replace-disallow-copy-and-assign-macro, modernize-replace-random-shuffle, modernize-return-braced-init-list, modernize-shrink-to-fit, modernize-unary-static-assert, modernize-use-auto, modernize-use-bool-literals, modernize-use-emplace, modernize-use-equals-default, modernize-use-equals-delete, modernize-use-nodiscard, modernize-use-noexcept, modernize-use-nullptr, modernize-use-override, modernize-use-transparent-functors, modernize-use-uncaught-exceptions, mpi-buffer-deref, mpi-type-mismatch, openmp-use-default-none, performance-faster-string-find, performance-for-range-copy, performance-implicit-conversion-in-loop, performance-inefficient-algorithm, performance-inefficient-string-concatenation, performance-inefficient-vector-operation, performance-move-const-arg, performance-move-constructor-init, performance-no-automatic-move, performance-noexcept-move-constructor, performance-trivially-destructible, performance-type-promotion-in-math-fn, performance-unnecessary-copy-initialization, performance-unnecessary-value-param, portability-simd-intrinsics, readability-avoid-const-params-in-decls, readability-const-return-type, readability-container-size-empty, readability-convert-member-functions-to-static, readability-delete-null-pointer, readability-deleted-default, readability-inconsistent-declaration-parameter-name, readability-make-member-function-const, readability-misleading-indentation, readability-misplaced-array-index, readability-non-const-parameter, readability-redundant-control-flow, readability-redundant-declaration, readability-redundant-function-ptr-dereference, readability-redundant-smartptr-get, readability-redundant-string-cstr, readability-redundant-string-init, readability-simplify-subscript-expr, readability-static-accessed-through-instance, readability-static-definition-in-anonymous-namespace, readability-string-compare, readability-uniqueptr-delete-release, readability-use-anyofallof' ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report-en.md ================================================ --- name: 'Bug Report' about: 'Please troubleshoot server-side issues and upgrade to the latest client before raising a question.' title: 'BUG: ' labels: '' assignees: '' --- ## Describe the problem Expected behavior: Actual behavior: ## How to reproduce Provide helpful screenshots, videos, text descriptions, subscription links, etc. ## log If you have logs, please upload them. Please see the detailed steps for exporting logs in the documentation. ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report-zh_cn.md ================================================ --- name: '问题反馈' about: '在提出问题前请先自行排除服务器端问题和升级到最新客户端。' title: 'BUG: ' labels: '' assignees: '' --- ## 描述问题 预期行为: 实际行为: ## 如何复现 提供有帮助的截图,录像,文字说明,订阅链接等。 ## 日志 如果有日志,请上传。请在文档内查看导出日志的详细步骤。 ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request-en.md ================================================ --- name: 'Feature Request' about: 'Make suggestions for new features of the software' title: '' labels: '' assignees: '' --- ## Description suggestions ## Necessity of recommendations ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request-zh_cn.md ================================================ --- name: '功能请求' about: '对软件的新功能提出建议。' title: '' labels: '' assignees: '' --- ## 描述建议 ## 建议的必要性 ================================================ FILE: .github/workflows/build-nekoray-cmake.yml ================================================ name: Nekoray build matrix - cmake on: workflow_dispatch: inputs: tag: description: "Release Tag" required: true publish: description: "Publish: If want ignore" required: false artifact-pack: description: "artifact-pack: If want ignore" required: false jobs: build-go: strategy: matrix: cross_os: [windows, linux] cross_arch: [amd64] include: - cross_os: public_res cross_arch: public_res fail-fast: false runs-on: ubuntu-latest steps: - name: Checking out sources uses: actions/checkout@v3 - name: Go Status run: git ls-files go | xargs cat | sha1sum > go_status - name: Cache Common Download id: cache-common uses: actions/cache@v3 with: path: artifacts.tgz key: CommonCache-${{ matrix.cross_os }}-${{ matrix.cross_arch }}-${{ hashFiles('libs/*.sh', 'go_status', '*.txt') }} - name: Install Golang if: steps.cache-common.outputs.cache-hit != 'true' uses: actions/setup-go@v3 with: go-version: ^1.22 - name: Build golang parts if: steps.cache-common.outputs.cache-hit != 'true' shell: bash run: | [ ${{ matrix.cross_os }} == public_res ] || ./libs/get_source.sh [ ${{ matrix.cross_os }} == public_res ] || GOOS=${{ matrix.cross_os }} GOARCH=${{ matrix.cross_arch }} ./libs/build_go.sh [ ${{ matrix.cross_os }} == public_res ] || exit 0 ./libs/build_public_res.sh - name: Tar files if: steps.cache-common.outputs.cache-hit != 'true' run: tar czvf artifacts.tgz ./deployment - name: Uploading Artifact uses: actions/upload-artifact@v3 with: name: NekoRay-${{ github.sha }}-Common-${{ matrix.cross_os }}-${{ matrix.cross_arch }} path: artifacts.tgz build-cpp: strategy: matrix: include: - platform: windows-2022 arch: x64 qt_version: "6.7" - platform: ubuntu-20.04 arch: x64 qt_version: "5.12" fail-fast: false runs-on: ${{ matrix.platform }} env: ACTIONS_ALLOW_UNSECURE_COMMANDS: true steps: - name: Checking out sources uses: actions/checkout@v3 with: submodules: "recursive" - name: Install MSVC compiler if: matrix.platform == 'windows-2022' uses: ilammy/msvc-dev-cmd@v1 with: # 14.1 is for vs2017, 14.2 is vs2019, following the upstream vcpkg build from Qv2ray-deps repo toolset: 14.2 arch: ${{ matrix.arch }} # ========================================================================================================= Qt Install - name: Windows - Download Custom Qt ${{ matrix.qt_version }} SDK shell: bash if: matrix.platform == 'windows-2022' env: DL_QT_VER: ${{ matrix.qt_version }} run: bash ./libs/download_qtsdk_win.sh # ========================================================================================================= 编译与 Qt 无关的依赖 - name: Install ninja-build tool uses: seanmiddleditch/gha-setup-ninja@v3 - name: Cache Download id: cache-deps uses: actions/cache@v3 with: path: libs/deps key: DepsCache-${{ matrix.platform }}-${{ matrix.arch }}-${{ hashFiles('libs/build_deps_*.sh') }}-Qt${{ matrix.qt_version }} - name: Build Dependencies shell: bash if: steps.cache-deps.outputs.cache-hit != 'true' && matrix.platform != 'ubuntu-20.04' run: ./libs/build_deps_all.sh - name: Build Dependencies (Docker) shell: bash if: steps.cache-deps.outputs.cache-hit != 'true' && matrix.platform == 'ubuntu-20.04' run: | docker run --rm \ -v $PWD:/nekoray \ -w /nekoray \ ghcr.io/matsuridayo/debian10-qt5:20230131 \ bash -c "./libs/build_deps_all.sh" # ========================================================================================================= Generate MakeFile and Build - name: Windows - Generate MakeFile and Build shell: bash if: matrix.platform == 'windows-2022' env: DL_QT_VER: ${{ matrix.qt_version }} CC: cl.exe CXX: cl.exe run: | source libs/env_qtsdk.sh $PWD/qtsdk/Qt mkdir build cd build cmake -GNinja -DQT_VERSION_MAJOR=6 -DCMAKE_BUILD_TYPE=Release .. ninja -j2 cd .. ./libs/deploy_windows64.sh - name: Linux - Generate MakeFile and Build shell: bash if: matrix.platform == 'ubuntu-20.04' run: | docker run --rm \ -v $PWD:/nekoray \ -w /nekoray \ ghcr.io/matsuridayo/debian10-qt5:20230131 \ bash -c "mkdir build && pushd build && cmake -GNinja -DCMAKE_BUILD_TYPE=Release .. && ninja && popd &&./libs/deploy_linux64.sh" # ========================================================================================================= Deployments - name: Tar files shell: bash run: tar czvf artifacts.tgz ./deployment - name: Uploading Artifact uses: actions/upload-artifact@v3 with: name: NekoRay-${{ github.sha }}-${{ matrix.platform }}-${{ matrix.arch }}-Qt${{ matrix.qt_version }} path: artifacts.tgz publish: name: Pack & Publish Release if: github.event.inputs.artifact-pack != 'y' runs-on: ubuntu-latest needs: - build-cpp - build-go steps: - name: Checking out sources uses: actions/checkout@v3 - name: Download Artifacts uses: actions/download-artifact@v3 with: path: download-artifact - name: Pack run: | curl -Lo - https://github.com/tcnksm/ghr/releases/download/v0.13.0/ghr_v0.13.0_linux_amd64.tar.gz | tar xzv mv ghr*linux_amd64/ghr . #### source libs/env_deploy.sh find . -name artifacts.tgz | xargs -n1 tar xvzf cd deployment cp -r public_res/* linux64 cp -r public_res/* windows64 rm -rf public_res *.pdb #### mv linux64 nekoray zip -r $version_standalone-linux64.zip nekoray rm -rf nekoray #### mv windows64 nekoray zip -r $version_standalone-windows64.zip nekoray rm -rf nekoray - name: Pack Debian run: | source libs/env_deploy.sh find . -name artifacts.tgz | xargs -n1 tar xvzf cd deployment cp -r public_res/* linux64 #### bash ../libs/package_debian.sh ${{ github.event.inputs.tag }} mv nekoray.deb $version_standalone-debian-x64.deb sudo rm -rf nekoray - name: Pack AppImage run: | source libs/env_deploy.sh find . -name artifacts.tgz | xargs -n1 tar xvzf cd deployment cp -r public_res/* linux64 #### bash ../libs/package_appimage.sh mv nekobox-x86_64.AppImage $version_standalone-linux-x64.AppImage - name: Clean Up run: | cd deployment rm -rf linux64 rm -rf windows64 rm -rf public_res rm -rf *.pdb - name: Uploading Artifact uses: actions/upload-artifact@v3 with: name: Deployment-${{ github.sha }} path: deployment - name: Release if: github.event.inputs.publish != 'y' run: | ./ghr -delete -t "${{ github.token }}" -n "${{ github.event.inputs.tag }}" "${{ github.event.inputs.tag }}" deployment ================================================ FILE: .github/workflows/update-pkgbuild.yml ================================================ name: AUR CI on: push: branches: - main paths-ignore: - '**.md' - 'LICENSE' - '!.github/workflows/**' jobs: update: runs-on: ubuntu-latest steps: - uses: chitang233/aur-pkgbuild-builder@main with: deploy_key: ${{ secrets.DEPLOY_KEY }} package_name: 'nekoray-git' ================================================ FILE: .gitignore ================================================ # This file is used to ignore files which are generated # ---------------------------------------------------------------------------- *~ *.autosave *.a *.core *.moc *.o *.obj *.orig *.rej *.so *.so.* *_pch.h.cpp *_resource.rc .#* *.*# core !core/ tags .DS_Store .directory *.debug /Makefile* *.prl *.app moc_*.cpp ui_*.h qrc_*.cpp Thumbs.db *.res /.qmake.cache /.qmake.stash # qtcreator generated files *.pro.user* # xemacs temporary files *.flc # Vim temporary files .*.swp # Visual Studio generated files *.ib_pdb_index *.idb *.ilk *.pdb *.sln *.suo *.vcproj *vcproj.*.*.user *.ncb *.sdf *.opensdf *.vcxproj *vcxproj.* # MinGW generated files *.Debug *.Release # Python byte code *.pyc # Binaries # -------- *.dll *.exe # Custom /nekoray /build CMakeLists.txt.user* /cmake-build-* /build-* .vscode .idea # Deploy /deployment /neko*.sh /qtsdk .vs out ================================================ FILE: .gitmodules ================================================ [submodule "3rdparty/QHotkey"] path = 3rdparty/QHotkey url = https://github.com/Skycoder42/QHotkey.git ================================================ FILE: 3rdparty/QThreadCreateThread.hpp ================================================ #pragma once #include #include // FOR OLD QT class QThreadCreateThread : public QThread { public: explicit QThreadCreateThread(std::future &&future) : m_future(std::move(future)) { // deleteLater connect(this, &QThread::finished, this, &QThread::deleteLater); } private: void run() override { m_future.get(); } std::future m_future; }; inline QThread *createThreadImpl(std::future &&future) { return new QThreadCreateThread(std::move(future)); } template QThread *createQThread(Function &&f, Args &&... args) { using DecayedFunction = typename std::decay::type; auto threadFunction = [f = static_cast(std::forward(f))](auto &&... largs) mutable -> void { (void) std::invoke(std::move(f), std::forward(largs)...); }; return createThreadImpl(std::async(std::launch::deferred, std::move(threadFunction), std::forward(args)...)); } ================================================ FILE: 3rdparty/QtExtKeySequenceEdit.cpp ================================================ #include "QtExtKeySequenceEdit.h" QtExtKeySequenceEdit::QtExtKeySequenceEdit(QWidget *parent) : QKeySequenceEdit(parent) { } QtExtKeySequenceEdit::~QtExtKeySequenceEdit() { } void QtExtKeySequenceEdit::keyPressEvent(QKeyEvent *pEvent) { QKeySequenceEdit::keyPressEvent(pEvent); QKeySequence keySeq = keySequence(); if (keySeq.count() <= 0) { return; } int key = keySeq[0]; if (key == Qt::Key_Backspace || key == Qt::Key_Delete) { key = 0; } setKeySequence(key); } ================================================ FILE: 3rdparty/QtExtKeySequenceEdit.h ================================================ #include class QtExtKeySequenceEdit : public QKeySequenceEdit { public: QtExtKeySequenceEdit(QWidget *parent); ~QtExtKeySequenceEdit(); protected: virtual void keyPressEvent(QKeyEvent *pEvent); }; ================================================ FILE: 3rdparty/RunGuard.hpp ================================================ #ifndef RUNGUARD_H #define RUNGUARD_H #include #include #include #include class RunGuard { public: RunGuard(const QString &key); ~RunGuard(); bool isAnotherRunning(quint64 *data_out); bool tryToRun(quint64 *data_in); void release(); private: const QString key; const QString memLockKey; const QString sharedmemKey; QSharedMemory sharedMem; QSystemSemaphore memLock; Q_DISABLE_COPY(RunGuard) }; namespace { QString generateKeyHash(const QString &key, const QString &salt) { QByteArray data; data.append(key.toUtf8()); data.append(salt.toUtf8()); data = QCryptographicHash::hash(data, QCryptographicHash::Sha1).toHex(); return data; } } // namespace RunGuard::RunGuard(const QString &key) : key(key), memLockKey(generateKeyHash(key, "_memLockKey")), sharedmemKey(generateKeyHash(key, "_sharedmemKey")), sharedMem(sharedmemKey), memLock(memLockKey, 1) { memLock.acquire(); { QSharedMemory fix(sharedmemKey); // Fix for *nix: http://habrahabr.ru/post/173281/ fix.attach(); } memLock.release(); } RunGuard::~RunGuard() { release(); } bool RunGuard::isAnotherRunning(quint64 *data_out) { if (sharedMem.isAttached()) return false; memLock.acquire(); const bool isRunning = sharedMem.attach(); if (isRunning) { if (data_out != nullptr) { memcpy(data_out, sharedMem.data(), sizeof(quint64)); } sharedMem.detach(); } memLock.release(); return isRunning; } bool RunGuard::tryToRun(quint64 *data_in) { memLock.acquire(); const bool result = sharedMem.create(sizeof(quint64)); if (result) memcpy(sharedMem.data(), data_in, sizeof(quint64)); memLock.release(); if (!result) { release(); return false; } return true; } void RunGuard::release() { memLock.acquire(); if (sharedMem.isAttached()) sharedMem.detach(); memLock.release(); } #endif // RUNGUARD_H ================================================ FILE: 3rdparty/VT100Parser.hpp ================================================ #pragma once #include inline QString cleanVT100String(const QString &in) { QString out; bool in_033 = false; for (auto &&chr: in) { if (chr == '\033') { in_033 = true; continue; } if (in_033) { if (chr == 'm') { in_033 = false; } continue; } out += chr; } return out; } ================================================ FILE: 3rdparty/WinCommander.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2014 UpdateNode UG (haftungsbeschränkt) ** Contact: code@updatenode.com ** ** This file is part of the UpdateNode Client. ** ** Commercial License Usage ** Licensees holding valid commercial UpdateNode license may use this file ** under the terms of the the Apache License, Version 2.0 ** Full license description file: LICENSE.COM ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation. Please review the following information to ensure the ** GNU General Public License version 3.0 requirements will be met: ** http://www.gnu.org/copyleft/gpl.html. ** Full license description file: LICENSE.GPL ** ****************************************************************************/ #include "WinCommander.hpp" #include #include #ifdef Q_OS_WIN #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #include #define MAX_KEY_LENGTH 255 #define MAX_VALUE_NAME 16383 #endif /*! Executes a command elevated specified by \apath , using paramters \aparameters. \n Parameter /aaWait decides if the function should return immediatelly after it's\n execution or wait for the exit of the launched process \n Returns the return value of the executed command */ uint WinCommander::runProcessElevated(const QString &path, const QStringList ¶meters, const QString &workingDir, int nShow, bool aWait) { uint result = 0; #ifdef Q_OS_WIN QString params; HWND hwnd = NULL; LPCTSTR pszPath = (LPCTSTR)path.utf16(); foreach(QString item, parameters) params += "\"" + item + "\" "; LPCTSTR pszParameters = (LPCTSTR)params.utf16(); QString dir; if (workingDir.count() == 0) dir = QDir::toNativeSeparators(QDir::currentPath()); else dir = QDir::toNativeSeparators(workingDir); LPCTSTR pszDirectory = (LPCTSTR)dir.utf16(); SHELLEXECUTEINFO shex; DWORD dwCode = 0; ZeroMemory(&shex, sizeof(shex)); shex.cbSize = sizeof(shex); shex.fMask = SEE_MASK_NOCLOSEPROCESS; shex.hwnd = hwnd; shex.lpVerb = TEXT("runas"); shex.lpFile = pszPath; shex.lpParameters = pszParameters; shex.lpDirectory = pszDirectory; // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow shex.nShow = nShow; ShellExecuteEx(&shex); if (shex.hProcess) { if(aWait) { WaitForSingleObject(shex.hProcess, INFINITE ); GetExitCodeProcess(shex.hProcess, &dwCode); } CloseHandle (shex.hProcess) ; } else return -1; result = (uint)dwCode; #else Q_UNUSED(path); Q_UNUSED(parameters); Q_UNUSED(workingDir); Q_UNUSED(aWait); #endif return result; } ================================================ FILE: 3rdparty/WinCommander.hpp ================================================ /**************************************************************************** ** ** Copyright (C) 2014 UpdateNode UG (haftungsbeschränkt) ** Contact: code@updatenode.com ** ** This file is part of the UpdateNode Client. ** ** Commercial License Usage ** Licensees holding valid commercial UpdateNode license may use this file ** under the terms of the the Apache License, Version 2.0 ** Full license description file: LICENSE.COM ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation. Please review the following information to ensure the ** GNU General Public License version 3.0 requirements will be met: ** http://www.gnu.org/copyleft/gpl.html. ** Full license description file: LICENSE.GPL ** ****************************************************************************/ #ifndef WINCOMMANDER_H #define WINCOMMANDER_H #include #include class WinCommander { public: static const int SW_HIDE = 0; static const int SW_NORMAL = 1; static const int SW_SHOWMINIMIZED = 2; static uint runProcessElevated(const QString &path, const QStringList ¶meters = QStringList(), const QString &workingDir = QString(), int nShow = SW_SHOWMINIMIZED, bool aWait = true); }; #endif // WINCOMMANDER_H ================================================ FILE: 3rdparty/ZxingQtReader.hpp ================================================ /* * Copyright 2020 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 #pragma once #include "ZXing/ReadBarcode.h" #include #include #include #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) #include "qscopeguard.h" #else #include #endif #ifdef QT_MULTIMEDIA_LIB #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #include #else #include #include #endif #include #endif // This is some sample code to start a discussion about how a minimal and header-only Qt wrapper/helper could look like. namespace ZXingQt { Q_NAMESPACE //TODO: find a better way to export these enums to QML than to duplicate their definition // #ifdef Q_MOC_RUN produces meta information in the moc output but it does end up working in qml #ifdef QT_QML_LIB enum class BarcodeFormat { None = 0, ///< Used as a return value if no valid barcode has been detected Aztec = (1 << 0), ///< Aztec Codabar = (1 << 1), ///< Codabar Code39 = (1 << 2), ///< Code39 Code93 = (1 << 3), ///< Code93 Code128 = (1 << 4), ///< Code128 DataBar = (1 << 5), ///< GS1 DataBar, formerly known as RSS 14 DataBarExpanded = (1 << 6), ///< GS1 DataBar Expanded, formerly known as RSS EXPANDED DataMatrix = (1 << 7), ///< DataMatrix EAN8 = (1 << 8), ///< EAN-8 EAN13 = (1 << 9), ///< EAN-13 ITF = (1 << 10), ///< ITF (Interleaved Two of Five) MaxiCode = (1 << 11), ///< MaxiCode PDF417 = (1 << 12), ///< PDF417 or QRCode = (1 << 13), ///< QR Code UPCA = (1 << 14), ///< UPC-A UPCE = (1 << 15), ///< UPC-E MicroQRCode = (1 << 16), ///< Micro QR Code LinearCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | UPCA | UPCE, MatrixCodes = Aztec | DataMatrix | MaxiCode | PDF417 | QRCode | MicroQRCode, }; enum class ContentType { Text, Binary, Mixed, GS1, ISO15434, UnknownECI }; #else using ZXing::BarcodeFormat; using ZXing::ContentType; #endif using ZXing::DecodeHints; using ZXing::Binarizer; using ZXing::BarcodeFormats; Q_ENUM_NS(BarcodeFormat) Q_ENUM_NS(ContentType) template QDebug operator<<(QDebug dbg, const T& v) { return dbg.noquote() << QString::fromStdString(ToString(v)); } class Position : public ZXing::Quadrilateral { Q_GADGET Q_PROPERTY(QPoint topLeft READ topLeft) Q_PROPERTY(QPoint topRight READ topRight) Q_PROPERTY(QPoint bottomRight READ bottomRight) Q_PROPERTY(QPoint bottomLeft READ bottomLeft) using Base = ZXing::Quadrilateral; public: using Base::Base; }; class Result : private ZXing::Result { Q_GADGET Q_PROPERTY(BarcodeFormat format READ format) Q_PROPERTY(QString formatName READ formatName) Q_PROPERTY(QString text READ text) Q_PROPERTY(QByteArray bytes READ bytes) Q_PROPERTY(bool isValid READ isValid) Q_PROPERTY(ContentType contentType READ contentType) Q_PROPERTY(Position position READ position) QString _text; QByteArray _bytes; Position _position; public: Result() = default; // required for qmetatype machinery explicit Result(ZXing::Result&& r) : ZXing::Result(std::move(r)) { _text = QString::fromStdString(ZXing::Result::text()); _bytes = QByteArray(reinterpret_cast(ZXing::Result::bytes().data()), Size(ZXing::Result::bytes())); auto& pos = ZXing::Result::position(); auto qp = [&pos](int i) { return QPoint(pos[i].x, pos[i].y); }; _position = {qp(0), qp(1), qp(2), qp(3)}; } using ZXing::Result::isValid; BarcodeFormat format() const { return static_cast(ZXing::Result::format()); } ContentType contentType() const { return static_cast(ZXing::Result::contentType()); } QString formatName() const { return QString::fromStdString(ZXing::ToString(ZXing::Result::format())); } const QString& text() const { return _text; } const QByteArray& bytes() const { return _bytes; } const Position& position() const { return _position; } // For debugging/development int runTime = 0; Q_PROPERTY(int runTime MEMBER runTime) }; inline QList QListResults(ZXing::Results&& zxres) { QList res; for (auto&& r : zxres) res.push_back(Result(std::move(r))); return res; } inline QList ReadBarcodes(const QImage& img, const DecodeHints& hints = {}) { using namespace ZXing; auto ImgFmtFromQImg = [](const QImage& img) { switch (img.format()) { case QImage::Format_ARGB32: case QImage::Format_RGB32: #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN return ImageFormat::BGRX; #else return ImageFormat::XRGB; #endif case QImage::Format_RGB888: return ImageFormat::RGB; case QImage::Format_RGBX8888: case QImage::Format_RGBA8888: return ImageFormat::RGBX; case QImage::Format_Grayscale8: return ImageFormat::Lum; default: return ImageFormat::None; } }; auto exec = [&](const QImage& img) { return QListResults(ZXing::ReadBarcodes( {img.bits(), img.width(), img.height(), ImgFmtFromQImg(img), static_cast(img.bytesPerLine())}, hints)); }; return ImgFmtFromQImg(img) == ImageFormat::None ? exec(img.convertToFormat(QImage::Format_Grayscale8)) : exec(img); } inline Result ReadBarcode(const QImage& img, const DecodeHints& hints = {}) { auto res = ReadBarcodes(img, DecodeHints(hints).setMaxNumberOfSymbols(1)); return !res.isEmpty() ? res.takeFirst() : Result(); } #ifdef QT_MULTIMEDIA_LIB inline QList ReadBarcodes(const QVideoFrame& frame, const DecodeHints& hints = {}) { using namespace ZXing; ImageFormat fmt = ImageFormat::None; int pixStride = 0; int pixOffset = 0; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #define FORMAT(F5, F6) QVideoFrame::Format_##F5 #define FIRST_PLANE #else #define FORMAT(F5, F6) QVideoFrameFormat::Format_##F6 #define FIRST_PLANE 0 #endif switch (frame.pixelFormat()) { case FORMAT(ARGB32, ARGB8888): case FORMAT(ARGB32_Premultiplied, ARGB8888_Premultiplied): case FORMAT(RGB32, RGBX8888): #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN fmt = ImageFormat::BGRX; #else fmt = ImageFormat::XRGB; #endif break; case FORMAT(BGRA32, BGRA8888): case FORMAT(BGRA32_Premultiplied, BGRA8888_Premultiplied): case FORMAT(BGR32, BGRX8888): #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN fmt = ImageFormat::RGBX; #else fmt = ImageFormat::XBGR; #endif break; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) case QVideoFrame::Format_RGB24: fmt = ImageFormat::RGB; break; case QVideoFrame::Format_BGR24: fmt = ImageFormat::BGR; break; case QVideoFrame::Format_YUV444: fmt = ImageFormat::Lum, pixStride = 3; break; #else case QVideoFrameFormat::Format_P010: case QVideoFrameFormat::Format_P016: fmt = ImageFormat::Lum, pixStride = 1; break; #endif case FORMAT(AYUV444, AYUV): case FORMAT(AYUV444_Premultiplied, AYUV_Premultiplied): #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN fmt = ImageFormat::Lum, pixStride = 4, pixOffset = 3; #else fmt = ImageFormat::Lum, pixStride = 4, pixOffset = 2; #endif break; case FORMAT(YUV420P, YUV420P): case FORMAT(NV12, NV12): case FORMAT(NV21, NV21): case FORMAT(IMC1, IMC1): case FORMAT(IMC2, IMC2): case FORMAT(IMC3, IMC3): case FORMAT(IMC4, IMC4): case FORMAT(YV12, YV12): fmt = ImageFormat::Lum; break; case FORMAT(UYVY, UYVY): fmt = ImageFormat::Lum, pixStride = 2, pixOffset = 1; break; case FORMAT(YUYV, YUYV): fmt = ImageFormat::Lum, pixStride = 2; break; case FORMAT(Y8, Y8): fmt = ImageFormat::Lum; break; case FORMAT(Y16, Y16): fmt = ImageFormat::Lum, pixStride = 2, pixOffset = 1; break; #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)) case FORMAT(ABGR32, ABGR8888): #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN fmt = ImageFormat::RGBX; #else fmt = ImageFormat::XBGR; #endif break; #endif #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) case FORMAT(YUV422P, YUV422P): fmt = ImageFormat::Lum; break; #endif default: break; } if (fmt != ImageFormat::None) { auto img = frame; // shallow copy just get access to non-const map() function #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) if (!img.isValid() || !img.map(QAbstractVideoBuffer::ReadOnly)){ #else if (!img.isValid() || !img.map(QVideoFrame::ReadOnly)){ #endif qWarning() << "invalid QVideoFrame: could not map memory"; return {}; } QScopeGuard unmap([&] { img.unmap(); }); return QListResults(ZXing::ReadBarcodes( {img.bits(FIRST_PLANE) + pixOffset, img.width(), img.height(), fmt, img.bytesPerLine(FIRST_PLANE), pixStride}, hints)); } else { #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) if (QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat()) != QImage::Format_Invalid) { qWarning() << "unsupported QVideoFrame::pixelFormat"; return {}; } auto qimg = frame.image(); #else auto qimg = frame.toImage(); #endif if (qimg.format() != QImage::Format_Invalid) return ReadBarcodes(qimg, hints); qWarning() << "failed to convert QVideoFrame to QImage"; return {}; } } inline Result ReadBarcode(const QVideoFrame& frame, const DecodeHints& hints = {}) { auto res = ReadBarcodes(frame, DecodeHints(hints).setMaxNumberOfSymbols(1)); return !res.isEmpty() ? res.takeFirst() : Result(); } #define ZQ_PROPERTY(Type, name, setter) \ public: \ Q_PROPERTY(Type name READ name WRITE setter NOTIFY name##Changed) \ Type name() const noexcept { return DecodeHints::name(); } \ Q_SLOT void setter(const Type& newVal) \ { \ if (name() != newVal) { \ DecodeHints::setter(newVal); \ emit name##Changed(); \ } \ } \ Q_SIGNAL void name##Changed(); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) class BarcodeReader : public QAbstractVideoFilter, private DecodeHints #else class BarcodeReader : public QObject, private DecodeHints #endif { Q_OBJECT public: #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) BarcodeReader(QObject* parent = nullptr) : QAbstractVideoFilter(parent) {} #else BarcodeReader(QObject* parent = nullptr) : QObject(parent) {} #endif // TODO: find out how to properly expose QFlags to QML // simply using ZQ_PROPERTY(BarcodeFormats, formats, setFormats) // results in the runtime error "can't assign int to formats" Q_PROPERTY(int formats READ formats WRITE setFormats NOTIFY formatsChanged) int formats() const noexcept { auto fmts = DecodeHints::formats(); return *reinterpret_cast(&fmts); } Q_SLOT void setFormats(int newVal) { if (formats() != newVal) { DecodeHints::setFormats(static_cast(newVal)); emit formatsChanged(); qDebug() << DecodeHints::formats(); } } Q_SIGNAL void formatsChanged(); ZQ_PROPERTY(bool, tryRotate, setTryRotate) ZQ_PROPERTY(bool, tryHarder, setTryHarder) ZQ_PROPERTY(bool, tryDownscale, setTryDownscale) public slots: ZXingQt::Result process(const QVideoFrame& image) { QElapsedTimer t; t.start(); auto res = ReadBarcode(image, *this); res.runTime = t.elapsed(); emit newResult(res); if (res.isValid()) emit foundBarcode(res); return res; } signals: void newResult(ZXingQt::Result result); void foundBarcode(ZXingQt::Result result); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) public: QVideoFilterRunnable *createFilterRunnable() override; #else private: QVideoSink *_sink = nullptr; public: void setVideoSink(QVideoSink* sink) { if (_sink == sink) return; if (_sink) disconnect(_sink, nullptr, this, nullptr); _sink = sink; connect(_sink, &QVideoSink::videoFrameChanged, this, &BarcodeReader::process); } Q_PROPERTY(QVideoSink* videoSink WRITE setVideoSink) #endif }; #undef ZX_PROPERTY #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) class VideoFilterRunnable : public QVideoFilterRunnable { BarcodeReader* _filter = nullptr; public: explicit VideoFilterRunnable(BarcodeReader* filter) : _filter(filter) {} QVideoFrame run(QVideoFrame* input, const QVideoSurfaceFormat& /*surfaceFormat*/, RunFlags /*flags*/) override { _filter->process(*input); return *input; } }; inline QVideoFilterRunnable* BarcodeReader::createFilterRunnable() { return new VideoFilterRunnable(this); } #endif #endif // QT_MULTIMEDIA_LIB } // namespace ZXingQt Q_DECLARE_METATYPE(ZXingQt::Position) Q_DECLARE_METATYPE(ZXingQt::Result) #ifdef QT_QML_LIB #include namespace ZXingQt { inline void registerQmlAndMetaTypes() { qRegisterMetaType("BarcodeFormat"); qRegisterMetaType("ContentType"); // supposedly the Q_DECLARE_METATYPE should be used with the overload without a custom name // but then the qml side complains about "unregistered type" qRegisterMetaType("Position"); qRegisterMetaType("Result"); qmlRegisterUncreatableMetaObject( ZXingQt::staticMetaObject, "ZXing", 1, 0, "ZXing", "Access to enums & flags only"); qmlRegisterType("ZXing", 1, 0, "BarcodeReader"); } } // namespace ZXingQt #endif // QT_QML_LIB ================================================ FILE: 3rdparty/base64.cpp ================================================ #include "base64.h" #ifndef qsizetype #define qsizetype size_t #endif namespace Qt515Base64 { namespace { struct fromBase64_helper_result { qsizetype decodedLength; Base64DecodingStatus status; }; fromBase64_helper_result fromBase64_helper(const char *input, qsizetype inputSize, char *output /* may alias input */, Base64Options options) { fromBase64_helper_result result{0, Base64DecodingStatus::Ok}; unsigned int buf = 0; int nbits = 0; qsizetype offset = 0; for (qsizetype i = 0; i < inputSize; ++i) { int ch = input[i]; int d; if (ch >= 'A' && ch <= 'Z') { d = ch - 'A'; } else if (ch >= 'a' && ch <= 'z') { d = ch - 'a' + 26; } else if (ch >= '0' && ch <= '9') { d = ch - '0' + 52; } else if (ch == '+' && (options & Base64UrlEncoding) == 0) { d = 62; } else if (ch == '-' && (options & Base64UrlEncoding) != 0) { d = 62; } else if (ch == '/' && (options & Base64UrlEncoding) == 0) { d = 63; } else if (ch == '_' && (options & Base64UrlEncoding) != 0) { d = 63; } else { if (options & AbortOnBase64DecodingErrors) { if (ch == '=') { // can have 1 or 2 '=' signs, in both cases padding base64Size to // a multiple of 4. Any other case is illegal. if ((inputSize % 4) != 0) { result.status = Base64DecodingStatus::IllegalInputLength; return result; } else if ((i == inputSize - 1) || (i == inputSize - 2 && input[++i] == '=')) { d = -1; // ... and exit the loop, normally } else { result.status = Base64DecodingStatus::IllegalPadding; return result; } } else { result.status = Base64DecodingStatus::IllegalCharacter; return result; } } else { d = -1; } } if (d != -1) { buf = (buf << 6) | d; nbits += 6; if (nbits >= 8) { nbits -= 8; Q_ASSERT(offset < i); output[offset++] = buf >> nbits; buf &= (1 << nbits) - 1; } } } result.decodedLength = offset; return result; } } // namespace FromBase64Result QByteArray_fromBase64Encoding(const QByteArray &base64, Base64Options options) { const auto base64Size = base64.size(); QByteArray result((base64Size * 3) / 4, Qt::Uninitialized); const auto base64result = fromBase64_helper(base64.data(), base64Size, const_cast(result.constData()), options); result.truncate(int(base64result.decodedLength)); return {std::move(result), base64result.status}; } } // namespace Qt515Base64 ================================================ FILE: 3rdparty/base64.h ================================================ #include namespace Qt515Base64 { enum Base64Option { Base64Encoding = 0, Base64UrlEncoding = 1, KeepTrailingEquals = 0, OmitTrailingEquals = 2, IgnoreBase64DecodingErrors = 0, AbortOnBase64DecodingErrors = 4, }; Q_DECLARE_FLAGS(Base64Options, Base64Option) Q_DECLARE_OPERATORS_FOR_FLAGS(Base64Options) enum class Base64DecodingStatus { Ok, IllegalInputLength, IllegalCharacter, IllegalPadding, }; class FromBase64Result { public: QByteArray decoded; Base64DecodingStatus decodingStatus; void swap(FromBase64Result &other) noexcept { qSwap(decoded, other.decoded); qSwap(decodingStatus, other.decodingStatus); } explicit operator bool() const noexcept { return decodingStatus == Base64DecodingStatus::Ok; } #if defined(Q_COMPILER_REF_QUALIFIERS) && !defined(Q_QDOC) QByteArray &operator*() &noexcept { return decoded; } const QByteArray &operator*() const &noexcept { return decoded; } QByteArray &&operator*() &&noexcept { return std::move(decoded); } #else QByteArray &operator*() noexcept { return decoded; } const QByteArray &operator*() const noexcept { return decoded; } #endif }; FromBase64Result QByteArray_fromBase64Encoding(const QByteArray &base64, Base64Options options); } // namespace Qt515Base64 ================================================ FILE: 3rdparty/fix_old_qt.h ================================================ #pragma once #include #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) inline QString qEnvironmentVariable(const char *varName) { return qgetenv(varName); } #endif ================================================ FILE: 3rdparty/qrcodegen.cpp ================================================ /* * QR Code generator library (C++) * * Copyright (c) Project Nayuki. (MIT License) * https://www.nayuki.io/page/qr-code-generator-library * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * - The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * - The Software is provided "as is", without warranty of any kind, express or * implied, including but not limited to the warranties of merchantability, * fitness for a particular purpose and noninfringement. In no event shall the * authors or copyright holders be liable for any claim, damages or other * liability, whether in an action of contract, tort or otherwise, arising from, * out of or in connection with the Software or the use or other dealings in the * Software. */ #include #include #include #include #include #include #include #include #include "qrcodegen.hpp" using std::int8_t; using std::uint8_t; using std::size_t; using std::vector; namespace qrcodegen { /*---- Class QrSegment ----*/ QrSegment::Mode::Mode(int mode, int cc0, int cc1, int cc2) : modeBits(mode) { numBitsCharCount[0] = cc0; numBitsCharCount[1] = cc1; numBitsCharCount[2] = cc2; } int QrSegment::Mode::getModeBits() const { return modeBits; } int QrSegment::Mode::numCharCountBits(int ver) const { return numBitsCharCount[(ver + 7) / 17]; } const QrSegment::Mode QrSegment::Mode::NUMERIC (0x1, 10, 12, 14); const QrSegment::Mode QrSegment::Mode::ALPHANUMERIC(0x2, 9, 11, 13); const QrSegment::Mode QrSegment::Mode::BYTE (0x4, 8, 16, 16); const QrSegment::Mode QrSegment::Mode::KANJI (0x8, 8, 10, 12); const QrSegment::Mode QrSegment::Mode::ECI (0x7, 0, 0, 0); QrSegment QrSegment::makeBytes(const vector &data) { if (data.size() > static_cast(INT_MAX)) throw std::length_error("Data too long"); BitBuffer bb; for (uint8_t b : data) bb.appendBits(b, 8); return QrSegment(Mode::BYTE, static_cast(data.size()), std::move(bb)); } QrSegment QrSegment::makeNumeric(const char *digits) { BitBuffer bb; int accumData = 0; int accumCount = 0; int charCount = 0; for (; *digits != '\0'; digits++, charCount++) { char c = *digits; if (c < '0' || c > '9') throw std::domain_error("String contains non-numeric characters"); accumData = accumData * 10 + (c - '0'); accumCount++; if (accumCount == 3) { bb.appendBits(static_cast(accumData), 10); accumData = 0; accumCount = 0; } } if (accumCount > 0) // 1 or 2 digits remaining bb.appendBits(static_cast(accumData), accumCount * 3 + 1); return QrSegment(Mode::NUMERIC, charCount, std::move(bb)); } QrSegment QrSegment::makeAlphanumeric(const char *text) { BitBuffer bb; int accumData = 0; int accumCount = 0; int charCount = 0; for (; *text != '\0'; text++, charCount++) { const char *temp = std::strchr(ALPHANUMERIC_CHARSET, *text); if (temp == nullptr) throw std::domain_error("String contains unencodable characters in alphanumeric mode"); accumData = accumData * 45 + static_cast(temp - ALPHANUMERIC_CHARSET); accumCount++; if (accumCount == 2) { bb.appendBits(static_cast(accumData), 11); accumData = 0; accumCount = 0; } } if (accumCount > 0) // 1 character remaining bb.appendBits(static_cast(accumData), 6); return QrSegment(Mode::ALPHANUMERIC, charCount, std::move(bb)); } vector QrSegment::makeSegments(const char *text) { // Select the most efficient segment encoding automatically vector result; if (*text == '\0'); // Leave result empty else if (isNumeric(text)) result.push_back(makeNumeric(text)); else if (isAlphanumeric(text)) result.push_back(makeAlphanumeric(text)); else { vector bytes; for (; *text != '\0'; text++) bytes.push_back(static_cast(*text)); result.push_back(makeBytes(bytes)); } return result; } QrSegment QrSegment::makeEci(long assignVal) { BitBuffer bb; if (assignVal < 0) throw std::domain_error("ECI assignment value out of range"); else if (assignVal < (1 << 7)) bb.appendBits(static_cast(assignVal), 8); else if (assignVal < (1 << 14)) { bb.appendBits(2, 2); bb.appendBits(static_cast(assignVal), 14); } else if (assignVal < 1000000L) { bb.appendBits(6, 3); bb.appendBits(static_cast(assignVal), 21); } else throw std::domain_error("ECI assignment value out of range"); return QrSegment(Mode::ECI, 0, std::move(bb)); } QrSegment::QrSegment(const Mode &md, int numCh, const std::vector &dt) : mode(&md), numChars(numCh), data(dt) { if (numCh < 0) throw std::domain_error("Invalid value"); } QrSegment::QrSegment(const Mode &md, int numCh, std::vector &&dt) : mode(&md), numChars(numCh), data(std::move(dt)) { if (numCh < 0) throw std::domain_error("Invalid value"); } int QrSegment::getTotalBits(const vector &segs, int version) { int result = 0; for (const QrSegment &seg : segs) { int ccbits = seg.mode->numCharCountBits(version); if (seg.numChars >= (1L << ccbits)) return -1; // The segment's length doesn't fit the field's bit width if (4 + ccbits > INT_MAX - result) return -1; // The sum will overflow an int type result += 4 + ccbits; if (seg.data.size() > static_cast(INT_MAX - result)) return -1; // The sum will overflow an int type result += static_cast(seg.data.size()); } return result; } bool QrSegment::isNumeric(const char *text) { for (; *text != '\0'; text++) { char c = *text; if (c < '0' || c > '9') return false; } return true; } bool QrSegment::isAlphanumeric(const char *text) { for (; *text != '\0'; text++) { if (std::strchr(ALPHANUMERIC_CHARSET, *text) == nullptr) return false; } return true; } const QrSegment::Mode &QrSegment::getMode() const { return *mode; } int QrSegment::getNumChars() const { return numChars; } const std::vector &QrSegment::getData() const { return data; } const char *QrSegment::ALPHANUMERIC_CHARSET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"; /*---- Class QrCode ----*/ int QrCode::getFormatBits(Ecc ecl) { switch (ecl) { case Ecc::LOW : return 1; case Ecc::MEDIUM : return 0; case Ecc::QUARTILE: return 3; case Ecc::HIGH : return 2; default: throw std::logic_error("Unreachable"); } } QrCode QrCode::encodeText(const char *text, Ecc ecl) { vector segs = QrSegment::makeSegments(text); return encodeSegments(segs, ecl); } QrCode QrCode::encodeBinary(const vector &data, Ecc ecl) { vector segs{QrSegment::makeBytes(data)}; return encodeSegments(segs, ecl); } QrCode QrCode::encodeSegments(const vector &segs, Ecc ecl, int minVersion, int maxVersion, int mask, bool boostEcl) { if (!(MIN_VERSION <= minVersion && minVersion <= maxVersion && maxVersion <= MAX_VERSION) || mask < -1 || mask > 7) throw std::invalid_argument("Invalid value"); // Find the minimal version number to use int version, dataUsedBits; for (version = minVersion; ; version++) { int dataCapacityBits = getNumDataCodewords(version, ecl) * 8; // Number of data bits available dataUsedBits = QrSegment::getTotalBits(segs, version); if (dataUsedBits != -1 && dataUsedBits <= dataCapacityBits) break; // This version number is found to be suitable if (version >= maxVersion) { // All versions in the range could not fit the given data std::ostringstream sb; if (dataUsedBits == -1) sb << "Segment too long"; else { sb << "Data length = " << dataUsedBits << " bits, "; sb << "Max capacity = " << dataCapacityBits << " bits"; } throw data_too_long(sb.str()); } } assert(dataUsedBits != -1); // Increase the error correction level while the data still fits in the current version number for (Ecc newEcl : {Ecc::MEDIUM, Ecc::QUARTILE, Ecc::HIGH}) { // From low to high if (boostEcl && dataUsedBits <= getNumDataCodewords(version, newEcl) * 8) ecl = newEcl; } // Concatenate all segments to create the data bit string BitBuffer bb; for (const QrSegment &seg : segs) { bb.appendBits(static_cast(seg.getMode().getModeBits()), 4); bb.appendBits(static_cast(seg.getNumChars()), seg.getMode().numCharCountBits(version)); bb.insert(bb.end(), seg.getData().begin(), seg.getData().end()); } assert(bb.size() == static_cast(dataUsedBits)); // Add terminator and pad up to a byte if applicable size_t dataCapacityBits = static_cast(getNumDataCodewords(version, ecl)) * 8; assert(bb.size() <= dataCapacityBits); bb.appendBits(0, std::min(4, static_cast(dataCapacityBits - bb.size()))); bb.appendBits(0, (8 - static_cast(bb.size() % 8)) % 8); assert(bb.size() % 8 == 0); // Pad with alternating bytes until data capacity is reached for (uint8_t padByte = 0xEC; bb.size() < dataCapacityBits; padByte ^= 0xEC ^ 0x11) bb.appendBits(padByte, 8); // Pack bits into bytes in big endian vector dataCodewords(bb.size() / 8); for (size_t i = 0; i < bb.size(); i++) dataCodewords.at(i >> 3) |= (bb.at(i) ? 1 : 0) << (7 - (i & 7)); // Create the QR Code object return QrCode(version, ecl, dataCodewords, mask); } QrCode::QrCode(int ver, Ecc ecl, const vector &dataCodewords, int msk) : // Initialize fields and check arguments version(ver), errorCorrectionLevel(ecl) { if (ver < MIN_VERSION || ver > MAX_VERSION) throw std::domain_error("Version value out of range"); if (msk < -1 || msk > 7) throw std::domain_error("Mask value out of range"); size = ver * 4 + 17; size_t sz = static_cast(size); modules = vector >(sz, vector(sz)); // Initially all light isFunction = vector >(sz, vector(sz)); // Compute ECC, draw modules drawFunctionPatterns(); const vector allCodewords = addEccAndInterleave(dataCodewords); drawCodewords(allCodewords); // Do masking if (msk == -1) { // Automatically choose best mask long minPenalty = LONG_MAX; for (int i = 0; i < 8; i++) { applyMask(i); drawFormatBits(i); long penalty = getPenaltyScore(); if (penalty < minPenalty) { msk = i; minPenalty = penalty; } applyMask(i); // Undoes the mask due to XOR } } assert(0 <= msk && msk <= 7); mask = msk; applyMask(msk); // Apply the final choice of mask drawFormatBits(msk); // Overwrite old format bits isFunction.clear(); isFunction.shrink_to_fit(); } int QrCode::getVersion() const { return version; } int QrCode::getSize() const { return size; } QrCode::Ecc QrCode::getErrorCorrectionLevel() const { return errorCorrectionLevel; } int QrCode::getMask() const { return mask; } bool QrCode::getModule(int x, int y) const { return 0 <= x && x < size && 0 <= y && y < size && module(x, y); } void QrCode::drawFunctionPatterns() { // Draw horizontal and vertical timing patterns for (int i = 0; i < size; i++) { setFunctionModule(6, i, i % 2 == 0); setFunctionModule(i, 6, i % 2 == 0); } // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) drawFinderPattern(3, 3); drawFinderPattern(size - 4, 3); drawFinderPattern(3, size - 4); // Draw numerous alignment patterns const vector alignPatPos = getAlignmentPatternPositions(); size_t numAlign = alignPatPos.size(); for (size_t i = 0; i < numAlign; i++) { for (size_t j = 0; j < numAlign; j++) { // Don't draw on the three finder corners if (!((i == 0 && j == 0) || (i == 0 && j == numAlign - 1) || (i == numAlign - 1 && j == 0))) drawAlignmentPattern(alignPatPos.at(i), alignPatPos.at(j)); } } // Draw configuration data drawFormatBits(0); // Dummy mask value; overwritten later in the constructor drawVersion(); } void QrCode::drawFormatBits(int msk) { // Calculate error correction code and pack bits int data = getFormatBits(errorCorrectionLevel) << 3 | msk; // errCorrLvl is uint2, msk is uint3 int rem = data; for (int i = 0; i < 10; i++) rem = (rem << 1) ^ ((rem >> 9) * 0x537); int bits = (data << 10 | rem) ^ 0x5412; // uint15 assert(bits >> 15 == 0); // Draw first copy for (int i = 0; i <= 5; i++) setFunctionModule(8, i, getBit(bits, i)); setFunctionModule(8, 7, getBit(bits, 6)); setFunctionModule(8, 8, getBit(bits, 7)); setFunctionModule(7, 8, getBit(bits, 8)); for (int i = 9; i < 15; i++) setFunctionModule(14 - i, 8, getBit(bits, i)); // Draw second copy for (int i = 0; i < 8; i++) setFunctionModule(size - 1 - i, 8, getBit(bits, i)); for (int i = 8; i < 15; i++) setFunctionModule(8, size - 15 + i, getBit(bits, i)); setFunctionModule(8, size - 8, true); // Always dark } void QrCode::drawVersion() { if (version < 7) return; // Calculate error correction code and pack bits int rem = version; // version is uint6, in the range [7, 40] for (int i = 0; i < 12; i++) rem = (rem << 1) ^ ((rem >> 11) * 0x1F25); long bits = static_cast(version) << 12 | rem; // uint18 assert(bits >> 18 == 0); // Draw two copies for (int i = 0; i < 18; i++) { bool bit = getBit(bits, i); int a = size - 11 + i % 3; int b = i / 3; setFunctionModule(a, b, bit); setFunctionModule(b, a, bit); } } void QrCode::drawFinderPattern(int x, int y) { for (int dy = -4; dy <= 4; dy++) { for (int dx = -4; dx <= 4; dx++) { int dist = std::max(std::abs(dx), std::abs(dy)); // Chebyshev/infinity norm int xx = x + dx, yy = y + dy; if (0 <= xx && xx < size && 0 <= yy && yy < size) setFunctionModule(xx, yy, dist != 2 && dist != 4); } } } void QrCode::drawAlignmentPattern(int x, int y) { for (int dy = -2; dy <= 2; dy++) { for (int dx = -2; dx <= 2; dx++) setFunctionModule(x + dx, y + dy, std::max(std::abs(dx), std::abs(dy)) != 1); } } void QrCode::setFunctionModule(int x, int y, bool isDark) { size_t ux = static_cast(x); size_t uy = static_cast(y); modules .at(uy).at(ux) = isDark; isFunction.at(uy).at(ux) = true; } bool QrCode::module(int x, int y) const { return modules.at(static_cast(y)).at(static_cast(x)); } vector QrCode::addEccAndInterleave(const vector &data) const { if (data.size() != static_cast(getNumDataCodewords(version, errorCorrectionLevel))) throw std::invalid_argument("Invalid argument"); // Calculate parameter numbers int numBlocks = NUM_ERROR_CORRECTION_BLOCKS[static_cast(errorCorrectionLevel)][version]; int blockEccLen = ECC_CODEWORDS_PER_BLOCK [static_cast(errorCorrectionLevel)][version]; int rawCodewords = getNumRawDataModules(version) / 8; int numShortBlocks = numBlocks - rawCodewords % numBlocks; int shortBlockLen = rawCodewords / numBlocks; // Split data into blocks and append ECC to each block vector > blocks; const vector rsDiv = reedSolomonComputeDivisor(blockEccLen); for (int i = 0, k = 0; i < numBlocks; i++) { vector dat(data.cbegin() + k, data.cbegin() + (k + shortBlockLen - blockEccLen + (i < numShortBlocks ? 0 : 1))); k += static_cast(dat.size()); const vector ecc = reedSolomonComputeRemainder(dat, rsDiv); if (i < numShortBlocks) dat.push_back(0); dat.insert(dat.end(), ecc.cbegin(), ecc.cend()); blocks.push_back(std::move(dat)); } // Interleave (not concatenate) the bytes from every block into a single sequence vector result; for (size_t i = 0; i < blocks.at(0).size(); i++) { for (size_t j = 0; j < blocks.size(); j++) { // Skip the padding byte in short blocks if (i != static_cast(shortBlockLen - blockEccLen) || j >= static_cast(numShortBlocks)) result.push_back(blocks.at(j).at(i)); } } assert(result.size() == static_cast(rawCodewords)); return result; } void QrCode::drawCodewords(const vector &data) { if (data.size() != static_cast(getNumRawDataModules(version) / 8)) throw std::invalid_argument("Invalid argument"); size_t i = 0; // Bit index into the data // Do the funny zigzag scan for (int right = size - 1; right >= 1; right -= 2) { // Index of right column in each column pair if (right == 6) right = 5; for (int vert = 0; vert < size; vert++) { // Vertical counter for (int j = 0; j < 2; j++) { size_t x = static_cast(right - j); // Actual x coordinate bool upward = ((right + 1) & 2) == 0; size_t y = static_cast(upward ? size - 1 - vert : vert); // Actual y coordinate if (!isFunction.at(y).at(x) && i < data.size() * 8) { modules.at(y).at(x) = getBit(data.at(i >> 3), 7 - static_cast(i & 7)); i++; } // If this QR Code has any remainder bits (0 to 7), they were assigned as // 0/false/light by the constructor and are left unchanged by this method } } } assert(i == data.size() * 8); } void QrCode::applyMask(int msk) { if (msk < 0 || msk > 7) throw std::domain_error("Mask value out of range"); size_t sz = static_cast(size); for (size_t y = 0; y < sz; y++) { for (size_t x = 0; x < sz; x++) { bool invert; switch (msk) { case 0: invert = (x + y) % 2 == 0; break; case 1: invert = y % 2 == 0; break; case 2: invert = x % 3 == 0; break; case 3: invert = (x + y) % 3 == 0; break; case 4: invert = (x / 3 + y / 2) % 2 == 0; break; case 5: invert = x * y % 2 + x * y % 3 == 0; break; case 6: invert = (x * y % 2 + x * y % 3) % 2 == 0; break; case 7: invert = ((x + y) % 2 + x * y % 3) % 2 == 0; break; default: throw std::logic_error("Unreachable"); } modules.at(y).at(x) = modules.at(y).at(x) ^ (invert & !isFunction.at(y).at(x)); } } } long QrCode::getPenaltyScore() const { long result = 0; // Adjacent modules in row having same color, and finder-like patterns for (int y = 0; y < size; y++) { bool runColor = false; int runX = 0; std::array runHistory = {}; for (int x = 0; x < size; x++) { if (module(x, y) == runColor) { runX++; if (runX == 5) result += PENALTY_N1; else if (runX > 5) result++; } else { finderPenaltyAddHistory(runX, runHistory); if (!runColor) result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; runColor = module(x, y); runX = 1; } } result += finderPenaltyTerminateAndCount(runColor, runX, runHistory) * PENALTY_N3; } // Adjacent modules in column having same color, and finder-like patterns for (int x = 0; x < size; x++) { bool runColor = false; int runY = 0; std::array runHistory = {}; for (int y = 0; y < size; y++) { if (module(x, y) == runColor) { runY++; if (runY == 5) result += PENALTY_N1; else if (runY > 5) result++; } else { finderPenaltyAddHistory(runY, runHistory); if (!runColor) result += finderPenaltyCountPatterns(runHistory) * PENALTY_N3; runColor = module(x, y); runY = 1; } } result += finderPenaltyTerminateAndCount(runColor, runY, runHistory) * PENALTY_N3; } // 2*2 blocks of modules having same color for (int y = 0; y < size - 1; y++) { for (int x = 0; x < size - 1; x++) { bool color = module(x, y); if ( color == module(x + 1, y) && color == module(x, y + 1) && color == module(x + 1, y + 1)) result += PENALTY_N2; } } // Balance of dark and light modules int dark = 0; for (const vector &row : modules) { for (bool color : row) { if (color) dark++; } } int total = size * size; // Note that size is odd, so dark/total != 1/2 // Compute the smallest integer k >= 0 such that (45-5k)% <= dark/total <= (55+5k)% int k = static_cast((std::abs(dark * 20L - total * 10L) + total - 1) / total) - 1; assert(0 <= k && k <= 9); result += k * PENALTY_N4; assert(0 <= result && result <= 2568888L); // Non-tight upper bound based on default values of PENALTY_N1, ..., N4 return result; } vector QrCode::getAlignmentPatternPositions() const { if (version == 1) return vector(); else { int numAlign = version / 7 + 2; int step = (version == 32) ? 26 : (version * 4 + numAlign * 2 + 1) / (numAlign * 2 - 2) * 2; vector result; for (int i = 0, pos = size - 7; i < numAlign - 1; i++, pos -= step) result.insert(result.begin(), pos); result.insert(result.begin(), 6); return result; } } int QrCode::getNumRawDataModules(int ver) { if (ver < MIN_VERSION || ver > MAX_VERSION) throw std::domain_error("Version number out of range"); int result = (16 * ver + 128) * ver + 64; if (ver >= 2) { int numAlign = ver / 7 + 2; result -= (25 * numAlign - 10) * numAlign - 55; if (ver >= 7) result -= 36; } assert(208 <= result && result <= 29648); return result; } int QrCode::getNumDataCodewords(int ver, Ecc ecl) { return getNumRawDataModules(ver) / 8 - ECC_CODEWORDS_PER_BLOCK [static_cast(ecl)][ver] * NUM_ERROR_CORRECTION_BLOCKS[static_cast(ecl)][ver]; } vector QrCode::reedSolomonComputeDivisor(int degree) { if (degree < 1 || degree > 255) throw std::domain_error("Degree out of range"); // Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1. // For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array {255, 8, 93}. vector result(static_cast(degree)); result.at(result.size() - 1) = 1; // Start off with the monomial x^0 // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), // and drop the highest monomial term which is always 1x^degree. // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). uint8_t root = 1; for (int i = 0; i < degree; i++) { // Multiply the current product by (x - r^i) for (size_t j = 0; j < result.size(); j++) { result.at(j) = reedSolomonMultiply(result.at(j), root); if (j + 1 < result.size()) result.at(j) ^= result.at(j + 1); } root = reedSolomonMultiply(root, 0x02); } return result; } vector QrCode::reedSolomonComputeRemainder(const vector &data, const vector &divisor) { vector result(divisor.size()); for (uint8_t b : data) { // Polynomial division uint8_t factor = b ^ result.at(0); result.erase(result.begin()); result.push_back(0); for (size_t i = 0; i < result.size(); i++) result.at(i) ^= reedSolomonMultiply(divisor.at(i), factor); } return result; } uint8_t QrCode::reedSolomonMultiply(uint8_t x, uint8_t y) { // Russian peasant multiplication int z = 0; for (int i = 7; i >= 0; i--) { z = (z << 1) ^ ((z >> 7) * 0x11D); z ^= ((y >> i) & 1) * x; } assert(z >> 8 == 0); return static_cast(z); } int QrCode::finderPenaltyCountPatterns(const std::array &runHistory) const { int n = runHistory.at(1); assert(n <= size * 3); bool core = n > 0 && runHistory.at(2) == n && runHistory.at(3) == n * 3 && runHistory.at(4) == n && runHistory.at(5) == n; return (core && runHistory.at(0) >= n * 4 && runHistory.at(6) >= n ? 1 : 0) + (core && runHistory.at(6) >= n * 4 && runHistory.at(0) >= n ? 1 : 0); } int QrCode::finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, std::array &runHistory) const { if (currentRunColor) { // Terminate dark run finderPenaltyAddHistory(currentRunLength, runHistory); currentRunLength = 0; } currentRunLength += size; // Add light border to final run finderPenaltyAddHistory(currentRunLength, runHistory); return finderPenaltyCountPatterns(runHistory); } void QrCode::finderPenaltyAddHistory(int currentRunLength, std::array &runHistory) const { if (runHistory.at(0) == 0) currentRunLength += size; // Add light border to initial run std::copy_backward(runHistory.cbegin(), runHistory.cend() - 1, runHistory.end()); runHistory.at(0) = currentRunLength; } bool QrCode::getBit(long x, int i) { return ((x >> i) & 1) != 0; } /*---- Tables of constants ----*/ const int QrCode::PENALTY_N1 = 3; const int QrCode::PENALTY_N2 = 3; const int QrCode::PENALTY_N3 = 40; const int QrCode::PENALTY_N4 = 10; const int8_t QrCode::ECC_CODEWORDS_PER_BLOCK[4][41] = { // Version: (note that index 0 is for padding, and is set to an illegal value) //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level {-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low {-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium {-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile {-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High }; const int8_t QrCode::NUM_ERROR_CORRECTION_BLOCKS[4][41] = { // Version: (note that index 0 is for padding, and is set to an illegal value) //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level {-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low {-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium {-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile {-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High }; data_too_long::data_too_long(const std::string &msg) : std::length_error(msg) {} /*---- Class BitBuffer ----*/ BitBuffer::BitBuffer() : std::vector() {} void BitBuffer::appendBits(std::uint32_t val, int len) { if (len < 0 || len > 31 || val >> len != 0) throw std::domain_error("Value out of range"); for (int i = len - 1; i >= 0; i--) // Append bit by bit this->push_back(((val >> i) & 1) != 0); } } ================================================ FILE: 3rdparty/qrcodegen.hpp ================================================ /* * QR Code generator library (C++) * * Copyright (c) Project Nayuki. (MIT License) * https://www.nayuki.io/page/qr-code-generator-library * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * - The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * - The Software is provided "as is", without warranty of any kind, express or * implied, including but not limited to the warranties of merchantability, * fitness for a particular purpose and noninfringement. In no event shall the * authors or copyright holders be liable for any claim, damages or other * liability, whether in an action of contract, tort or otherwise, arising from, * out of or in connection with the Software or the use or other dealings in the * Software. */ #pragma once #include #include #include #include #include namespace qrcodegen { /* * A segment of character/binary/control data in a QR Code symbol. * Instances of this class are immutable. * The mid-level way to create a segment is to take the payload data * and call a static factory function such as QrSegment::makeNumeric(). * The low-level way to create a segment is to custom-make the bit buffer * and call the QrSegment() constructor with appropriate values. * This segment class imposes no length restrictions, but QR Codes have restrictions. * Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. * Any segment longer than this is meaningless for the purpose of generating QR Codes. */ class QrSegment final { /*---- Public helper enumeration ----*/ /* * Describes how a segment's data bits are interpreted. Immutable. */ public: class Mode final { /*-- Constants --*/ public: static const Mode NUMERIC; public: static const Mode ALPHANUMERIC; public: static const Mode BYTE; public: static const Mode KANJI; public: static const Mode ECI; /*-- Fields --*/ // The mode indicator bits, which is a uint4 value (range 0 to 15). private: int modeBits; // Number of character count bits for three different version ranges. private: int numBitsCharCount[3]; /*-- Constructor --*/ private: Mode(int mode, int cc0, int cc1, int cc2); /*-- Methods --*/ /* * (Package-private) Returns the mode indicator bits, which is an unsigned 4-bit value (range 0 to 15). */ public: int getModeBits() const; /* * (Package-private) Returns the bit width of the character count field for a segment in * this mode in a QR Code at the given version number. The result is in the range [0, 16]. */ public: int numCharCountBits(int ver) const; }; /*---- Static factory functions (mid level) ----*/ /* * Returns a segment representing the given binary data encoded in * byte mode. All input byte vectors are acceptable. Any text string * can be converted to UTF-8 bytes and encoded as a byte mode segment. */ public: static QrSegment makeBytes(const std::vector &data); /* * Returns a segment representing the given string of decimal digits encoded in numeric mode. */ public: static QrSegment makeNumeric(const char *digits); /* * Returns a segment representing the given text string encoded in alphanumeric mode. * The characters allowed are: 0 to 9, A to Z (uppercase only), space, * dollar, percent, asterisk, plus, hyphen, period, slash, colon. */ public: static QrSegment makeAlphanumeric(const char *text); /* * Returns a list of zero or more segments to represent the given text string. The result * may use various segment modes and switch modes to optimize the length of the bit stream. */ public: static std::vector makeSegments(const char *text); /* * Returns a segment representing an Extended Channel Interpretation * (ECI) designator with the given assignment value. */ public: static QrSegment makeEci(long assignVal); /*---- Public static helper functions ----*/ /* * Tests whether the given string can be encoded as a segment in numeric mode. * A string is encodable iff each character is in the range 0 to 9. */ public: static bool isNumeric(const char *text); /* * Tests whether the given string can be encoded as a segment in alphanumeric mode. * A string is encodable iff each character is in the following set: 0 to 9, A to Z * (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. */ public: static bool isAlphanumeric(const char *text); /*---- Instance fields ----*/ /* The mode indicator of this segment. Accessed through getMode(). */ private: const Mode *mode; /* The length of this segment's unencoded data. Measured in characters for * numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. * Always zero or positive. Not the same as the data's bit length. * Accessed through getNumChars(). */ private: int numChars; /* The data bits of this segment. Accessed through getData(). */ private: std::vector data; /*---- Constructors (low level) ----*/ /* * Creates a new QR Code segment with the given attributes and data. * The character count (numCh) must agree with the mode and the bit buffer length, * but the constraint isn't checked. The given bit buffer is copied and stored. */ public: QrSegment(const Mode &md, int numCh, const std::vector &dt); /* * Creates a new QR Code segment with the given parameters and data. * The character count (numCh) must agree with the mode and the bit buffer length, * but the constraint isn't checked. The given bit buffer is moved and stored. */ public: QrSegment(const Mode &md, int numCh, std::vector &&dt); /*---- Methods ----*/ /* * Returns the mode field of this segment. */ public: const Mode &getMode() const; /* * Returns the character count field of this segment. */ public: int getNumChars() const; /* * Returns the data bits of this segment. */ public: const std::vector &getData() const; // (Package-private) Calculates the number of bits needed to encode the given segments at // the given version. Returns a non-negative number if successful. Otherwise returns -1 if a // segment has too many characters to fit its length field, or the total bits exceeds INT_MAX. public: static int getTotalBits(const std::vector &segs, int version); /*---- Private constant ----*/ /* The set of all legal characters in alphanumeric mode, where * each character value maps to the index in the string. */ private: static const char *ALPHANUMERIC_CHARSET; }; /* * A QR Code symbol, which is a type of two-dimension barcode. * Invented by Denso Wave and described in the ISO/IEC 18004 standard. * Instances of this class represent an immutable square grid of dark and light cells. * The class provides static factory functions to create a QR Code from text or binary data. * The class covers the QR Code Model 2 specification, supporting all versions (sizes) * from 1 to 40, all 4 error correction levels, and 4 character encoding modes. * * Ways to create a QR Code object: * - High level: Take the payload data and call QrCode::encodeText() or QrCode::encodeBinary(). * - Mid level: Custom-make the list of segments and call QrCode::encodeSegments(). * - Low level: Custom-make the array of data codeword bytes (including * segment headers and final padding, excluding error correction codewords), * supply the appropriate version number, and call the QrCode() constructor. * (Note that all ways require supplying the desired error correction level.) */ class QrCode final { /*---- Public helper enumeration ----*/ /* * The error correction level in a QR Code symbol. */ public: enum class Ecc { LOW = 0 , // The QR Code can tolerate about 7% erroneous codewords MEDIUM , // The QR Code can tolerate about 15% erroneous codewords QUARTILE, // The QR Code can tolerate about 25% erroneous codewords HIGH , // The QR Code can tolerate about 30% erroneous codewords }; // Returns a value in the range 0 to 3 (unsigned 2-bit integer). private: static int getFormatBits(Ecc ecl); /*---- Static factory functions (high level) ----*/ /* * Returns a QR Code representing the given Unicode text string at the given error correction level. * As a conservative upper bound, this function is guaranteed to succeed for strings that have 2953 or fewer * UTF-8 code units (not Unicode code points) if the low error correction level is used. The smallest possible * QR Code version is automatically chosen for the output. The ECC level of the result may be higher than * the ecl argument if it can be done without increasing the version. */ public: static QrCode encodeText(const char *text, Ecc ecl); /* * Returns a QR Code representing the given binary data at the given error correction level. * This function always encodes using the binary segment mode, not any text mode. The maximum number of * bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output. * The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version. */ public: static QrCode encodeBinary(const std::vector &data, Ecc ecl); /*---- Static factory functions (mid level) ----*/ /* * Returns a QR Code representing the given segments with the given encoding parameters. * The smallest possible QR Code version within the given range is automatically * chosen for the output. Iff boostEcl is true, then the ECC level of the result * may be higher than the ecl argument if it can be done without increasing the * version. The mask number is either between 0 to 7 (inclusive) to force that * mask, or -1 to automatically choose an appropriate mask (which may be slow). * This function allows the user to create a custom sequence of segments that switches * between modes (such as alphanumeric and byte) to encode text in less space. * This is a mid-level API; the high-level API is encodeText() and encodeBinary(). */ public: static QrCode encodeSegments(const std::vector &segs, Ecc ecl, int minVersion=1, int maxVersion=40, int mask=-1, bool boostEcl=true); // All optional parameters /*---- Instance fields ----*/ // Immutable scalar parameters: /* The version number of this QR Code, which is between 1 and 40 (inclusive). * This determines the size of this barcode. */ private: int version; /* The width and height of this QR Code, measured in modules, between * 21 and 177 (inclusive). This is equal to version * 4 + 17. */ private: int size; /* The error correction level used in this QR Code. */ private: Ecc errorCorrectionLevel; /* The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive). * Even if a QR Code is created with automatic masking requested (mask = -1), * the resulting object still has a mask value between 0 and 7. */ private: int mask; // Private grids of modules/pixels, with dimensions of size*size: // The modules of this QR Code (false = light, true = dark). // Immutable after constructor finishes. Accessed through getModule(). private: std::vector > modules; // Indicates function modules that are not subjected to masking. Discarded when constructor finishes. private: std::vector > isFunction; /*---- Constructor (low level) ----*/ /* * Creates a new QR Code with the given version number, * error correction level, data codeword bytes, and mask number. * This is a low-level API that most users should not use directly. * A mid-level API is the encodeSegments() function. */ public: QrCode(int ver, Ecc ecl, const std::vector &dataCodewords, int msk); /*---- Public instance methods ----*/ /* * Returns this QR Code's version, in the range [1, 40]. */ public: int getVersion() const; /* * Returns this QR Code's size, in the range [21, 177]. */ public: int getSize() const; /* * Returns this QR Code's error correction level. */ public: Ecc getErrorCorrectionLevel() const; /* * Returns this QR Code's mask, in the range [0, 7]. */ public: int getMask() const; /* * Returns the color of the module (pixel) at the given coordinates, which is false * for light or true for dark. The top left corner has the coordinates (x=0, y=0). * If the given coordinates are out of bounds, then false (light) is returned. */ public: bool getModule(int x, int y) const; /*---- Private helper methods for constructor: Drawing function modules ----*/ // Reads this object's version field, and draws and marks all function modules. private: void drawFunctionPatterns(); // Draws two copies of the format bits (with its own error correction code) // based on the given mask and this object's error correction level field. private: void drawFormatBits(int msk); // Draws two copies of the version bits (with its own error correction code), // based on this object's version field, iff 7 <= version <= 40. private: void drawVersion(); // Draws a 9*9 finder pattern including the border separator, // with the center module at (x, y). Modules can be out of bounds. private: void drawFinderPattern(int x, int y); // Draws a 5*5 alignment pattern, with the center module // at (x, y). All modules must be in bounds. private: void drawAlignmentPattern(int x, int y); // Sets the color of a module and marks it as a function module. // Only used by the constructor. Coordinates must be in bounds. private: void setFunctionModule(int x, int y, bool isDark); // Returns the color of the module at the given coordinates, which must be in range. private: bool module(int x, int y) const; /*---- Private helper methods for constructor: Codewords and masking ----*/ // Returns a new byte string representing the given data with the appropriate error correction // codewords appended to it, based on this object's version and error correction level. private: std::vector addEccAndInterleave(const std::vector &data) const; // Draws the given sequence of 8-bit codewords (data and error correction) onto the entire // data area of this QR Code. Function modules need to be marked off before this is called. private: void drawCodewords(const std::vector &data); // XORs the codeword modules in this QR Code with the given mask pattern. // The function modules must be marked and the codeword bits must be drawn // before masking. Due to the arithmetic of XOR, calling applyMask() with // the same mask value a second time will undo the mask. A final well-formed // QR Code needs exactly one (not zero, two, etc.) mask applied. private: void applyMask(int msk); // Calculates and returns the penalty score based on state of this QR Code's current modules. // This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. private: long getPenaltyScore() const; /*---- Private helper functions ----*/ // Returns an ascending list of positions of alignment patterns for this version number. // Each position is in the range [0,177), and are used on both the x and y axes. // This could be implemented as lookup table of 40 variable-length lists of unsigned bytes. private: std::vector getAlignmentPatternPositions() const; // Returns the number of data bits that can be stored in a QR Code of the given version number, after // all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. // The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. private: static int getNumRawDataModules(int ver); // Returns the number of 8-bit data (i.e. not error correction) codewords contained in any // QR Code of the given version number and error correction level, with remainder bits discarded. // This stateless pure function could be implemented as a (40*4)-cell lookup table. private: static int getNumDataCodewords(int ver, Ecc ecl); // Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be // implemented as a lookup table over all possible parameter values, instead of as an algorithm. private: static std::vector reedSolomonComputeDivisor(int degree); // Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials. private: static std::vector reedSolomonComputeRemainder(const std::vector &data, const std::vector &divisor); // Returns the product of the two given field elements modulo GF(2^8/0x11D). // All inputs are valid. This could be implemented as a 256*256 lookup table. private: static std::uint8_t reedSolomonMultiply(std::uint8_t x, std::uint8_t y); // Can only be called immediately after a light run is added, and // returns either 0, 1, or 2. A helper function for getPenaltyScore(). private: int finderPenaltyCountPatterns(const std::array &runHistory) const; // Must be called at the end of a line (row or column) of modules. A helper function for getPenaltyScore(). private: int finderPenaltyTerminateAndCount(bool currentRunColor, int currentRunLength, std::array &runHistory) const; // Pushes the given value to the front and drops the last value. A helper function for getPenaltyScore(). private: void finderPenaltyAddHistory(int currentRunLength, std::array &runHistory) const; // Returns true iff the i'th bit of x is set to 1. private: static bool getBit(long x, int i); /*---- Constants and tables ----*/ // The minimum version number supported in the QR Code Model 2 standard. public: static constexpr int MIN_VERSION = 1; // The maximum version number supported in the QR Code Model 2 standard. public: static constexpr int MAX_VERSION = 40; // For use in getPenaltyScore(), when evaluating which mask is best. private: static const int PENALTY_N1; private: static const int PENALTY_N2; private: static const int PENALTY_N3; private: static const int PENALTY_N4; private: static const std::int8_t ECC_CODEWORDS_PER_BLOCK[4][41]; private: static const std::int8_t NUM_ERROR_CORRECTION_BLOCKS[4][41]; }; /*---- Public exception class ----*/ /* * Thrown when the supplied data does not fit any QR Code version. Ways to handle this exception include: * - Decrease the error correction level if it was greater than Ecc::LOW. * - If the encodeSegments() function was called with a maxVersion argument, then increase * it if it was less than QrCode::MAX_VERSION. (This advice does not apply to the other * factory functions because they search all versions up to QrCode::MAX_VERSION.) * - Split the text data into better or optimal segments in order to reduce the number of bits required. * - Change the text or binary data to be shorter. * - Change the text to fit the character set of a particular segment mode (e.g. alphanumeric). * - Propagate the error upward to the caller/user. */ class data_too_long : public std::length_error { public: explicit data_too_long(const std::string &msg); }; /* * An appendable sequence of bits (0s and 1s). Mainly used by QrSegment. */ class BitBuffer final : public std::vector { /*---- Constructor ----*/ // Creates an empty bit buffer (length 0). public: BitBuffer(); /*---- Method ----*/ // Appends the given number of low-order bits of the given value // to this buffer. Requires 0 <= len <= 31 and val < 2^len. public: void appendBits(std::uint32_t val, int len); }; } ================================================ FILE: 3rdparty/qscopeguard.h ================================================ /**************************************************************************** ** ** Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Sérgio Martins ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef QSCOPEGUARD_H #define QSCOPEGUARD_H #include QT_BEGIN_NAMESPACE template class QScopeGuard; template QScopeGuard qScopeGuard(F f); template class QScopeGuard { public: QScopeGuard(QScopeGuard &&other) Q_DECL_NOEXCEPT : m_func(std::move(other.m_func)) , m_invoke(other.m_invoke) { other.dismiss(); } ~QScopeGuard() { if (m_invoke) m_func(); } void dismiss() Q_DECL_NOEXCEPT { m_invoke = false; } private: explicit QScopeGuard(F f) Q_DECL_NOEXCEPT : m_func(std::move(f)) { } Q_DISABLE_COPY(QScopeGuard) F m_func; bool m_invoke = true; friend QScopeGuard qScopeGuard(F); }; template QScopeGuard qScopeGuard(F f) { return QScopeGuard(std::move(f)); } QT_END_NAMESPACE #endif // QSCOPEGUARD_H ================================================ FILE: 3rdparty/qv2ray/v2/components/proxy/QvProxyConfigurator.cpp ================================================ #include "QvProxyConfigurator.hpp" #ifdef Q_OS_WIN // #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include // #include #include #include #include #endif #include #include #include "3rdparty/fix_old_qt.h" #include "3rdparty/qv2ray/wrapper.hpp" #include "fmt/Preset.hpp" #include "main/NekoGui.hpp" #define QV_MODULE_NAME "SystemProxy" #define QSTRN(num) QString::number(num) namespace Qv2ray::components::proxy { using ProcessArgument = QPair; #ifdef Q_OS_MACOS QStringList macOSgetNetworkServices() { QProcess p; p.setProgram("/usr/sbin/networksetup"); p.setArguments(QStringList{"-listallnetworkservices"}); p.start(); p.waitForStarted(); p.waitForFinished(); LOG(p.errorString()); auto str = p.readAllStandardOutput(); auto lines = SplitLines(str); QStringList result; // Start from 1 since first line is unneeded. for (auto i = 1; i < lines.count(); i++) { // * means disabled. if (!lines[i].contains("*")) { result << lines[i]; } } LOG("Found " + QSTRN(result.size()) + " network services: " + result.join(";")); return result; } #endif #ifdef Q_OS_WIN #define NO_CONST(expr) const_cast(expr) // static auto DEFAULT_CONNECTION_NAME = // NO_CONST(L"DefaultConnectionSettings"); /// /// INTERNAL FUNCTION bool __QueryProxyOptions() { INTERNET_PER_CONN_OPTION_LIST List; INTERNET_PER_CONN_OPTION Option[5]; // unsigned long nSize = sizeof(INTERNET_PER_CONN_OPTION_LIST); Option[0].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL; Option[1].dwOption = INTERNET_PER_CONN_AUTODISCOVERY_FLAGS; Option[2].dwOption = INTERNET_PER_CONN_FLAGS; Option[3].dwOption = INTERNET_PER_CONN_PROXY_BYPASS; Option[4].dwOption = INTERNET_PER_CONN_PROXY_SERVER; // List.dwSize = sizeof(INTERNET_PER_CONN_OPTION_LIST); List.pszConnection = nullptr; // NO_CONST(DEFAULT_CONNECTION_NAME); List.dwOptionCount = 5; List.dwOptionError = 0; List.pOptions = Option; if (!InternetQueryOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, &nSize)) { LOG("InternetQueryOption failed, GLE=" + QSTRN(GetLastError())); } LOG("System default proxy info:"); if (Option[0].Value.pszValue != nullptr) { LOG(QString::fromWCharArray(Option[0].Value.pszValue)); } if ((Option[2].Value.dwValue & PROXY_TYPE_AUTO_PROXY_URL) == PROXY_TYPE_AUTO_PROXY_URL) { LOG("PROXY_TYPE_AUTO_PROXY_URL"); } if ((Option[2].Value.dwValue & PROXY_TYPE_AUTO_DETECT) == PROXY_TYPE_AUTO_DETECT) { LOG("PROXY_TYPE_AUTO_DETECT"); } if ((Option[2].Value.dwValue & PROXY_TYPE_DIRECT) == PROXY_TYPE_DIRECT) { LOG("PROXY_TYPE_DIRECT"); } if ((Option[2].Value.dwValue & PROXY_TYPE_PROXY) == PROXY_TYPE_PROXY) { LOG("PROXY_TYPE_PROXY"); } if (!InternetQueryOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &List, &nSize)) { LOG("InternetQueryOption failed,GLE=" + QSTRN(GetLastError())); } if (Option[4].Value.pszValue != nullptr) { LOG(QString::fromStdWString(Option[4].Value.pszValue)); } INTERNET_VERSION_INFO Version; nSize = sizeof(INTERNET_VERSION_INFO); InternetQueryOption(nullptr, INTERNET_OPTION_VERSION, &Version, &nSize); if (Option[0].Value.pszValue != nullptr) { GlobalFree(Option[0].Value.pszValue); } if (Option[3].Value.pszValue != nullptr) { GlobalFree(Option[3].Value.pszValue); } if (Option[4].Value.pszValue != nullptr) { GlobalFree(Option[4].Value.pszValue); } return false; } bool __SetProxyOptions(LPWSTR proxy_full_addr, bool isPAC) { INTERNET_PER_CONN_OPTION_LIST list; DWORD dwBufSize = sizeof(list); // Fill the list structure. list.dwSize = sizeof(list); // NULL == LAN, otherwise connectoid name. list.pszConnection = nullptr; if (nullptr == proxy_full_addr) { LOG("Clearing system proxy"); // list.dwOptionCount = 1; list.pOptions = new INTERNET_PER_CONN_OPTION[1]; // Ensure that the memory was allocated. if (nullptr == list.pOptions) { // Return if the memory wasn't allocated. return false; } // Set flags. list.pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS; list.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT; } else if (isPAC) { LOG("Setting system proxy for PAC"); // list.dwOptionCount = 2; list.pOptions = new INTERNET_PER_CONN_OPTION[2]; if (nullptr == list.pOptions) { return false; } // Set flags. list.pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS; list.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_AUTO_PROXY_URL; // Set proxy name. list.pOptions[1].dwOption = INTERNET_PER_CONN_AUTOCONFIG_URL; list.pOptions[1].Value.pszValue = proxy_full_addr; } else { LOG("Setting system proxy for Global Proxy"); // list.dwOptionCount = 2; list.pOptions = new INTERNET_PER_CONN_OPTION[2]; if (nullptr == list.pOptions) { return false; } // Set flags. list.pOptions[0].dwOption = INTERNET_PER_CONN_FLAGS; list.pOptions[0].Value.dwValue = PROXY_TYPE_DIRECT | PROXY_TYPE_PROXY; // Set proxy name. list.pOptions[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER; list.pOptions[1].Value.pszValue = proxy_full_addr; // Set proxy override. // list.pOptions[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS; // auto localhost = L"localhost"; // list.pOptions[2].Value.pszValue = NO_CONST(localhost); } // Set proxy for LAN. if (!InternetSetOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, dwBufSize)) { LOG("InternetSetOption failed for LAN, GLE=" + QSTRN(GetLastError())); } RASENTRYNAME entry; entry.dwSize = sizeof(entry); std::vector entries; DWORD size = sizeof(entry), count; LPRASENTRYNAME entryAddr = &entry; auto ret = RasEnumEntries(nullptr, nullptr, entryAddr, &size, &count); if (ERROR_BUFFER_TOO_SMALL == ret) { entries.resize(count); entries[0].dwSize = sizeof(RASENTRYNAME); entryAddr = entries.data(); ret = RasEnumEntries(nullptr, nullptr, entryAddr, &size, &count); } if (ERROR_SUCCESS != ret) { LOG("Failed to list entry names"); return false; } // Set proxy for each connectoid. for (DWORD i = 0; i < count; ++i) { list.pszConnection = entryAddr[i].szEntryName; if (!InternetSetOption(nullptr, INTERNET_OPTION_PER_CONNECTION_OPTION, &list, dwBufSize)) { LOG("InternetSetOption failed for connectoid " + QString::fromWCharArray(list.pszConnection) + ", GLE=" + QSTRN(GetLastError())); } } delete[] list.pOptions; InternetSetOption(nullptr, INTERNET_OPTION_SETTINGS_CHANGED, nullptr, 0); InternetSetOption(nullptr, INTERNET_OPTION_REFRESH, nullptr, 0); return true; } #endif void SetSystemProxy(int httpPort, int socksPort) { const QString &address = "127.0.0.1"; bool hasHTTP = (httpPort > 0 && httpPort < 65536); bool hasSOCKS = (socksPort > 0 && socksPort < 65536); #ifdef Q_OS_WIN if (!hasHTTP) { LOG("Nothing?"); return; } else { LOG("Qv2ray will set system proxy to use HTTP"); } #else if (!hasHTTP && !hasSOCKS) { LOG("Nothing?"); return; } if (hasHTTP) { LOG("Qv2ray will set system proxy to use HTTP"); } if (hasSOCKS) { LOG("Qv2ray will set system proxy to use SOCKS"); } #endif #ifdef Q_OS_WIN QString str = NekoGui::dataStore->system_proxy_format; if (str.isEmpty()) str = Preset::Windows::system_proxy_format[0]; str = str.replace("{ip}", address) .replace("{http_port}", Int2String(httpPort)) .replace("{socks_port}", Int2String(socksPort)); // LOG("Windows proxy string: " + str); auto proxyStrW = new WCHAR[str.length() + 1]; wcscpy(proxyStrW, str.toStdWString().c_str()); // __QueryProxyOptions(); if (!__SetProxyOptions(proxyStrW, false)) { LOG("Failed to set proxy."); } __QueryProxyOptions(); #elif defined(Q_OS_LINUX) QList actions; actions << ProcessArgument{"gsettings", {"set", "org.gnome.system.proxy", "mode", "manual"}}; // bool isKDE = qEnvironmentVariable("XDG_SESSION_DESKTOP") == "KDE" || qEnvironmentVariable("XDG_SESSION_DESKTOP") == "plasma"; const auto configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); // // Configure HTTP Proxies for HTTP, FTP and HTTPS if (hasHTTP) { // iterate over protocols... for (const auto &protocol: QStringList{"http", "ftp", "https"}) { // for GNOME: { actions << ProcessArgument{"gsettings", {"set", "org.gnome.system.proxy." + protocol, "host", address}}; actions << ProcessArgument{"gsettings", {"set", "org.gnome.system.proxy." + protocol, "port", QSTRN(httpPort)}}; } // for KDE: if (isKDE) { actions << ProcessArgument{"kwriteconfig5", {"--file", configPath + "/kioslaverc", // "--group", "Proxy Settings", // "--key", protocol + "Proxy", // "http://" + address + " " + QSTRN(httpPort)}}; } } } // Configure SOCKS5 Proxies if (hasSOCKS) { // for GNOME: { actions << ProcessArgument{"gsettings", {"set", "org.gnome.system.proxy.socks", "host", address}}; actions << ProcessArgument{"gsettings", {"set", "org.gnome.system.proxy.socks", "port", QSTRN(socksPort)}}; // for KDE: if (isKDE) { actions << ProcessArgument{"kwriteconfig5", {"--file", configPath + "/kioslaverc", // "--group", "Proxy Settings", // "--key", "socksProxy", // "socks://" + address + " " + QSTRN(socksPort)}}; } } } // Setting Proxy Mode to Manual { // for GNOME: { actions << ProcessArgument{"gsettings", {"set", "org.gnome.system.proxy", "mode", "manual"}}; } // for KDE: if (isKDE) { actions << ProcessArgument{"kwriteconfig5", {"--file", configPath + "/kioslaverc", // "--group", "Proxy Settings", // "--key", "ProxyType", "1"}}; } } // Notify kioslaves to reload system proxy configuration. if (isKDE) { actions << ProcessArgument{"dbus-send", {"--type=signal", "/KIO/Scheduler", // "org.kde.KIO.Scheduler.reparseSlaveConfiguration", // "string:''"}}; } // Execute them all! // // note: do not use std::all_of / any_of / none_of, // because those are short-circuit and cannot guarantee atomicity. QList results; for (const auto &action: actions) { // execute and get the code const auto returnCode = QProcess::execute(action.first, action.second); // print out the commands and result codes DEBUG(QStringLiteral("[%1] Program: %2, Args: %3").arg(returnCode).arg(action.first).arg(action.second.join(";"))); // give the code back results << (returnCode == QProcess::NormalExit); } if (results.count(true) != actions.size()) { LOG("Something wrong when setting proxies."); } #else for (const auto &service: macOSgetNetworkServices()) { LOG("Setting proxy for interface: " + service); if (hasHTTP) { QProcess::execute("/usr/sbin/networksetup", {"-setwebproxystate", service, "on"}); QProcess::execute("/usr/sbin/networksetup", {"-setsecurewebproxystate", service, "on"}); QProcess::execute("/usr/sbin/networksetup", {"-setwebproxy", service, address, QSTRN(httpPort)}); QProcess::execute("/usr/sbin/networksetup", {"-setsecurewebproxy", service, address, QSTRN(httpPort)}); } if (hasSOCKS) { QProcess::execute("/usr/sbin/networksetup", {"-setsocksfirewallproxystate", service, "on"}); QProcess::execute("/usr/sbin/networksetup", {"-setsocksfirewallproxy", service, address, QSTRN(socksPort)}); } } #endif } void ClearSystemProxy() { LOG("Clearing System Proxy"); #ifdef Q_OS_WIN if (!__SetProxyOptions(nullptr, false)) { LOG("Failed to clear proxy."); } #elif defined(Q_OS_LINUX) QList actions; const bool isKDE = qEnvironmentVariable("XDG_SESSION_DESKTOP") == "KDE" || qEnvironmentVariable("XDG_SESSION_DESKTOP") == "plasma"; const auto configRoot = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); // Setting System Proxy Mode to: None { // for GNOME: { actions << ProcessArgument{"gsettings", {"set", "org.gnome.system.proxy", "mode", "none"}}; } // for KDE: if (isKDE) { actions << ProcessArgument{"kwriteconfig5", {"--file", configRoot + "/kioslaverc", // "--group", "Proxy Settings", // "--key", "ProxyType", "0"}}; } } // Notify kioslaves to reload system proxy configuration. if (isKDE) { actions << ProcessArgument{"dbus-send", {"--type=signal", "/KIO/Scheduler", // "org.kde.KIO.Scheduler.reparseSlaveConfiguration", // "string:''"}}; } // Execute the Actions for (const auto &action: actions) { // execute and get the code const auto returnCode = QProcess::execute(action.first, action.second); // print out the commands and result codes DEBUG(QStringLiteral("[%1] Program: %2, Args: %3").arg(returnCode).arg(action.first).arg(action.second.join(";"))); } #else for (const auto &service: macOSgetNetworkServices()) { LOG("Clearing proxy for interface: " + service); QProcess::execute("/usr/sbin/networksetup", {"-setautoproxystate", service, "off"}); QProcess::execute("/usr/sbin/networksetup", {"-setwebproxystate", service, "off"}); QProcess::execute("/usr/sbin/networksetup", {"-setsecurewebproxystate", service, "off"}); QProcess::execute("/usr/sbin/networksetup", {"-setsocksfirewallproxystate", service, "off"}); } #endif } } // namespace Qv2ray::components::proxy ================================================ FILE: 3rdparty/qv2ray/v2/components/proxy/QvProxyConfigurator.hpp ================================================ #pragma once #include #include #include // namespace Qv2ray::components::proxy { void ClearSystemProxy(); void SetSystemProxy(int http_port, int socks_port); } // namespace Qv2ray::components::proxy using namespace Qv2ray::components; using namespace Qv2ray::components::proxy; ================================================ FILE: 3rdparty/qv2ray/v2/ui/QvAutoCompleteTextEdit.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the examples of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:BSD$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** BSD License Usage ** Alternatively, you may use this file under the terms of the BSD license ** as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of The Qt Company Ltd nor the names of its ** contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "QvAutoCompleteTextEdit.hpp" #include #include #include #include #include #include #include #include #include #include namespace Qv2ray::ui::widgets { AutoCompleteTextEdit::AutoCompleteTextEdit(const QString &prefix, const QStringList &sourceStrings, QWidget *parent) : QPlainTextEdit(parent) { this->prefix = prefix; this->setLineWrapMode(QPlainTextEdit::NoWrap); c = new QCompleter(this); c->setModel(new QStringListModel(sourceStrings, c)); c->setWidget(this); c->setCompletionMode(QCompleter::PopupCompletion); c->setCaseSensitivity(Qt::CaseInsensitive); QObject::connect(c, static_cast(&QCompleter::activated), this, &AutoCompleteTextEdit::insertCompletion); } AutoCompleteTextEdit::~AutoCompleteTextEdit() { } void AutoCompleteTextEdit::insertCompletion(const QString &completion) { QTextCursor tc = textCursor(); int extra = completion.length() - c->completionPrefix().length(); tc.movePosition(QTextCursor::Left); tc.movePosition(QTextCursor::EndOfWord); tc.insertText(completion.right(extra).toLower()); setTextCursor(tc); } QString AutoCompleteTextEdit::lineUnderCursor() const { QTextCursor tc = textCursor(); tc.select(QTextCursor::LineUnderCursor); return tc.selectedText(); } QString AutoCompleteTextEdit::wordUnderCursor() const { QTextCursor tc = textCursor(); tc.select(QTextCursor::WordUnderCursor); return tc.selectedText(); } void AutoCompleteTextEdit::focusInEvent(QFocusEvent *e) { if (c) c->setWidget(this); QPlainTextEdit::focusInEvent(e); } void AutoCompleteTextEdit::keyPressEvent(QKeyEvent *e) { const bool hasCtrlOrShiftModifier = e->modifiers().testFlag(Qt::ControlModifier) || e->modifiers().testFlag(Qt::ShiftModifier); const bool hasOtherModifiers = (e->modifiers() != Qt::NoModifier) && !hasCtrlOrShiftModifier; // has other modifiers // const bool isSpace = (e->modifiers().testFlag(Qt::ShiftModifier) || e->modifiers().testFlag(Qt::NoModifier)) // && e->key() == Qt::Key_Space; const bool isTab = (e->modifiers().testFlag(Qt::NoModifier) && e->key() == Qt::Key_Tab); const bool isOtherSpace = e->text() == " "; // if (isSpace || isTab || isOtherSpace) { QToolTip::showText(this->mapToGlobal(QPoint(0, 0)), tr("You can not input space characters here."), this, QRect{}, 2000); return; } // if (c && c->popup()->isVisible()) { // The following keys are forwarded by the completer to the widget switch (e->key()) { case Qt::Key_Enter: case Qt::Key_Return: case Qt::Key_Escape: case Qt::Key_Tab: case Qt::Key_Backtab: e->ignore(); return; // let the completer do default behavior default: break; } } QPlainTextEdit::keyPressEvent(e); if (!c || (hasCtrlOrShiftModifier && e->text().isEmpty())) return; // if we have other modifiers, or the text is empty, or the line does not start with our prefix. if (hasOtherModifiers || e->text().isEmpty() || !lineUnderCursor().startsWith(prefix)) { c->popup()->hide(); return; } if (auto word = wordUnderCursor(); word != c->completionPrefix()) { c->setCompletionPrefix(word); c->popup()->setCurrentIndex(c->completionModel()->index(0, 0)); } QRect cr = cursorRect(); cr.setWidth(c->popup()->sizeHintForColumn(0) + c->popup()->verticalScrollBar()->sizeHint().width()); c->complete(cr); // popup it up! } } // namespace Qv2ray::ui::widgets ================================================ FILE: 3rdparty/qv2ray/v2/ui/QvAutoCompleteTextEdit.hpp ================================================ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the examples of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:BSD$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** BSD License Usage ** Alternatively, you may use this file under the terms of the BSD license ** as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of The Qt Company Ltd nor the names of its ** contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #pragma once #include #include QT_BEGIN_NAMESPACE class QCompleter; QT_END_NAMESPACE namespace Qv2ray { namespace ui { namespace widgets { class AutoCompleteTextEdit : public QPlainTextEdit { Q_OBJECT public: AutoCompleteTextEdit(const QString &prefix, const QStringList &sourceStrings, QWidget *parent = nullptr); ~AutoCompleteTextEdit(); protected: void keyPressEvent(QKeyEvent *e) override; void focusInEvent(QFocusEvent *e) override; private slots: void insertCompletion(const QString &completion); private: QString lineUnderCursor() const; QString wordUnderCursor() const; QString prefix; QCompleter *c = nullptr; }; } // namespace widgets } // namespace ui } // namespace Qv2ray using namespace Qv2ray::ui::widgets; ================================================ FILE: 3rdparty/qv2ray/v2/ui/widgets/common/QJsonModel.cpp ================================================ /* * The MIT License (MIT) * * Copyright (c) 2011 SCHUTZ Sacha * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "QJsonModel.hpp" #include #include QJsonTreeItem::QJsonTreeItem(QJsonTreeItem *parent) { mParent = parent; } QJsonTreeItem::~QJsonTreeItem() { qDeleteAll(mChilds); } void QJsonTreeItem::appendChild(QJsonTreeItem *item) { mChilds.append(item); } QJsonTreeItem *QJsonTreeItem::child(int row) { return mChilds.value(row); } QJsonTreeItem *QJsonTreeItem::parent() { return mParent; } int QJsonTreeItem::childCount() const { return mChilds.count(); } int QJsonTreeItem::row() const { if (mParent) return mParent->mChilds.indexOf(const_cast(this)); return 0; } void QJsonTreeItem::setKey(const QString &key) { mKey = key; } void QJsonTreeItem::setValue(const QString &value) { mValue = value; } void QJsonTreeItem::setType(const QJsonValue::Type &type) { mType = type; } QString QJsonTreeItem::key() const { return mKey; } QString QJsonTreeItem::value() const { return mValue; } QJsonValue::Type QJsonTreeItem::type() const { return mType; } QJsonTreeItem *QJsonTreeItem::load(const QJsonValue &value, QJsonTreeItem *parent) { QJsonTreeItem *rootItem = new QJsonTreeItem(parent); rootItem->setKey("root"); if (value.isObject()) { // Get all QJsonValue childs for (QString key: value.toObject().keys()) { QJsonValue v = value.toObject().value(key); QJsonTreeItem *child = load(v, rootItem); child->setKey(key); child->setType(v.type()); rootItem->appendChild(child); } } else if (value.isArray()) { // Get all QJsonValue childs int index = 0; for (QJsonValue v: value.toArray()) { QJsonTreeItem *child = load(v, rootItem); child->setKey(QString::number(index)); child->setType(v.type()); rootItem->appendChild(child); ++index; } } else { rootItem->setValue(value.toVariant().toString()); rootItem->setType(value.type()); } return rootItem; } //========================================================================= QJsonModel::QJsonModel(QObject *parent) : QAbstractItemModel(parent), mRootItem{new QJsonTreeItem} { mHeaders.append("key"); mHeaders.append("value"); } QJsonModel::QJsonModel(const QString &fileName, QObject *parent) : QAbstractItemModel(parent), mRootItem{new QJsonTreeItem} { mHeaders.append("key"); mHeaders.append("value"); load(fileName); } QJsonModel::QJsonModel(QIODevice *device, QObject *parent) : QAbstractItemModel(parent), mRootItem{new QJsonTreeItem} { mHeaders.append("key"); mHeaders.append("value"); load(device); } QJsonModel::QJsonModel(const QByteArray &json, QObject *parent) : QAbstractItemModel(parent), mRootItem{new QJsonTreeItem} { mHeaders.append("key"); mHeaders.append("value"); loadJson(json); } QJsonModel::~QJsonModel() { delete mRootItem; } bool QJsonModel::load(const QString &fileName) { QFile file(fileName); bool success = false; if (file.open(QIODevice::ReadOnly)) { success = load(&file); file.close(); } else success = false; return success; } bool QJsonModel::load(QIODevice *device) { return loadJson(device->readAll()); } bool QJsonModel::loadJson(const QByteArray &json) { auto const &jdoc = QJsonDocument::fromJson(json); if (!jdoc.isNull()) { beginResetModel(); delete mRootItem; if (jdoc.isArray()) { mRootItem = QJsonTreeItem::load(QJsonValue(jdoc.array())); mRootItem->setType(QJsonValue::Array); } else { mRootItem = QJsonTreeItem::load(QJsonValue(jdoc.object())); mRootItem->setType(QJsonValue::Object); } endResetModel(); return true; } qDebug() << Q_FUNC_INFO << "cannot load json"; return false; } QVariant QJsonModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); QJsonTreeItem *item = static_cast(index.internalPointer()); if (role == Qt::DisplayRole) { if (index.column() == 0) return QStringLiteral("%1").arg(item->key()); if (index.column() == 1) return QStringLiteral("%1").arg(item->value()); } else if (Qt::EditRole == role) { if (index.column() == 1) { return QStringLiteral("%1").arg(item->value()); } } return QVariant(); } bool QJsonModel::setData(const QModelIndex &index, const QVariant &value, int role) { int col = index.column(); if (Qt::EditRole == role) { if (col == 1) { QJsonTreeItem *item = static_cast(index.internalPointer()); item->setValue(value.toString()); emit dataChanged(index, index, {Qt::EditRole}); return true; } } return false; } QVariant QJsonModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) return QVariant(); if (orientation == Qt::Horizontal) { return mHeaders.value(section); } else return QVariant(); } QModelIndex QJsonModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); QJsonTreeItem *parentItem; if (!parent.isValid()) parentItem = mRootItem; else parentItem = static_cast(parent.internalPointer()); QJsonTreeItem *childItem = parentItem->child(row); if (childItem) return createIndex(row, column, childItem); else return QModelIndex(); } QModelIndex QJsonModel::parent(const QModelIndex &index) const { if (!index.isValid()) return QModelIndex(); QJsonTreeItem *childItem = static_cast(index.internalPointer()); QJsonTreeItem *parentItem = childItem->parent(); if (parentItem == mRootItem) return QModelIndex(); return createIndex(parentItem->row(), 0, parentItem); } int QJsonModel::rowCount(const QModelIndex &parent) const { QJsonTreeItem *parentItem; if (parent.column() > 0) return 0; if (!parent.isValid()) parentItem = mRootItem; else parentItem = static_cast(parent.internalPointer()); return parentItem->childCount(); } int QJsonModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 2; } Qt::ItemFlags QJsonModel::flags(const QModelIndex &index) const { int col = index.column(); auto item = static_cast(index.internalPointer()); auto isArray = QJsonValue::Array == item->type(); auto isObject = QJsonValue::Object == item->type(); if ((col == 1) && !(isArray || isObject)) { return Qt::ItemIsEditable | QAbstractItemModel::flags(index); } else { return QAbstractItemModel::flags(index); } } QJsonDocument QJsonModel::json() const { auto v = genJson(mRootItem); QJsonDocument doc; if (v.isObject()) { doc = QJsonDocument(v.toObject()); } else { doc = QJsonDocument(v.toArray()); } return doc; } QJsonValue QJsonModel::genJson(QJsonTreeItem *item) const { auto type = item->type(); int nchild = item->childCount(); if (QJsonValue::Object == type) { QJsonObject jo; for (int i = 0; i < nchild; ++i) { auto ch = item->child(i); auto key = ch->key(); jo.insert(key, genJson(ch)); } return jo; } else if (QJsonValue::Array == type) { QJsonArray arr; for (int i = 0; i < nchild; ++i) { auto ch = item->child(i); arr.append(genJson(ch)); } return arr; } else { QJsonValue va(item->value()); return va; } } ================================================ FILE: 3rdparty/qv2ray/v2/ui/widgets/common/QJsonModel.hpp ================================================ /* * The MIT License (MIT) * * Copyright (c) 2011 SCHUTZ Sacha * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #pragma once #include #include #include #include #include #include #include class QJsonModel; class QJsonItem; class QJsonTreeItem { public: QJsonTreeItem(QJsonTreeItem *parent = nullptr); ~QJsonTreeItem(); void appendChild(QJsonTreeItem *item); QJsonTreeItem *child(int row); QJsonTreeItem *parent(); int childCount() const; int row() const; void setKey(const QString &key); void setValue(const QString &value); void setType(const QJsonValue::Type &type); QString key() const; QString value() const; QJsonValue::Type type() const; static QJsonTreeItem *load(const QJsonValue &value, QJsonTreeItem *parent = 0); protected: private: QString mKey; QString mValue; QJsonValue::Type mType; QList mChilds; QJsonTreeItem *mParent; }; //--------------------------------------------------- class QJsonModel : public QAbstractItemModel { Q_OBJECT public: explicit QJsonModel(QObject *parent = nullptr); QJsonModel(const QString &fileName, QObject *parent = nullptr); QJsonModel(QIODevice *device, QObject *parent = nullptr); QJsonModel(const QByteArray &json, QObject *parent = nullptr); ~QJsonModel(); bool load(const QString &fileName); bool load(QIODevice *device); bool loadJson(const QByteArray &json); QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) Q_DECL_OVERRIDE; QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; QModelIndex parent(const QModelIndex &index) const Q_DECL_OVERRIDE; int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE; QJsonDocument json() const; private: QJsonValue genJson(QJsonTreeItem *) const; QJsonTreeItem *mRootItem; QStringList mHeaders; }; ================================================ FILE: 3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.cpp ================================================ #include "w_JsonEditor.hpp" #include "main/NekoGui.hpp" JsonEditor::JsonEditor(const QJsonObject& rootObject, QWidget* parent) : QDialog(parent) { setupUi(this); // QvMessageBusConnect(JsonEditor); // original = rootObject; final = rootObject; QString jsonString = JsonToString(rootObject); if (VerifyJsonString(jsonString).isEmpty()) { jsonTree->setModel(&model); model.loadJson(QJsonDocument(rootObject).toJson()); } else { QvMessageBoxWarn(this, tr("Json Contains Syntax Errors"), tr("Original Json may contain syntax errors. Json tree is disabled.")); } jsonEditor->setText(JsonToString(rootObject)); jsonTree->expandAll(); jsonTree->resizeColumnToContents(0); } // QvMessageBusSlotImpl(JsonEditor) // { // switch (msg) // { // MBShowDefaultImpl; // MBHideDefaultImpl; // MBRetranslateDefaultImpl; // case UPDATE_COLORSCHEME: // break; // } // } QJsonObject JsonEditor::OpenEditor() { int resultCode = this->exec(); auto string = jsonEditor->toPlainText(); while (resultCode == QDialog::Accepted && !VerifyJsonString(string).isEmpty()) { if (string.isEmpty()) { resultCode = QDialog::Accepted; final = {}; break; } QvMessageBoxWarn(this, tr("Json Contains Syntax Errors"), tr("You must correct these errors before continuing.")); resultCode = this->exec(); string = jsonEditor->toPlainText(); } return resultCode == QDialog::Accepted ? final : original; } JsonEditor::~JsonEditor() { } void JsonEditor::on_jsonEditor_textChanged() { auto string = jsonEditor->toPlainText(); auto VerifyResult = VerifyJsonString(string); jsonValidateStatus->setText(VerifyResult); if (VerifyResult.isEmpty()) { BLACK(jsonEditor); final = JsonFromString(string); model.loadJson(QJsonDocument(final).toJson()); jsonTree->expandAll(); jsonTree->resizeColumnToContents(0); } else { RED(jsonEditor); } } void JsonEditor::on_formatJsonBtn_clicked() { auto string = jsonEditor->toPlainText(); auto VerifyResult = VerifyJsonString(string); jsonValidateStatus->setText(VerifyResult); if (VerifyResult.isEmpty()) { BLACK(jsonEditor); jsonEditor->setPlainText(JsonToString(JsonFromString(string))); model.loadJson(QJsonDocument(JsonFromString(string)).toJson()); jsonTree->setModel(&model); jsonTree->expandAll(); jsonTree->resizeColumnToContents(0); } else { RED(jsonEditor); QvMessageBoxWarn(this, tr("Syntax Errors"), tr("Please fix the JSON errors or remove the comments before continue")); } } void JsonEditor::on_removeCommentsBtn_clicked() { jsonEditor->setPlainText(JsonToString(JsonFromString(jsonEditor->toPlainText()))); } ================================================ FILE: 3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp ================================================ #pragma once #include "3rdparty/qv2ray/wrapper.hpp" #include "3rdparty/qv2ray/v2/ui/widgets/common/QJsonModel.hpp" #include "ui_w_JsonEditor.h" #include class JsonEditor : public QDialog, private Ui::JsonEditor { Q_OBJECT public: explicit JsonEditor(const QJsonObject& rootObject, QWidget* parent = nullptr); ~JsonEditor(); QJsonObject OpenEditor(); private slots: void on_jsonEditor_textChanged(); void on_formatJsonBtn_clicked(); void on_removeCommentsBtn_clicked(); private: QJsonModel model; QJsonObject original; QJsonObject final; }; ================================================ FILE: 3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.ui ================================================ JsonEditor Qt::ApplicationModal 0 0 889 572 JSON Editor true Qt::Horizontal false Monospace QTextEdit::NoWrap false Format JSON Remove All Comments Json Editor Structure Preview QAbstractItemView::NoEditTriggers true 15 true true true 132 152 OK Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok jsonEditor formatJsonBtn removeCommentsBtn jsonTree buttonBox accepted() JsonEditor accept() 248 254 157 274 buttonBox rejected() JsonEditor reject() 316 260 286 274 ================================================ FILE: 3rdparty/qv2ray/v3/components/GeositeReader/GeositeReader.cpp ================================================ #include "GeositeReader.hpp" #include "3rdparty/qv2ray/wrapper.hpp" #include "picoproto.hpp" #include #include namespace Qv2ray::components::GeositeReader { QMap GeositeEntries; QStringList ReadGeoSiteFromFile(const QString &filepath, bool allowCache) { if (GeositeEntries.contains(filepath) && allowCache) return GeositeEntries.value(filepath); QStringList list; qInfo() << "Reading geosites from:" << filepath; QFile f(filepath); bool opened = f.open(QFile::OpenModeFlag::ReadOnly); if (!opened) { qInfo() << "File cannot be opened:" << filepath; return list; } const auto content = f.readAll(); f.close(); { picoproto::Message root; root.ParseFromBytes((unsigned char *) content.data(), content.size()); list.reserve(root.GetMessageArray(1).size()); for (const auto &geosite: root.GetMessageArray(1)) list << QString::fromStdString(geosite->GetString(1)); } qInfo() << "Loaded" << list.count() << "geosite entries from data file."; list.sort(); GeositeEntries[filepath] = list; return list; } } // namespace Qv2ray::components::GeositeReader ================================================ FILE: 3rdparty/qv2ray/v3/components/GeositeReader/GeositeReader.hpp ================================================ #pragma once #include namespace Qv2ray::components::GeositeReader { QStringList ReadGeoSiteFromFile(const QString &filepath, bool allowCache = true); } // namespace Qv2ray::components::GeositeReader ================================================ FILE: 3rdparty/qv2ray/v3/components/GeositeReader/picoproto.cpp ================================================ /* Copyright 2016 Pete Warden. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ #include "picoproto.hpp" namespace picoproto { namespace { // To keep the dependencies down, here's a local copy of the widespread bit_cast // operator. This is necessary because in practice weird things can happen if // you just try to use reinterpret_cast. template inline Dest bit_cast(const Source &source) { static_assert(sizeof(Dest) == sizeof(Source), "Sizes do not match"); Dest dest; memcpy(&dest, &source, sizeof(dest)); return dest; } // These are defined in: // https://developers.google.com/protocol-buffers/docs/encoding enum WireType { WIRETYPE_VARINT = 0, WIRETYPE_64BIT = 1, WIRETYPE_LENGTH_DELIMITED = 2, WIRETYPE_GROUP_START = 3, WIRETYPE_GROUP_END = 4, WIRETYPE_32BIT = 5, }; // Pull bytes from the stream, updating the state. bool ConsumeBytes(uint8_t **current, size_t how_many, size_t *remaining) { if (how_many > *remaining) { PP_LOG(ERROR) << "ReadBytes overrun!"; return false; } *current += how_many; *remaining -= how_many; return true; } // Grabs a particular type from the byte stream. template T ReadFromBytes(uint8_t **current, size_t *remaining) { PP_CHECK(ConsumeBytes(current, sizeof(T), remaining)); const T result = *(bit_cast(*current - sizeof(T))); return result; } uint64_t ReadVarInt(uint8_t **current, size_t *remaining) { uint64_t result = 0; bool keep_going; int shift = 0; do { const uint8_t next_number = ReadFromBytes(current, remaining); keep_going = (next_number >= 128); result += (uint64_t) (next_number & 0x7f) << shift; shift += 7; } while (keep_going); return result; } void ReadWireTypeAndFieldNumber(uint8_t **current, size_t *remaining, uint8_t *wire_type, uint32_t *field_number) { uint64_t wire_type_and_field_number = ReadVarInt(current, remaining); *wire_type = wire_type_and_field_number & 0x07; *field_number = wire_type_and_field_number >> 3; } } // namespace std::string FieldTypeDebugString(enum FieldType type) { switch (type) { case FIELD_UNSET: return "UNSET"; break; case FIELD_UINT32: return "UINT32"; break; case FIELD_UINT64: return "UINT64"; break; case FIELD_BYTES: return "BYTES"; break; default: return "Unknown field type"; break; } return "Should never get here"; } Field::Field(FieldType type, bool owns_data) : type(type), owns_data(owns_data) { cached_messages = nullptr; switch (type) { case FIELD_UINT32: { value.v_uint32 = new std::vector(); } break; case FIELD_UINT64: { value.v_uint64 = new std::vector(); } break; case FIELD_BYTES: { value.v_bytes = new std::vector>(); cached_messages = new std::vector(); } break; default: { PP_LOG(ERROR) << "Bad field type when constructing field: " << type; } break; } } Field::Field(const Field &other) : type(other.type), owns_data(other.owns_data) { switch (type) { case FIELD_UINT32: { value.v_uint32 = new std::vector(*other.value.v_uint32); } break; case FIELD_UINT64: { value.v_uint64 = new std::vector(*other.value.v_uint64); } break; case FIELD_BYTES: { if (owns_data) { value.v_bytes = new std::vector>(); for (std::pair data_info: *other.value.v_bytes) { uint8_t *new_data = new uint8_t[data_info.second]; std::copy_n(data_info.first, data_info.second, new_data); value.v_bytes->push_back({new_data, data_info.second}); } } else { value.v_bytes = new std::vector>(*other.value.v_bytes); } cached_messages = new std::vector(); cached_messages->reserve(other.cached_messages->size()); for (Message *other_cached_message: *other.cached_messages) { Message *cached_message; if (other_cached_message) { cached_message = new Message(*other_cached_message); } else { cached_message = nullptr; } cached_messages->push_back(cached_message); } } break; default: { PP_LOG(ERROR) << "Bad field type when constructing field: " << type; } break; } } Field::~Field() { switch (type) { case FIELD_UINT32: delete value.v_uint32; break; case FIELD_UINT64: delete value.v_uint64; break; case FIELD_BYTES: { if (owns_data) for (std::pair data_info: *value.v_bytes) delete[] data_info.first; delete value.v_bytes; for (Message *cached_message: *cached_messages) if (cached_message) delete cached_message; delete cached_messages; break; } default: PP_LOG(ERROR) << "Bad field type when destroying field: " << type; break; } } Message::Message() : Message(true){}; Message::Message(bool copy_arrays) : copy_arrays(copy_arrays){}; Message::Message(const Message &other) : field_map(other.field_map), fields(other.fields), copy_arrays(other.copy_arrays){}; Message::~Message(){}; bool Message::ParseFromBytes(uint8_t *bytes, size_t bytes_size) { uint8_t *current = bytes; size_t remaining = bytes_size; while (remaining > 0) { uint8_t wire_type; uint32_t field_number; ReadWireTypeAndFieldNumber(¤t, &remaining, &wire_type, &field_number); switch (wire_type) { case WIRETYPE_VARINT: { Field *field = AddField(field_number, FIELD_UINT64); const uint64_t varint = ReadVarInt(¤t, &remaining); field->value.v_uint64->push_back(varint); break; } case WIRETYPE_64BIT: { Field *field = AddField(field_number, FIELD_UINT64); const uint64_t value = ReadFromBytes(¤t, &remaining); field->value.v_uint64->push_back(value); break; } case WIRETYPE_LENGTH_DELIMITED: { Field *field = AddField(field_number, FIELD_BYTES); const uint64_t size = ReadVarInt(¤t, &remaining); uint8_t *data; if (copy_arrays) { data = new uint8_t[size]; std::copy_n(current, size, data); field->owns_data = true; } else { data = current; field->owns_data = false; } field->value.v_bytes->push_back({data, size}); field->cached_messages->push_back(nullptr); current += size; remaining -= size; break; } case WIRETYPE_GROUP_START: { PP_LOG(INFO) << field_number << ": GROUPSTART" << std::endl; PP_LOG(ERROR) << "Unhandled wire type encountered"; break; } case WIRETYPE_GROUP_END: { PP_LOG(INFO) << field_number << ": GROUPEND" << std::endl; PP_LOG(ERROR) << "Unhandled wire type encountered"; break; } case WIRETYPE_32BIT: { Field *field = AddField(field_number, FIELD_UINT32); const uint32_t value = ReadFromBytes(¤t, &remaining); field->value.v_uint32->push_back(value); break; } default: { PP_LOG(ERROR) << "Unknown wire type encountered: " << static_cast(wire_type) << " at offset" << (bytes_size - remaining); return false; break; } } } return true; } Field *Message::AddField(int32_t number, enum FieldType type) { Field *field = GetField(number); if (!field) { fields.push_back(Field(type, copy_arrays)); field = &fields.back(); field_map.insert({number, fields.size() - 1}); } return field; } Field *Message::GetField(int32_t number) { if (field_map.count(number) == 0) return nullptr; return &(fields[field_map[number]]); } Field *Message::GetFieldAndCheckType(int32_t number, enum FieldType type) { Field *field = GetField(number); PP_CHECK(field) << "No field for " << number; PP_CHECK(field->type == type) << "For field " << number << " wanted type " << FieldTypeDebugString(type) << " but found " << FieldTypeDebugString(field->type); return field; } int32_t Message::GetInt32(int32_t number) { Field *field = GetFieldAndCheckType(number, FIELD_UINT32); uint32_t first_value = (*(field->value.v_uint32))[0]; int32_t zig_zag_decoded = static_cast((first_value >> 1) ^ (-(first_value & 1))); return zig_zag_decoded; } int64_t Message::GetInt64(int32_t number) { Field *field = GetFieldAndCheckType(number, FIELD_UINT64); uint64_t first_value = (*(field->value.v_uint64))[0]; int64_t zig_zag_decoded = static_cast((first_value >> 1) ^ (-(first_value & 1))); return zig_zag_decoded; } uint32_t Message::GetUInt32(int32_t number) { Field *field = GetFieldAndCheckType(number, FIELD_UINT32); uint32_t first_value = (*(field->value.v_uint32))[0]; return first_value; } uint64_t Message::GetUInt64(int32_t number) { Field *field = GetFieldAndCheckType(number, FIELD_UINT64); uint64_t first_value = (*(field->value.v_uint64))[0]; return first_value; } int64_t Message::GetInt(int32_t number) { Field *field = GetField(number); PP_CHECK(field) << "No field for " << number; PP_CHECK((field->type == FIELD_UINT32) || (field->type == FIELD_UINT64)) << "For field " << number << " wanted integer type but found " << FieldTypeDebugString(field->type); switch (field->type) { case FIELD_UINT32: return GetInt32(number); break; case FIELD_UINT64: return GetInt64(number); break; default: { // Should never get here. } break; } // Should never get here. return 0; } bool Message::GetBool(int32_t number) { return (GetInt(number) != 0); } float Message::GetFloat(int32_t number) { uint32_t int_value = GetUInt32(number); float float_value = *(bit_cast(&int_value)); return float_value; } double Message::GetDouble(int32_t number) { uint64_t int_value = GetUInt64(number); return *(bit_cast(&int_value)); } std::pair Message::GetBytes(int32_t number) { Field *field = GetFieldAndCheckType(number, FIELD_BYTES); std::pair first_value = (*(field->value.v_bytes))[0]; return first_value; } std::string Message::GetString(int32_t number) { Field *field = GetFieldAndCheckType(number, FIELD_BYTES); std::pair first_value = (*(field->value.v_bytes))[0]; std::string result(first_value.first, first_value.first + first_value.second); return result; } Message *Message::GetMessage(int32_t number) { Field *field = GetFieldAndCheckType(number, FIELD_BYTES); Message *cached_message = field->cached_messages->at(0); if (!cached_message) { std::pair first_value = (*(field->value.v_bytes))[0]; cached_message = new Message(copy_arrays); cached_message->ParseFromBytes(first_value.first, first_value.second); field->cached_messages->at(0) = cached_message; } return cached_message; } std::vector Message::GetInt32Array(int32_t number) { std::vector raw_array = GetUInt64Array(number); std::vector result; result.reserve(raw_array.size()); for (uint64_t raw_value: raw_array) { int32_t zig_zag_decoded = static_cast((raw_value >> 1) ^ (-(raw_value & 1))); result.push_back(zig_zag_decoded); } return result; } std::vector Message::GetInt64Array(int32_t number) { std::vector raw_array = GetUInt64Array(number); std::vector result; result.reserve(raw_array.size()); for (uint64_t raw_value: raw_array) { int64_t zig_zag_decoded = static_cast((raw_value >> 1) ^ (-(raw_value & 1))); result.push_back(zig_zag_decoded); } return result; } std::vector Message::GetUInt32Array(int32_t number) { std::vector raw_array = GetUInt64Array(number); std::vector result; result.reserve(raw_array.size()); for (uint64_t raw_value: raw_array) { result.push_back(static_cast(raw_value)); } return result; } std::vector Message::GetUInt64Array(int32_t number) { std::vector result; Field *field = GetField(number); if (!field) { return result; } if (field->type == FIELD_UINT64) { result.reserve(field->value.v_uint64->size()); for (uint64_t value: *field->value.v_uint64) { result.push_back(static_cast(value)); } } else if (field->type == FIELD_UINT32) { result.reserve(field->value.v_uint32->size()); for (uint32_t value: *field->value.v_uint32) { result.push_back(static_cast(value)); } } else if (field->type == FIELD_BYTES) { for (std::pair data_info: *field->value.v_bytes) { uint8_t *current = data_info.first; size_t remaining = data_info.second; while (remaining > 0) { const uint64_t varint = ReadVarInt(¤t, &remaining); result.push_back(static_cast(varint)); } } } else { PP_LOG(ERROR) << "Expected field type UINT32, UINT64, or BYTES but got " << FieldTypeDebugString(field->type); } return result; } std::vector Message::GetBoolArray(int32_t number) { std::vector raw_array = GetUInt64Array(number); std::vector result; result.reserve(raw_array.size()); for (uint64_t raw_value: raw_array) { result.push_back(raw_value != 0); } return result; } std::vector Message::GetFloatArray(int32_t number) { std::vector result; Field *field = GetField(number); if (!field) { return result; } if (field->type == FIELD_UINT32) { result.reserve(field->value.v_uint32->size()); for (uint32_t value: *field->value.v_uint32) { result.push_back(bit_cast(value)); } } else if (field->type == FIELD_BYTES) { for (std::pair data_info: *field->value.v_bytes) { uint8_t *current = data_info.first; size_t remaining = data_info.second; while (remaining > 0) { const uint64_t varint = ReadVarInt(¤t, &remaining); const uint32_t varint32 = static_cast(varint & 0xffffffff); result.push_back(bit_cast(varint32)); } } } else { PP_LOG(ERROR) << "Expected field type UINT32 or BYTES but got " << FieldTypeDebugString(field->type); } return result; } std::vector Message::GetDoubleArray(int32_t number) { std::vector result; Field *field = GetField(number); if (!field) { return result; } if (field->type == FIELD_UINT64) { result.reserve(field->value.v_uint64->size()); for (uint64_t value: *field->value.v_uint64) { result.push_back(bit_cast(value)); } } else if (field->type == FIELD_BYTES) { for (std::pair data_info: *field->value.v_bytes) { uint8_t *current = data_info.first; size_t remaining = data_info.second; while (remaining > 0) { const uint64_t varint = ReadVarInt(¤t, &remaining); result.push_back(bit_cast(varint)); } } } else { PP_LOG(ERROR) << "Expected field type UINT64 or BYTES but got " << FieldTypeDebugString(field->type); } return result; } std::vector> Message::GetByteArray(int32_t number) { std::vector> result; Field *field = GetField(number); if (!field) { return result; } if (field->type == FIELD_BYTES) { result.reserve(field->value.v_bytes->size()); for (std::pair data_info: *field->value.v_bytes) { result.push_back(data_info); } } else { PP_LOG(ERROR) << "Expected field type BYTES but got " << FieldTypeDebugString(field->type); } return result; } std::vector Message::GetStringArray(int32_t number) { std::vector result; Field *field = GetField(number); if (!field) return result; if (field->type == FIELD_BYTES) { result.reserve(field->value.v_bytes->size()); for (std::pair data_info: *field->value.v_bytes) { result.push_back(std::string(data_info.first, data_info.first + data_info.second)); } } else { PP_LOG(ERROR) << "Expected field type BYTES but got " << FieldTypeDebugString(field->type); } return result; } std::vector Message::GetMessageArray(int32_t number) { std::vector result; Field *field = GetField(number); if (!field) return result; if (field->type == FIELD_BYTES) { result.reserve(field->value.v_bytes->size()); for (size_t i = 0; i < field->value.v_bytes->size(); ++i) { Message *cached_message = field->cached_messages->at(i); if (!cached_message) { std::pair value = field->value.v_bytes->at(i); cached_message = new Message(copy_arrays); cached_message->ParseFromBytes(value.first, value.second); field->cached_messages->at(i) = cached_message; } result.push_back(cached_message); } } else { PP_LOG(ERROR) << "Expected field type BYTES but got " << FieldTypeDebugString(field->type); } return result; } } // namespace picoproto ================================================ FILE: 3rdparty/qv2ray/v3/components/GeositeReader/picoproto.hpp ================================================ /* Copyright 2016 Pete Warden. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ==============================================================================*/ /* See the README for full details, but this module lets you read in protobuf encoded files with a minimal code footprint. It doesn't create classes for each kind of message it encounters, it just has a single Message interface that lets you access the members of the protobuf as a key/value store. This loses a lot of the convenience of type-checked classes, but it does mean that very little code is needed to access data from files. As a simple example, if you had read a `bytes_size` long file into `bytes` that contained a TensorFlow GraphDef proto: Message graph_def; graph_def.ParseFromBytes(bytes, bytes_size); You can then access the different fields of the GraphDef using the field numbers assigned in the .proto file: std::vector nodes = graph_def.GetMessageArray(1); One big difference between this minimal approach and normal protobufs is that the calling code has to already know the field number and type of any members it's trying to access. Here I know that the `node` field is number 1, and that it should contain a repeated list of NodeDefs. Since they are not primitive types like numbers or strings, they are accessed as an array of Messages. Here are the design goals of this module: - Keep the code size tiny (single-digit kilobytes on most platforms). - Minimize memory usage (for example allow in-place references to byte data). - Provide a simple, readable implementation that can be ported easily. - Deserialize all saved protobuf files into a usable representation. - No dependencies other than the standard C++ library. - No build-time support (e.g. protoc) required. Here's what it's explicitly not offering: - Providing a readable and transparent way of accessing serialized data. - Saving out data to protobuf format. */ #ifndef INCLUDE_PICOPROTO_H #define INCLUDE_PICOPROTO_H #include #include #include #include #include #include #include #include // To keep dependencies minimal, some bare-bones macros to make logging easier. #define PP_LOG(X) PP_LOG_##X #define PP_LOG_INFO std::cerr << __FILE__ << ":" << __LINE__ << " - INFO: " #define PP_LOG_WARN std::cerr << __FILE__ << ":" << __LINE__ << " - WARN: " #define PP_LOG_ERROR std::cerr << __FILE__ << ":" << __LINE__ << " - ERROR: " #define PP_CHECK(X) \ if (!(X)) \ PP_LOG(ERROR) << "PP_CHECK(" << #X << ") failed. " namespace picoproto { // These roughly correspond to the wire types used to save data in protobuf // files. The best reference to understand the full format is: // https://developers.google.com/protocol-buffers/docs/encoding // Because we don't know the bit-depth of VarInts, they're always stored as // uint64 values, which is why there's no specific type for them. enum FieldType { FIELD_UNSET, FIELD_UINT32, FIELD_UINT64, FIELD_BYTES, }; // Gives a readable name for the field type for logging purposes. std::string FieldTypeDebugString(enum FieldType type); // Forward declare the main message class, since fields can contain them. class Message; // Fields are the building blocks of messages. They contain the values for each // data member, and handle all the allocation and deallocation of storage. // It's unlikely you'll want to access this class directly, since you'll // normally want to use Message below to pull typed values. class Field { public: // You need to specify the type of a Field on creation, so that the right // storage can be set up for the values. You also need to indicate whether the // underlying memory will be around for the lifetime of the message (in which // case no copies are needed) or whether the class should make copies and take // ownership in case the data goes away. Field(FieldType type, bool owns_data); Field(const Field &other); ~Field(); enum FieldType type; // I know, this isn't very OOP, but the simplicity of keeping track of a type // and deciding how to initialize and access the data based on that persuaded // me this was the best approach. The `value` member contains whatever data // the field should be holding. union { std::vector *v_uint32; std::vector *v_uint64; std::vector> *v_bytes; } value; // One of the drawbacks of not requiring .proto files ahead of time is that I // don't know if a length-delimited field contains raw bytes, strings, or // sub-messages. The only time we know that a field should be interpreted as a // message is when client code requests it in that form. Because parsing can // be costly, here we cache the results of any such calls for subsequent // accesses. std::vector *cached_messages; // If this is set, then the object will allocate its own storage for // length-delimited values, and copy from the input stream. If you know the // underlying data will be around for the lifetime of the message, you can // save memory and copies by leaving this as false. bool owns_data; }; // The main interface for loading and accessing serialized protobuf data. class Message { public: // If you're not sure about the lifetime of any binary data you're reading // from, just call this default constructor. Message(); // In the case when you're sure the lifetime of the byte stream you'll be // decoding is longer than the lifetime of the message, you can set // copy_arrays to false. This is especially useful if you have a memory // mapped file to read from containing large binary blobs, since you'll skip // a lot of copying and extra allocation. Message(bool copy_arrays); Message(const Message &other); ~Message(); // Populates fields with all of the data from this stream of bytes. // You can call this repeatedly with new messages, and the results will be // merged together. bool ParseFromBytes(uint8_t *binary, size_t binary_size); // These are the accessor functions if you're expecting exactly one value in a // field. As discussed above, the burden is on the client code to know the // field number and type of each member it's trying to access, and so pick the // correct accessor function. // If the field isn't present, this will raise an error, so if it's optional // you should use the array accessors below. int32_t GetInt32(int32_t number); int64_t GetInt64(int32_t number); uint32_t GetUInt32(int32_t number); uint64_t GetUInt64(int32_t number); int64_t GetInt(int32_t number); bool GetBool(int32_t number); float GetFloat(int32_t number); double GetDouble(int32_t number); std::pair GetBytes(int32_t number); std::string GetString(int32_t number); Message *GetMessage(int32_t number); // If you're not sure if a value will be present, or if it is repeated, you // should call these array functions. If no such field has been seen, then the // result will be an empty vector, otherwise you'll get back one or more // entries. std::vector GetInt32Array(int32_t number); std::vector GetInt64Array(int32_t number); std::vector GetUInt32Array(int32_t number); std::vector GetUInt64Array(int32_t number); std::vector GetBoolArray(int32_t number); std::vector GetFloatArray(int32_t number); std::vector GetDoubleArray(int32_t number); std::vector> GetByteArray(int32_t number); std::vector GetStringArray(int32_t number); std::vector GetMessageArray(int32_t number); // It's unlikely you'll want to access fields directly, but here's an escape // hatch in case you do have to manipulate them more directly. Field *GetField(int32_t number); private: // Inserts a new field, updating all the internal data structures. Field *AddField(int32_t number, enum FieldType type); Field *GetFieldAndCheckType(int32_t number, enum FieldType type); // Maps from a field number to an index in the `fields` vector. std::map field_map; // The core list of fields that have been parsed. std::vector fields; bool copy_arrays; }; } // namespace picoproto #endif // INCLUDE_PICOPROTO_H ================================================ FILE: 3rdparty/qv2ray/wrapper.hpp ================================================ #pragma once // Qv2ray wrapper #include #include #define LOG(...) Qv2ray::base::log_internal(__VA_ARGS__) #define DEBUG(...) Qv2ray::base::log_internal(__VA_ARGS__) namespace Qv2ray { namespace base { template inline void log_internal(T... v) {} } // namespace base } // namespace Qv2ray #define JsonToString(a) QJsonObject2QString(a, false) #define JsonFromString(a) QString2QJsonObject(a) #define QvMessageBoxWarn(a, b, c) MessageBoxWarning(b, c) inline QString VerifyJsonString(const QString &source) { QJsonParseError error{}; QJsonDocument doc = QJsonDocument::fromJson(source.toUtf8(), &error); Q_UNUSED(doc) if (error.error == QJsonParseError::NoError) { return ""; } else { // LOG("WARNING: Json parse returns: " + error.errorString()); return error.errorString(); } } #define RED(obj) \ { \ auto _temp = obj->palette(); \ _temp.setColor(QPalette::Text, Qt::red); \ obj->setPalette(_temp); \ } #define BLACK(obj) obj->setPalette(QWidget::palette()); ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.5) project(nekobox VERSION 0.1 LANGUAGES CXX) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # WINDOWS PDB FILE if (WIN32) if (MSVC) set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Zi") set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG /OPT:REF /OPT:ICF") endif () endif () # Find Qt if (NOT QT_VERSION_MAJOR) set(QT_VERSION_MAJOR 5) endif () find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network Svg LinguistTools) if (NKR_CROSS) set_property(TARGET Qt5::moc PROPERTY IMPORTED_LOCATION /usr/bin/moc) set_property(TARGET Qt5::uic PROPERTY IMPORTED_LOCATION /usr/bin/uic) set_property(TARGET Qt5::rcc PROPERTY IMPORTED_LOCATION /usr/bin/rcc) set_property(TARGET Qt5::lrelease PROPERTY IMPORTED_LOCATION /usr/bin/lrelease) set_property(TARGET Qt5::lupdate PROPERTY IMPORTED_LOCATION /usr/bin/lupdate) endif () #### Platform Variables #### if (WIN32) include("cmake/windows/windows.cmake") else () include("cmake/linux/linux.cmake") endif () #### default prefix path #### if (NOT NKR_LIBS) if (NKR_PACKAGE) list(APPEND NKR_LIBS ${CMAKE_SOURCE_DIR}/libs/deps/package) else () list(APPEND NKR_LIBS ${CMAKE_SOURCE_DIR}/libs/deps/built) endif () endif () if (NOT NKR_DISABLE_LIBS) list(APPEND CMAKE_PREFIX_PATH ${NKR_LIBS}) endif () message("[CMAKE_PREFIX_PATH] ${CMAKE_PREFIX_PATH}") # for some cross toolchain list(APPEND CMAKE_FIND_ROOT_PATH ${CMAKE_PREFIX_PATH}) message("[CMAKE_FIND_ROOT_PATH] ${CMAKE_FIND_ROOT_PATH}") #### NKR #### include("cmake/print.cmake") include("cmake/nkr.cmake") find_package(Threads) #### NKR EXTERNAL #### if (NKR_NO_EXTERNAL) set(NKR_NO_GRPC 1) set(NKR_NO_YAML 1) set(NKR_NO_ZXING 1) set(NKR_NO_QHOTKEY 1) endif () # grpc if (NKR_NO_GRPC) nkr_add_compile_definitions(NKR_NO_GRPC) else () # My proto include("cmake/myproto.cmake") list(APPEND NKR_EXTERNAL_TARGETS myproto) endif () # yaml-cpp if (NKR_NO_YAML) nkr_add_compile_definitions(NKR_NO_YAML) else () find_package(yaml-cpp CONFIG REQUIRED) # only Release is built list(APPEND NKR_EXTERNAL_TARGETS yaml-cpp) endif () # zxing-cpp if (NKR_NO_ZXING) nkr_add_compile_definitions(NKR_NO_ZXING) else () find_package(ZXing CONFIG REQUIRED) list(APPEND NKR_EXTERNAL_TARGETS ZXing::ZXing) endif () # QHotkey (static submodule) if (NKR_NO_QHOTKEY) nkr_add_compile_definitions(NKR_NO_QHOTKEY) else () set(QHOTKEY_INSTALL OFF) set(BUILD_SHARED_LIBS OFF) add_subdirectory(3rdparty/QHotkey) list(APPEND NKR_EXTERNAL_TARGETS qhotkey) endif () #### debug print #### if (DBG_CMAKE) print_all_variables() print_target_properties(myproto) print_target_properties(yaml-cpp) print_target_properties(ZXing::ZXing) set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CMAKE_COMMAND} -E time") set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK "${CMAKE_COMMAND} -E time") endif () # Sources set(PROJECT_SOURCES ${PLATFORM_SOURCES} main/main.cpp main/NekoGui.cpp main/NekoGui_Utils.cpp main/HTTPRequestHelper.cpp 3rdparty/base64.cpp 3rdparty/qrcodegen.cpp 3rdparty/QtExtKeySequenceEdit.cpp 3rdparty/qv2ray/v2/ui/QvAutoCompleteTextEdit.cpp 3rdparty/qv2ray/v2/components/proxy/QvProxyConfigurator.cpp 3rdparty/qv2ray/v2/ui/widgets/common/QJsonModel.cpp 3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.cpp 3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp 3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.ui 3rdparty/qv2ray/v3/components/GeositeReader/GeositeReader.cpp 3rdparty/qv2ray/v3/components/GeositeReader/picoproto.cpp rpc/gRPC.cpp db/Database.cpp db/traffic/TrafficLooper.cpp db/ProfileFilter.cpp db/ConfigBuilder.cpp fmt/AbstractBean.cpp fmt/Bean2CoreObj_box.cpp fmt/Bean2External.cpp fmt/Bean2Link.cpp fmt/Link2Bean.cpp fmt/ChainBean.hpp # translate sub/GroupUpdater.cpp sys/ExternalProcess.cpp sys/AutoRun.cpp ui/ThemeManager.cpp ui/Icon.cpp ui/mainwindow_grpc.cpp ui/mainwindow.cpp ui/mainwindow.h ui/mainwindow.ui ui/edit/dialog_edit_profile.h ui/edit/dialog_edit_profile.cpp ui/edit/dialog_edit_profile.ui ui/edit/dialog_edit_group.h ui/edit/dialog_edit_group.cpp ui/edit/dialog_edit_group.ui ui/edit/edit_chain.h ui/edit/edit_chain.cpp ui/edit/edit_chain.ui ui/edit/edit_socks_http.h ui/edit/edit_socks_http.cpp ui/edit/edit_socks_http.ui ui/edit/edit_shadowsocks.h ui/edit/edit_shadowsocks.cpp ui/edit/edit_shadowsocks.ui ui/edit/edit_vmess.h ui/edit/edit_vmess.cpp ui/edit/edit_vmess.ui ui/edit/edit_trojan_vless.h ui/edit/edit_trojan_vless.cpp ui/edit/edit_trojan_vless.ui ui/edit/edit_naive.h ui/edit/edit_naive.cpp ui/edit/edit_naive.ui ui/edit/edit_quic.h ui/edit/edit_quic.cpp ui/edit/edit_quic.ui ui/edit/edit_custom.h ui/edit/edit_custom.cpp ui/edit/edit_custom.ui ui/dialog_basic_settings.cpp ui/dialog_basic_settings.h ui/dialog_basic_settings.ui ui/dialog_manage_groups.cpp ui/dialog_manage_groups.h ui/dialog_manage_groups.ui ui/dialog_manage_routes.cpp ui/dialog_manage_routes.h ui/dialog_manage_routes.ui ui/dialog_vpn_settings.cpp ui/dialog_vpn_settings.h ui/dialog_vpn_settings.ui ui/dialog_hotkey.cpp ui/dialog_hotkey.h ui/dialog_hotkey.ui ui/widget/ProxyItem.cpp ui/widget/ProxyItem.h ui/widget/ProxyItem.ui ui/widget/GroupItem.cpp ui/widget/GroupItem.h ui/widget/GroupItem.ui res/neko.qrc res/theme/feiyangqingyun/qss.qrc ${QV2RAY_RC} ) # Qt exe if (${QT_VERSION_MAJOR} GREATER_EQUAL 6) qt_add_executable(nekobox MANUAL_FINALIZATION ${PROJECT_SOURCES} ) # Define target properties for Android with Qt 6 as: # set_property(TARGET nekobox APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR # ${CMAKE_CURRENT_SOURCE_DIR}/android) # For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation else () if (ANDROID) add_library(nekobox SHARED ${PROJECT_SOURCES} ) # Define properties for Android with Qt 5 after find_package() calls as: # set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") else () add_executable(nekobox ${PROJECT_SOURCES} ) endif () endif () # Target set_property(TARGET nekobox PROPERTY AUTOUIC ON) set_property(TARGET nekobox PROPERTY AUTOMOC ON) set_property(TARGET nekobox PROPERTY AUTORCC ON) set_target_properties(nekobox PROPERTIES WIN32_EXECUTABLE TRUE ) # Target Source Translations set(TS_FILES translations/zh_CN.ts translations/fa_IR.ts translations/ru_RU.ts ) set(LUPDATE_OPTIONS -locations none -no-obsolete ) if (${QT_VERSION_MAJOR} GREATER_EQUAL 6) qt_add_lupdate(nekobox TS_FILES ${TS_FILES} OPTIONS ${LUPDATE_OPTIONS}) qt_add_lrelease(nekobox TS_FILES ${TS_FILES} QM_FILES_OUTPUT_VARIABLE QM_FILES) else () qt5_create_translation(QM_FILES ${PROJECT_SOURCES} ${TS_FILES} OPTIONS ${LUPDATE_OPTIONS}) endif () configure_file(translations/translations.qrc ${CMAKE_BINARY_DIR} COPYONLY) target_sources(nekobox PRIVATE ${CMAKE_BINARY_DIR}/translations.qrc) # Target Link target_link_libraries(nekobox PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Svg Threads::Threads ${NKR_EXTERNAL_TARGETS} ${PLATFORM_LIBRARIES} ) if (QT_VERSION_MAJOR EQUAL 6) qt_finalize_executable(nekobox) endif () ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README.md ================================================ # NekoBox For PC Qt based cross-platform GUI proxy configuration manager (backend: sing-box) Support Windows / Linux out of the box now. 基于 Qt 的跨平台代理配置管理器 (后端 sing-box) 目前支持 Windows / Linux 开箱即用 ## 下载 / Download ### GitHub Releases (Portable ZIP) 便携格式,无安装器。转到 Releases 下载预编译的二进制文件,解压后即可使用。 [![GitHub All Releases](https://img.shields.io/github/downloads/Matsuridayo/nekoray/total?label=downloads-total&logo=github&style=flat-square)](https://github.com/Matsuridayo/nekoray/releases) [下载 / Download](https://github.com/Matsuridayo/nekoray/releases) [安装包的说明,如果你不知道要下载哪一个](https://github.com/MatsuriDayo/nekoray/wiki/Installation-package-description) ### Package #### AUR - [nekoray](https://aur.archlinux.org/packages/nekoray) - [nekoray-git](https://aur.archlinux.org/packages/nekoray-git) #### archlinuxcn - [nekoray](https://github.com/archlinuxcn/repo/tree/master/archlinuxcn/nekoray) - [nekoray-git](https://github.com/archlinuxcn/repo/tree/master/archlinuxcn/nekoray-git) #### Scoop Extras `scoop install nekoray` ## 更改记录 & 发布频道 / Changelog & Telegram Channel https://t.me/Matsuridayo ## 项目主页 & 文档 / Homepage & Documents https://matsuridayo.github.io ## 代理 / Proxy - SOCKS (4/4a/5) - HTTP(S) - Shadowsocks - VMess - VLESS - Trojan - TUIC ( sing-box ) - NaïveProxy ( Custom Core ) - Hysteria2 ( Custom Core or sing-box ) - Custom Outbound - Custom Config - Custom Core ## 订阅 / Subscription - Raw: some widely used formats (like Shadowsocks, Clash and v2rayN) - 原始格式: 一些广泛使用的格式 (如 Shadowsocks、Clash 和 v2rayN) ## 运行参数 [运行参数](docs/RunFlags.md) ## Windows 运行 若提示 DLL 缺失,无法运行,请下载 安装 [微软 C++ 运行库](https://aka.ms/vs/17/release/vc_redist.x64.exe) ## Linux 运行 [Linux 运行教程](docs/Run_Linux.md) ## 编译教程 / Compile Tutorial 请看 [技术文档 / Technical documentation](https://github.com/MatsuriDayo/nekoray/tree/main/docs) ## 捐助 / Donate 如果这个项目对您有帮助,可以通过捐赠的方式帮助我们维持这个项目。 捐赠满等额 50 USD 可以在「[捐赠榜](https://mtrdnt.pages.dev/donation_list)」显示头像,如果您未被添加到这里,欢迎联系我们补充。 Donations of 50 USD or more can display your avatar on the [Donation List](https://mtrdnt.pages.dev/donation_list). If you are not added here, please contact us to add it. USDT TRC20 `TRhnA7SXE5Sap5gSG3ijxRmdYFiD4KRhPs` XMR `49bwESYQjoRL3xmvTcjZKHEKaiGywjLYVQJMUv79bXonGiyDCs8AzE3KiGW2ytTybBCpWJUvov8SjZZEGg66a4e59GXa6k5` ## Credits Core: - [v2fly/v2ray-core](https://github.com/v2fly/v2ray-core) ( < 3.10 ) - [MatsuriDayo/Matsuri](https://github.com/MatsuriDayo/Matsuri) ( < 3.10 ) - [MatsuriDayo/v2ray-core](https://github.com/MatsuriDayo/v2ray-core) ( < 3.10 ) - [XTLS/Xray-core](https://github.com/XTLS/Xray-core) ( 3.10 <= Version <= 3.26 ) - [MatsuriDayo/Xray-core](https://github.com/MatsuriDayo/Xray-core) ( 3.10 <= Version <= 3.26 ) - [SagerNet/sing-box](https://github.com/SagerNet/sing-box) - [Matsuridayo/sing-box-extra](https://github.com/MatsuriDayo/sing-box-extra) Gui: - [Qv2ray](https://github.com/Qv2ray/Qv2ray) - [Qt](https://www.qt.io/) - [protobuf](https://github.com/protocolbuffers/protobuf) - [yaml-cpp](https://github.com/jbeder/yaml-cpp) - [zxing-cpp](https://github.com/nu-book/zxing-cpp) - [QHotkey](https://github.com/Skycoder42/QHotkey) - [AppImageKit](https://github.com/AppImage/AppImageKit) ================================================ FILE: cmake/linux/linux.cmake ================================================ set(PLATFORM_SOURCES sys/linux/LinuxCap.cpp) set(PLATFORM_LIBRARIES dl) ================================================ FILE: cmake/myproto.cmake ================================================ find_package(Protobuf CONFIG REQUIRED) set(PROTO_FILES go/grpc_server/gen/libcore.proto ) add_library(myproto STATIC ${PROTO_FILES}) target_link_libraries(myproto PUBLIC protobuf::libprotobuf ) target_include_directories(myproto PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) protobuf_generate(TARGET myproto LANGUAGE cpp) ================================================ FILE: cmake/nkr.cmake ================================================ # Release file(STRINGS nekoray_version.txt NKR_VERSION) add_compile_definitions(NKR_VERSION=\"${NKR_VERSION}\") # Debug set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DNKR_CPP_DEBUG") # Func function(nkr_add_compile_definitions arg) message("[add_compile_definitions] ${ARGV}") add_compile_definitions(${ARGV}) endfunction() ================================================ FILE: cmake/print.cmake ================================================ macro(print_all_variables) message(STATUS "print_all_variables------------------------------------------{") get_cmake_property(_variableNames VARIABLES) foreach (_variableName ${_variableNames}) message(STATUS "${_variableName}=${${_variableName}}") endforeach() message(STATUS "print_all_variables------------------------------------------}") endmacro() # Get all propreties that cmake supports if(NOT CMAKE_PROPERTY_LIST) execute_process(COMMAND cmake --help-property-list OUTPUT_VARIABLE CMAKE_PROPERTY_LIST) # Convert command output into a CMake list string(REGEX REPLACE ";" "\\\\;" CMAKE_PROPERTY_LIST "${CMAKE_PROPERTY_LIST}") string(REGEX REPLACE "\n" ";" CMAKE_PROPERTY_LIST "${CMAKE_PROPERTY_LIST}") endif() function(print_properties) message("CMAKE_PROPERTY_LIST = ${CMAKE_PROPERTY_LIST}") endfunction() function(print_target_properties target) if(NOT TARGET ${target}) message(STATUS "There is no target named '${target}'") return() endif() foreach(property ${CMAKE_PROPERTY_LIST}) string(REPLACE "" "${CMAKE_BUILD_TYPE}" property ${property}) # Fix https://stackoverflow.com/questions/32197663/how-can-i-remove-the-the-location-property-may-not-be-read-from-target-error-i if(property STREQUAL "LOCATION" OR property MATCHES "^LOCATION_" OR property MATCHES "_LOCATION$") continue() endif() get_property(was_set TARGET ${target} PROPERTY ${property} SET) if(was_set) get_target_property(value ${target} ${property}) message("${target} ${property} = ${value}") endif() endforeach() endfunction() ================================================ FILE: cmake/windows/VersionInfo.in ================================================ #pragma once #ifndef PRODUCT_VERSION_MAJOR #define PRODUCT_VERSION_MAJOR @PRODUCT_VERSION_MAJOR@ #endif #ifndef PRODUCT_VERSION_MINOR #define PRODUCT_VERSION_MINOR @PRODUCT_VERSION_MINOR@ #endif #ifndef PRODUCT_VERSION_PATCH #define PRODUCT_VERSION_PATCH @PRODUCT_VERSION_PATCH@ #endif #ifndef PRODUCT_VERSION_BUILD #define PRODUCT_VERSION_BUILD @PRODUCT_VERSION_REVISION@ #endif #ifndef FILE_VERSION_MAJOR #define FILE_VERSION_MAJOR @PRODUCT_VERSION_MAJOR@ #endif #ifndef FILE_VERSION_MINOR #define FILE_VERSION_MINOR @PRODUCT_VERSION_MINOR@ #endif #ifndef FILE_VERSION_PATCH #define FILE_VERSION_PATCH @PRODUCT_VERSION_PATCH@ #endif #ifndef FILE_VERSION_BUILD #define FILE_VERSION_BUILD @PRODUCT_VERSION_REVISION@ #endif #ifndef __TO_STRING #define __TO_STRING_IMPL(x) #x #define __TO_STRING(x) __TO_STRING_IMPL(x) #endif #define PRODUCT_VERSION_MAJOR_MINOR_STR __TO_STRING(PRODUCT_VERSION_MAJOR) "." __TO_STRING(PRODUCT_VERSION_MINOR) #define PRODUCT_VERSION_MAJOR_MINOR_PATCH_STR PRODUCT_VERSION_MAJOR_MINOR_STR "." __TO_STRING(PRODUCT_VERSION_PATCH) #define PRODUCT_VERSION_FULL_STR PRODUCT_VERSION_MAJOR_MINOR_PATCH_STR "." __TO_STRING(PRODUCT_VERSION_BUILD) #define PRODUCT_VERSION_RESOURCE PRODUCT_VERSION_MAJOR,PRODUCT_VERSION_MINOR,PRODUCT_VERSION_PATCH,PRODUCT_VERSION_BUILD #define PRODUCT_VERSION_RESOURCE_STR PRODUCT_VERSION_FULL_STR "\0" #define FILE_VERSION_MAJOR_MINOR_STR __TO_STRING(FILE_VERSION_MAJOR) "." __TO_STRING(FILE_VERSION_MINOR) #define FILE_VERSION_MAJOR_MINOR_PATCH_STR FILE_VERSION_MAJOR_MINOR_STR "." __TO_STRING(FILE_VERSION_PATCH) #define FILE_VERSION_FULL_STR FILE_VERSION_MAJOR_MINOR_PATCH_STR "." __TO_STRING(FILE_VERSION_BUILD) #define FILE_VERSION_RESOURCE FILE_VERSION_MAJOR,FILE_VERSION_MINOR,FILE_VERSION_PATCH,FILE_VERSION_BUILD #define FILE_VERSION_RESOURCE_STR FILE_VERSION_FULL_STR "\0" #ifndef PRODUCT_ICON #define PRODUCT_ICON "@PRODUCT_ICON@" #endif #ifndef PRODUCT_COMMENTS #define PRODUCT_COMMENTS "@PRODUCT_COMMENTS@\0" #endif #ifndef PRODUCT_COMPANY_NAME #define PRODUCT_COMPANY_NAME "@PRODUCT_COMPANY_NAME@\0" #endif #ifndef PRODUCT_COMPANY_COPYRIGHT #define PRODUCT_COMPANY_COPYRIGHT "@PRODUCT_COMPANY_COPYRIGHT@\0" #endif #ifndef PRODUCT_FILE_DESCRIPTION #define PRODUCT_FILE_DESCRIPTION "@PRODUCT_FILE_DESCRIPTION@\0" #endif #ifndef PRODUCT_INTERNAL_NAME #define PRODUCT_INTERNAL_NAME "@PRODUCT_NAME@\0" #endif #ifndef PRODUCT_ORIGINAL_FILENAME #define PRODUCT_ORIGINAL_FILENAME "@PRODUCT_ORIGINAL_FILENAME@\0" #endif #ifndef PRODUCT_BUNDLE #define PRODUCT_BUNDLE "@PRODUCT_BUNDLE@\0" #endif ================================================ FILE: cmake/windows/VersionResource.rc ================================================ #include "VersionInfo.h" #if defined(__MINGW64__) || defined(__MINGW32__) // MinGW-w64, MinGW #if defined(__has_include) && __has_include() #include #else #include #include #endif #else // MSVC, Windows SDK #include #endif IDI_ICON1 ICON PRODUCT_ICON LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT VS_VERSION_INFO VERSIONINFO FILEVERSION FILE_VERSION_RESOURCE PRODUCTVERSION PRODUCT_VERSION_RESOURCE FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L #else FILEFLAGS 0x0L #endif FILEOS 0x4L FILETYPE 0x1L FILESUBTYPE 0x0L BEGIN BLOCK "StringFileInfo" BEGIN BLOCK "000904b0" BEGIN VALUE "Comments", PRODUCT_COMMENTS VALUE "CompanyName", PRODUCT_COMPANY_NAME VALUE "FileDescription", PRODUCT_FILE_DESCRIPTION VALUE "FileVersion", FILE_VERSION_RESOURCE_STR VALUE "InternalName", PRODUCT_INTERNAL_NAME VALUE "LegalCopyright", PRODUCT_COMPANY_COPYRIGHT VALUE "OriginalFilename", PRODUCT_ORIGINAL_FILENAME VALUE "ProductName", PRODUCT_BUNDLE VALUE "ProductVersion", PRODUCT_VERSION_RESOURCE_STR END END BLOCK "VarFileInfo" BEGIN VALUE "Translation", 0x9, 1200 END END ================================================ FILE: cmake/windows/generate_product_version.cmake ================================================ include (CMakeParseArguments) set (GenerateProductVersionCurrentDir ${CMAKE_CURRENT_LIST_DIR}) # generate_product_version() function # # This function uses VersionInfo.in template file and VersionResource.rc file # to generate WIN32 resource with version information and general resource strings. # # Usage: # generate_product_version( # SomeOutputResourceVariable # NAME MyGreatProject # ICON ${PATH_TO_APP_ICON} # VERSION_MAJOR 2 # VERSION_MINOR 3 # VERSION_PATCH ${BUILD_COUNTER} # VERSION_REVISION ${BUILD_REVISION} # ) # where BUILD_COUNTER and BUILD_REVISION could be values from your CI server. # # You can use generated resource for your executable targets: # add_executable(target-name ${target-files} ${SomeOutputResourceVariable}) # # You can specify resource strings in arguments: # NAME - name of executable (no defaults, ex: Microsoft Word) # BUNDLE - bundle (${NAME} is default, ex: Microsoft Office) # ICON - path to application icon (${CMAKE_SOURCE_DIR}/product.ico by default) # VERSION_MAJOR - 1 is default # VERSION_MINOR - 0 is default # VERSION_PATCH - 0 is default # VERSION_REVISION - 0 is default # COMPANY_NAME - your company name (no defaults) # COMPANY_COPYRIGHT - ${COMPANY_NAME} (C) Copyright ${CURRENT_YEAR} is default # COMMENTS - ${NAME} v${VERSION_MAJOR}.${VERSION_MINOR} is default # ORIGINAL_FILENAME - ${NAME} is default # INTERNAL_NAME - ${NAME} is default # FILE_DESCRIPTION - ${NAME} is default function(generate_product_version outfiles) set (options) set (oneValueArgs NAME BUNDLE ICON VERSION_MAJOR VERSION_MINOR VERSION_PATCH VERSION_REVISION COMPANY_NAME COMPANY_COPYRIGHT COMMENTS ORIGINAL_FILENAME INTERNAL_NAME FILE_DESCRIPTION) set (multiValueArgs) cmake_parse_arguments(PRODUCT "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) if (NOT PRODUCT_BUNDLE OR "${PRODUCT_BUNDLE}" STREQUAL "") set(PRODUCT_BUNDLE "${PRODUCT_NAME}") endif() if (NOT PRODUCT_ICON OR "${PRODUCT_ICON}" STREQUAL "") set(PRODUCT_ICON "${CMAKE_SOURCE_DIR}/product.ico") endif() if (NOT PRODUCT_VERSION_MAJOR EQUAL 0 AND (NOT PRODUCT_VERSION_MAJOR OR "${PRODUCT_VERSION_MAJOR}" STREQUAL "")) set(PRODUCT_VERSION_MAJOR 1) endif() if (NOT PRODUCT_VERSION_MINOR EQUAL 0 AND (NOT PRODUCT_VERSION_MINOR OR "${PRODUCT_VERSION_MINOR}" STREQUAL "")) set(PRODUCT_VERSION_MINOR 0) endif() if (NOT PRODUCT_VERSION_PATCH EQUAL 0 AND (NOT PRODUCT_VERSION_PATCH OR "${PRODUCT_VERSION_PATCH}" STREQUAL "")) set(PRODUCT_VERSION_PATCH 0) endif() if (NOT PRODUCT_VERSION_REVISION EQUAL 0 AND (NOT PRODUCT_VERSION_REVISION OR "${PRODUCT_VERSION_REVISION}" STREQUAL "")) set(PRODUCT_VERSION_REVISION 0) endif() if (NOT PRODUCT_COMPANY_COPYRIGHT OR "${PRODUCT_COMPANY_COPYRIGHT}" STREQUAL "") string(TIMESTAMP PRODUCT_CURRENT_YEAR "%Y") set(PRODUCT_COMPANY_COPYRIGHT "${PRODUCT_COMPANY_NAME} (C) Copyright ${PRODUCT_CURRENT_YEAR}") endif() if (NOT PRODUCT_COMMENTS OR "${PRODUCT_COMMENTS}" STREQUAL "") set(PRODUCT_COMMENTS "${PRODUCT_NAME} v${PRODUCT_VERSION_MAJOR}.${PRODUCT_VERSION_MINOR}") endif() if (NOT PRODUCT_ORIGINAL_FILENAME OR "${PRODUCT_ORIGINAL_FILENAME}" STREQUAL "") set(PRODUCT_ORIGINAL_FILENAME "${PRODUCT_NAME}") endif() if (NOT PRODUCT_INTERNAL_NAME OR "${PRODUCT_INTERNAL_NAME}" STREQUAL "") set(PRODUCT_INTERNAL_NAME "${PRODUCT_NAME}") endif() if (NOT PRODUCT_FILE_DESCRIPTION OR "${PRODUCT_FILE_DESCRIPTION}" STREQUAL "") set(PRODUCT_FILE_DESCRIPTION "${PRODUCT_NAME}") endif() set (_VersionInfoFile ${CMAKE_CURRENT_BINARY_DIR}/VersionInfo.h) set (_VersionResourceFile ${CMAKE_CURRENT_BINARY_DIR}/VersionResource.rc) configure_file( ${GenerateProductVersionCurrentDir}/VersionInfo.in ${_VersionInfoFile} @ONLY) configure_file( ${GenerateProductVersionCurrentDir}/VersionResource.rc ${_VersionResourceFile} COPYONLY) list(APPEND ${outfiles} ${_VersionInfoFile} ${_VersionResourceFile}) set (${outfiles} ${${outfiles}} PARENT_SCOPE) endfunction() ================================================ FILE: cmake/windows/windows.cmake ================================================ set(PLATFORM_SOURCES 3rdparty/WinCommander.cpp sys/windows/guihelper.cpp sys/windows/MiniDump.cpp) set(PLATFORM_LIBRARIES wininet wsock32 ws2_32 user32 rasapi32 iphlpapi) include(cmake/windows/generate_product_version.cmake) generate_product_version( QV2RAY_RC ICON "${CMAKE_SOURCE_DIR}/res/nekobox.ico" NAME "nekobox" BUNDLE "nekobox" COMPANY_NAME "nekobox" COMPANY_COPYRIGHT "nekobox" FILE_DESCRIPTION "nekobox" ) add_definitions(-DUNICODE -D_UNICODE -DNOMINMAX) set(GUI_TYPE WIN32) if (MINGW) if (NOT DEFINED MinGW_ROOT) set(MinGW_ROOT "C:/msys64/mingw64") endif () else () add_compile_options("/utf-8") add_compile_options("/std:c++17") add_definitions(-D_WIN32_WINNT=0x600 -D_SCL_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS) endif () ================================================ FILE: db/ConfigBuilder.cpp ================================================ #include "db/ConfigBuilder.hpp" #include "db/Database.hpp" #include "fmt/includes.h" #include "fmt/Preset.hpp" #include #include #include #define BOX_UNDERLYING_DNS dataStore->core_box_underlying_dns.isEmpty() ? "local" : dataStore->core_box_underlying_dns namespace NekoGui { QStringList getAutoBypassExternalProcessPaths(const std::shared_ptr &result) { QStringList paths; for (const auto &extR: result->extRs) { auto path = extR->program; if (path.trimmed().isEmpty()) continue; paths << path.replace("\\", "/"); } return paths; } QString genTunName() { auto tun_name = "neko-tun"; #ifdef Q_OS_MACOS tun_name = "utun9"; #endif return tun_name; } void MergeJson(QJsonObject &dst, const QJsonObject &src) { // 合并 if (src.isEmpty()) return; for (const auto &key: src.keys()) { auto v_src = src[key]; if (dst.contains(key)) { auto v_dst = dst[key]; if (v_src.isObject() && v_dst.isObject()) { // isObject 则合并? auto v_src_obj = v_src.toObject(); auto v_dst_obj = v_dst.toObject(); MergeJson(v_dst_obj, v_src_obj); dst[key] = v_dst_obj; } else { dst[key] = v_src; } } else if (v_src.isArray()) { if (key.startsWith("+")) { auto key2 = SubStrAfter(key, "+"); auto v_dst = dst[key2]; auto v_src_arr = v_src.toArray(); auto v_dst_arr = v_dst.toArray(); QJSONARRAY_ADD(v_src_arr, v_dst_arr) dst[key2] = v_src_arr; } else if (key.endsWith("+")) { auto key2 = SubStrBefore(key, "+"); auto v_dst = dst[key2]; auto v_src_arr = v_src.toArray(); auto v_dst_arr = v_dst.toArray(); QJSONARRAY_ADD(v_dst_arr, v_src_arr) dst[key2] = v_dst_arr; } else { dst[key] = v_src; } } else { dst[key] = v_src; } } } // Common std::shared_ptr BuildConfig(const std::shared_ptr &ent, bool forTest, bool forExport) { auto result = std::make_shared(); auto status = std::make_shared(); status->ent = ent; status->result = result; status->forTest = forTest; status->forExport = forExport; auto customBean = dynamic_cast(ent->bean.get()); if (customBean != nullptr && customBean->core == "internal-full") { result->coreConfig = QString2QJsonObject(customBean->config_simple); } else { BuildConfigSingBox(status); } // apply custom config MergeJson(result->coreConfig, QString2QJsonObject(ent->bean->custom_config)); return result; } QString BuildChain(int chainId, const std::shared_ptr &status) { auto group = profileManager->GetGroup(status->ent->gid); if (group == nullptr) { status->result->error = QStringLiteral("This profile is not in any group, your data may be corrupted."); return {}; } auto resolveChain = [=](const std::shared_ptr &ent) { QList> resolved; if (ent->type == "chain") { auto list = ent->ChainBean()->list; std::reverse(std::begin(list), std::end(list)); for (auto id: list) { resolved += profileManager->GetProfile(id); if (resolved.last() == nullptr) { status->result->error = QStringLiteral("chain missing ent: %1").arg(id); break; } if (resolved.last()->type == "chain") { status->result->error = QStringLiteral("chain in chain is not allowed: %1").arg(id); break; } } } else { resolved += ent; }; return resolved; }; // Make list auto ents = resolveChain(status->ent); if (!status->result->error.isEmpty()) return {}; if (group->front_proxy_id >= 0) { auto fEnt = profileManager->GetProfile(group->front_proxy_id); if (fEnt == nullptr) { status->result->error = QStringLiteral("front proxy ent not found."); return {}; } ents += resolveChain(fEnt); if (!status->result->error.isEmpty()) return {}; } // BuildChain QString chainTagOut = BuildChainInternal(0, ents, status); // Chain ent traffic stat if (ents.length() > 1) { status->ent->traffic_data->id = status->ent->id; status->ent->traffic_data->tag = chainTagOut.toStdString(); status->result->outboundStats += status->ent->traffic_data; } return chainTagOut; } #define DOMAIN_USER_RULE \ for (const auto &line: SplitLinesSkipSharp(dataStore->routing->proxy_domain)) { \ if (dataStore->routing->dns_routing) status->domainListDNSRemote += line; \ status->domainListRemote += line; \ } \ for (const auto &line: SplitLinesSkipSharp(dataStore->routing->direct_domain)) { \ if (dataStore->routing->dns_routing) status->domainListDNSDirect += line; \ status->domainListDirect += line; \ } \ for (const auto &line: SplitLinesSkipSharp(dataStore->routing->block_domain)) { \ status->domainListBlock += line; \ } #define IP_USER_RULE \ for (const auto &line: SplitLinesSkipSharp(dataStore->routing->block_ip)) { \ status->ipListBlock += line; \ } \ for (const auto &line: SplitLinesSkipSharp(dataStore->routing->proxy_ip)) { \ status->ipListRemote += line; \ } \ for (const auto &line: SplitLinesSkipSharp(dataStore->routing->direct_ip)) { \ status->ipListDirect += line; \ } QString BuildChainInternal(int chainId, const QList> &ents, const std::shared_ptr &status) { QString chainTag = "c-" + Int2String(chainId); QString chainTagOut; bool muxApplied = false; QString pastTag; int pastExternalStat = 0; int index = 0; for (const auto &ent: ents) { // tagOut: v2ray outbound tag for a profile // profile2 (in) (global) tag g-(id) // profile1 tag (chainTag)-(id) // profile0 (out) tag (chainTag)-(id) / single: chainTag=g-(id) auto tagOut = chainTag + "-" + Int2String(ent->id); // needGlobal: can only contain one? bool needGlobal = false; // first profile set as global auto isFirstProfile = index == ents.length() - 1; if (isFirstProfile) { needGlobal = true; tagOut = "g-" + Int2String(ent->id); } // last profile set as "proxy" if (chainId == 0 && index == 0) { needGlobal = false; tagOut = "proxy"; } // ignoreConnTag if (index != 0) { status->result->ignoreConnTag << tagOut; } if (needGlobal) { if (status->globalProfiles.contains(ent->id)) { continue; } status->globalProfiles += ent->id; } if (index > 0) { // chain rules: past if (pastExternalStat == 0) { auto replaced = status->outbounds.last().toObject(); replaced["detour"] = tagOut; status->outbounds.removeLast(); status->outbounds += replaced; } else { status->routingRules += QJsonObject{ {"inbound", QJsonArray{pastTag + "-mapping"}}, {"outbound", tagOut}, }; } } else { // index == 0 means last profile in chain / not chain chainTagOut = tagOut; status->result->outboundStat = ent->traffic_data; } // chain rules: this auto ext_mapping_port = 0; auto ext_socks_port = 0; auto thisExternalStat = ent->bean->NeedExternal(isFirstProfile); if (thisExternalStat < 0) { status->result->error = "This configuration cannot be set automatically, please try another."; return {}; } // determine port if (thisExternalStat > 0) { if (ent->type == "custom") { auto bean = ent->CustomBean(); if (IsValidPort(bean->mapping_port)) { ext_mapping_port = bean->mapping_port; } else { ext_mapping_port = MkPort(); } if (IsValidPort(bean->socks_port)) { ext_socks_port = bean->socks_port; } else { ext_socks_port = MkPort(); } } else { ext_mapping_port = MkPort(); ext_socks_port = MkPort(); } } if (thisExternalStat == 2) dataStore->need_keep_vpn_off = true; if (thisExternalStat == 1) { // mapping status->inbounds += QJsonObject{ {"type", "direct"}, {"tag", tagOut + "-mapping"}, {"listen", "127.0.0.1"}, {"listen_port", ext_mapping_port}, {"override_address", ent->bean->serverAddress}, {"override_port", ent->bean->serverPort}, }; // no chain rule and not outbound, so need to set to direct if (isFirstProfile) { status->routingRules += QJsonObject{ {"inbound", QJsonArray{tagOut + "-mapping"}}, {"outbound", "direct"}, }; } } // Outbound QJsonObject outbound; auto stream = GetStreamSettings(ent->bean.get()); if (thisExternalStat > 0) { auto extR = ent->bean->BuildExternal(ext_mapping_port, ext_socks_port, thisExternalStat); if (extR.program.isEmpty()) { status->result->error = QObject::tr("Core not found: %1").arg(ent->bean->DisplayCoreType()); return {}; } if (!extR.error.isEmpty()) { // rejected status->result->error = extR.error; return {}; } extR.tag = ent->bean->DisplayType(); status->result->extRs.emplace_back(std::make_shared(extR)); // SOCKS OUTBOUND outbound["type"] = "socks"; outbound["server"] = "127.0.0.1"; outbound["server_port"] = ext_socks_port; } else { const auto coreR = ent->bean->BuildCoreObjSingBox(); if (coreR.outbound.isEmpty()) { status->result->error = "unsupported outbound"; return {}; } if (!coreR.error.isEmpty()) { // rejected status->result->error = coreR.error; return {}; } outbound = coreR.outbound; } // outbound misc outbound["tag"] = tagOut; ent->traffic_data->id = ent->id; ent->traffic_data->tag = tagOut.toStdString(); status->result->outboundStats += ent->traffic_data; // mux common auto needMux = ent->type == "vmess" || ent->type == "trojan" || ent->type == "vless"; needMux &= dataStore->mux_concurrency > 0; if (stream != nullptr) { if (stream->network == "grpc" || stream->network == "quic" || (stream->network == "http" && stream->security == "tls")) { needMux = false; } if (stream->multiplex_status == 0) { if (!dataStore->mux_default_on) needMux = false; } else if (stream->multiplex_status == 1) { needMux = true; } else if (stream->multiplex_status == 2) { needMux = false; } } if (ent->type == "vless" && outbound["flow"] != "") { needMux = false; } // common // apply domain_strategy outbound["domain_strategy"] = dataStore->routing->outbound_domain_strategy; // apply mux if (!muxApplied && needMux) { auto muxObj = QJsonObject{ {"enabled", true}, {"protocol", dataStore->mux_protocol}, {"padding", dataStore->mux_padding}, {"max_streams", dataStore->mux_concurrency}, }; outbound["multiplex"] = muxObj; muxApplied = true; } // apply custom outbound settings MergeJson(outbound, QString2QJsonObject(ent->bean->custom_outbound)); // Bypass Lookup for the first profile auto serverAddress = ent->bean->serverAddress; auto customBean = dynamic_cast(ent->bean.get()); if (customBean != nullptr && customBean->core == "internal") { auto server = QString2QJsonObject(customBean->config_simple)["server"].toString(); if (!server.isEmpty()) serverAddress = server; } if (!IsIpAddress(serverAddress)) { status->domainListDNSDirect += "full:" + serverAddress; } status->outbounds += outbound; pastTag = tagOut; pastExternalStat = thisExternalStat; index++; } return chainTagOut; } // SingBox void BuildConfigSingBox(const std::shared_ptr &status) { // Log status->result->coreConfig["log"] = QJsonObject{{"level", dataStore->log_level}}; // Inbounds // mixed-in if (IsValidPort(dataStore->inbound_socks_port) && !status->forTest) { QJsonObject inboundObj; inboundObj["tag"] = "mixed-in"; inboundObj["type"] = "mixed"; inboundObj["listen"] = dataStore->inbound_address; inboundObj["listen_port"] = dataStore->inbound_socks_port; if (dataStore->routing->sniffing_mode != SniffingMode::DISABLE) { inboundObj["sniff"] = true; inboundObj["sniff_override_destination"] = dataStore->routing->sniffing_mode == SniffingMode::FOR_DESTINATION; } if (dataStore->inbound_auth->NeedAuth()) { inboundObj["users"] = QJsonArray{ QJsonObject{ {"username", dataStore->inbound_auth->username}, {"password", dataStore->inbound_auth->password}, }, }; } inboundObj["domain_strategy"] = dataStore->routing->domain_strategy; status->inbounds += inboundObj; } // tun-in if (dataStore->vpn_internal_tun && dataStore->spmode_vpn && !status->forTest) { QJsonObject inboundObj; inboundObj["tag"] = "tun-in"; inboundObj["type"] = "tun"; inboundObj["interface_name"] = genTunName(); inboundObj["auto_route"] = true; inboundObj["endpoint_independent_nat"] = true; inboundObj["mtu"] = dataStore->vpn_mtu; inboundObj["stack"] = Preset::SingBox::VpnImplementation.value(dataStore->vpn_implementation); inboundObj["strict_route"] = dataStore->vpn_strict_route; inboundObj["inet4_address"] = "172.19.0.1/28"; if (dataStore->vpn_ipv6) inboundObj["inet6_address"] = "fdfe:dcba:9876::1/126"; if (dataStore->routing->sniffing_mode != SniffingMode::DISABLE) { inboundObj["sniff"] = true; inboundObj["sniff_override_destination"] = dataStore->routing->sniffing_mode == SniffingMode::FOR_DESTINATION; } inboundObj["domain_strategy"] = dataStore->routing->domain_strategy; status->inbounds += inboundObj; } // Outbounds auto tagProxy = BuildChain(0, status); if (!status->result->error.isEmpty()) return; // direct & bypass & block status->outbounds += QJsonObject{ {"type", "direct"}, {"tag", "direct"}, }; status->outbounds += QJsonObject{ {"type", "direct"}, {"tag", "bypass"}, }; status->outbounds += QJsonObject{ {"type", "block"}, {"tag", "block"}, }; if (!status->forTest) { status->outbounds += QJsonObject{ {"type", "dns"}, {"tag", "dns-out"}, }; } // custom inbound if (!status->forTest) QJSONARRAY_ADD(status->inbounds, QString2QJsonObject(dataStore->custom_inbound)["inbounds"].toArray()) status->result->coreConfig.insert("inbounds", status->inbounds); status->result->coreConfig.insert("outbounds", status->outbounds); // user rule if (!status->forTest) { DOMAIN_USER_RULE IP_USER_RULE } // sing-box common rule object auto make_rule = [&](const QStringList &list, bool isIP = false) { QJsonObject rule; // QJsonArray ip_cidr; QJsonArray geoip; // QJsonArray domain_keyword; QJsonArray domain_subdomain; QJsonArray domain_regexp; QJsonArray domain_full; QJsonArray geosite; for (auto item: list) { if (isIP) { if (item.startsWith("geoip:")) { geoip += item.replace("geoip:", ""); } else { ip_cidr += item; } } else { // https://www.v2fly.org/config/dns.html#dnsobject if (item.startsWith("geosite:")) { geosite += item.replace("geosite:", ""); } else if (item.startsWith("full:")) { domain_full += item.replace("full:", "").toLower(); } else if (item.startsWith("domain:")) { domain_subdomain += item.replace("domain:", "").toLower(); } else if (item.startsWith("regexp:")) { domain_regexp += item.replace("regexp:", "").toLower(); } else if (item.startsWith("keyword:")) { domain_keyword += item.replace("keyword:", "").toLower(); } else { domain_subdomain += item.toLower(); } } } if (isIP) { if (ip_cidr.isEmpty() && geoip.isEmpty()) return rule; rule["ip_cidr"] = ip_cidr; rule["geoip"] = geoip; } else { if (domain_keyword.isEmpty() && domain_subdomain.isEmpty() && domain_regexp.isEmpty() && domain_full.isEmpty() && geosite.isEmpty()) { return rule; } rule["domain"] = domain_full; rule["domain_suffix"] = domain_subdomain; // v2ray Subdomain => sing-box suffix rule["domain_keyword"] = domain_keyword; rule["domain_regex"] = domain_regexp; rule["geosite"] = geosite; } return rule; }; // final add DNS QJsonObject dns; QJsonArray dnsServers; QJsonArray dnsRules; // Remote if (!status->forTest) dnsServers += QJsonObject{ {"tag", "dns-remote"}, {"address_resolver", "dns-local"}, {"strategy", dataStore->routing->remote_dns_strategy}, {"address", dataStore->routing->remote_dns}, {"detour", tagProxy}, }; // Direct QJsonObject directObj{ {"tag", "dns-direct"}, {"address_resolver", "dns-local"}, {"strategy", dataStore->routing->direct_dns_strategy}, {"address", dataStore->routing->direct_dns}, {"detour", "direct"}, }; if (dataStore->routing->dns_final_out == "bypass") { dnsServers.prepend(directObj); } else { dnsServers.append(directObj); } dnsRules.append(QJsonObject{ {"outbound", "any"}, {"server", "dns-direct"}, }); // block if (!status->forTest) dnsServers += QJsonObject{ {"tag", "dns-block"}, {"address", "rcode://success"}, }; // Fakedns if (dataStore->fake_dns && dataStore->vpn_internal_tun && dataStore->spmode_vpn && !status->forTest) { dnsServers += QJsonObject{ {"tag", "dns-fake"}, {"address", "fakeip"}, }; dns["fakeip"] = QJsonObject{ {"enabled", true}, {"inet4_range", "198.18.0.0/15"}, {"inet6_range", "fc00::/18"}, }; } // Underlying 100% Working DNS ? dnsServers += QJsonObject{ {"tag", "dns-local"}, {"address", BOX_UNDERLYING_DNS}, {"detour", "direct"}, }; // sing-box dns rule object auto add_rule_dns = [&](const QStringList &list, const QString &server) { auto rule = make_rule(list, false); if (rule.isEmpty()) return; rule["server"] = server; dnsRules += rule; }; add_rule_dns(status->domainListDNSRemote, "dns-remote"); add_rule_dns(status->domainListDNSDirect, "dns-direct"); // built-in rules if (!status->forTest) { dnsRules += QJsonObject{ {"query_type", QJsonArray{32, 33}}, {"server", "dns-block"}, }; dnsRules += QJsonObject{ {"domain_suffix", ".lan"}, {"server", "dns-block"}, }; } // fakedns rule if (dataStore->fake_dns && dataStore->vpn_internal_tun && dataStore->spmode_vpn && !status->forTest) { dnsRules += QJsonObject{ {"inbound", "tun-in"}, {"server", "dns-fake"}, }; } dns["servers"] = dnsServers; dns["rules"] = dnsRules; dns["independent_cache"] = true; if (dataStore->routing->use_dns_object) { dns = QString2QJsonObject(dataStore->routing->dns_object); } status->result->coreConfig.insert("dns", dns); // Routing // dns hijack if (!status->forTest) { status->routingRules += QJsonObject{ {"protocol", "dns"}, {"outbound", "dns-out"}, }; } // sing-box routing rule object auto add_rule_route = [&](const QStringList &list, bool isIP, const QString &out) { auto rule = make_rule(list, isIP); if (rule.isEmpty()) return; rule["outbound"] = out; status->routingRules += rule; }; // final add user rule add_rule_route(status->domainListBlock, false, "block"); add_rule_route(status->domainListRemote, false, tagProxy); add_rule_route(status->domainListDirect, false, "bypass"); add_rule_route(status->ipListBlock, true, "block"); add_rule_route(status->ipListRemote, true, tagProxy); add_rule_route(status->ipListDirect, true, "bypass"); // built-in rules status->routingRules += QJsonObject{ {"network", "udp"}, {"port", QJsonArray{135, 137, 138, 139, 5353}}, {"outbound", "block"}, }; status->routingRules += QJsonObject{ {"ip_cidr", QJsonArray{"224.0.0.0/3", "ff00::/8"}}, {"outbound", "block"}, }; status->routingRules += QJsonObject{ {"source_ip_cidr", QJsonArray{"224.0.0.0/3", "ff00::/8"}}, {"outbound", "block"}, }; // tun user rule if (dataStore->vpn_internal_tun && dataStore->spmode_vpn && !status->forTest) { auto match_out = dataStore->vpn_rule_white ? "proxy" : "bypass"; QString process_name_rule = dataStore->vpn_rule_process.trimmed(); if (!process_name_rule.isEmpty()) { auto arr = SplitLinesSkipSharp(process_name_rule); QJsonObject rule{{"outbound", match_out}, {"process_name", QList2QJsonArray(arr)}}; status->routingRules += rule; } QString cidr_rule = dataStore->vpn_rule_cidr.trimmed(); if (!cidr_rule.isEmpty()) { auto arr = SplitLinesSkipSharp(cidr_rule); QJsonObject rule{{"outbound", match_out}, {"ip_cidr", QList2QJsonArray(arr)}}; status->routingRules += rule; } auto autoBypassExternalProcessPaths = getAutoBypassExternalProcessPaths(status->result); if (!autoBypassExternalProcessPaths.isEmpty()) { QJsonObject rule{{"outbound", "bypass"}, {"process_name", QList2QJsonArray(autoBypassExternalProcessPaths)}}; status->routingRules += rule; } } // geopath auto geoip = FindCoreAsset("geoip.db"); auto geosite = FindCoreAsset("geosite.db"); if (geoip.isEmpty()) status->result->error = +"geoip.db not found"; if (geosite.isEmpty()) status->result->error = +"geosite.db not found"; // final add routing rule auto routingRules = QString2QJsonObject(dataStore->routing->custom)["rules"].toArray(); if (status->forTest) routingRules = {}; if (!status->forTest) QJSONARRAY_ADD(routingRules, QString2QJsonObject(dataStore->custom_route_global)["rules"].toArray()) QJSONARRAY_ADD(routingRules, status->routingRules) auto routeObj = QJsonObject{ {"rules", routingRules}, {"auto_detect_interface", dataStore->spmode_vpn}, // TODO force enable? { "geoip", QJsonObject{ {"path", geoip}, }, }, { "geosite", QJsonObject{ {"path", geosite}, }, }}; if (!status->forTest) routeObj["final"] = dataStore->routing->def_outbound; if (status->forExport) { routeObj.remove("geoip"); routeObj.remove("geosite"); routeObj.remove("auto_detect_interface"); } status->result->coreConfig.insert("route", routeObj); // experimental QJsonObject experimentalObj; if (!status->forTest && dataStore->core_box_clash_api > 0) { QJsonObject clash_api = { {"external_controller", "127.0.0.1:" + Int2String(dataStore->core_box_clash_api)}, {"secret", dataStore->core_box_clash_api_secret}, {"external_ui", "dashboard"}, }; experimentalObj["clash_api"] = clash_api; } if (!experimentalObj.isEmpty()) status->result->coreConfig.insert("experimental", experimentalObj); } QString WriteVPNSingBoxConfig() { // tun user rule auto match_out = dataStore->vpn_rule_white ? "neko-socks" : "direct"; auto no_match_out = dataStore->vpn_rule_white ? "direct" : "neko-socks"; QString process_name_rule = dataStore->vpn_rule_process.trimmed(); if (!process_name_rule.isEmpty()) { auto arr = SplitLinesSkipSharp(process_name_rule); QJsonObject rule{{"outbound", match_out}, {"process_name", QList2QJsonArray(arr)}}; process_name_rule = "," + QJsonObject2QString(rule, false); } QString cidr_rule = dataStore->vpn_rule_cidr.trimmed(); if (!cidr_rule.isEmpty()) { auto arr = SplitLinesSkipSharp(cidr_rule); QJsonObject rule{{"outbound", match_out}, {"ip_cidr", QList2QJsonArray(arr)}}; cidr_rule = "," + QJsonObject2QString(rule, false); } // TODO bypass ext core process path? // auth QString socks_user_pass; if (dataStore->inbound_auth->NeedAuth()) { socks_user_pass = R"( "username": "%1", "password": "%2", )"; socks_user_pass = socks_user_pass.arg(dataStore->inbound_auth->username, dataStore->inbound_auth->password); } // gen config auto configFn = ":/neko/vpn/sing-box-vpn.json"; if (QFile::exists("vpn/sing-box-vpn.json")) configFn = "vpn/sing-box-vpn.json"; auto config = ReadFileText(configFn) .replace("//%IPV6_ADDRESS%", dataStore->vpn_ipv6 ? R"("inet6_address": "fdfe:dcba:9876::1/126",)" : "") .replace("//%SOCKS_USER_PASS%", socks_user_pass) .replace("//%PROCESS_NAME_RULE%", process_name_rule) .replace("//%CIDR_RULE%", cidr_rule) .replace("%MTU%", Int2String(dataStore->vpn_mtu)) .replace("%STACK%", Preset::SingBox::VpnImplementation.value(dataStore->vpn_implementation)) .replace("%TUN_NAME%", genTunName()) .replace("%STRICT_ROUTE%", dataStore->vpn_strict_route ? "true" : "false") .replace("%FINAL_OUT%", no_match_out) .replace("%DNS_ADDRESS%", BOX_UNDERLYING_DNS) .replace("%FAKE_DNS_INBOUND%", dataStore->fake_dns ? "tun-in" : "empty") .replace("%PORT%", Int2String(dataStore->inbound_socks_port)); // write config QFile file; file.setFileName(QFileInfo(configFn).fileName()); file.open(QIODevice::ReadWrite | QIODevice::Truncate); file.write(config.toUtf8()); file.close(); return QFileInfo(file).absoluteFilePath(); } QString WriteVPNLinuxScript(const QString &configPath) { #ifdef Q_OS_WIN return {}; #endif // gen script auto scriptFn = ":/neko/vpn/vpn-run-root.sh"; if (QFile::exists("vpn/vpn-run-root.sh")) scriptFn = "vpn/vpn-run-root.sh"; auto script = ReadFileText(scriptFn) .replace("./nekobox_core", QApplication::applicationDirPath() + "/nekobox_core") .replace("$CONFIG_PATH", configPath); // write script QFile file2; file2.setFileName(QFileInfo(scriptFn).fileName()); file2.open(QIODevice::ReadWrite | QIODevice::Truncate); file2.write(script.toUtf8()); file2.close(); return QFileInfo(file2).absoluteFilePath(); } } // namespace NekoGui ================================================ FILE: db/ConfigBuilder.hpp ================================================ #pragma once #include "ProxyEntity.hpp" #include "sys/ExternalProcess.hpp" namespace NekoGui { class BuildConfigResult { public: QString error; QJsonObject coreConfig; QList> outboundStats; // all, but not including "bypass" "block" std::shared_ptr outboundStat; // main QStringList ignoreConnTag; std::list> extRs; }; class BuildConfigStatus { public: std::shared_ptr result; std::shared_ptr ent; bool forTest; bool forExport; // priv QList globalProfiles; // xxList is V2Ray format string list QStringList domainListDNSRemote; QStringList domainListDNSDirect; QStringList domainListRemote; QStringList domainListDirect; QStringList ipListRemote; QStringList ipListDirect; QStringList domainListBlock; QStringList ipListBlock; // config format QJsonArray routingRules; QJsonArray inbounds; QJsonArray outbounds; }; std::shared_ptr BuildConfig(const std::shared_ptr &ent, bool forTest, bool forExport); void BuildConfigSingBox(const std::shared_ptr &status); QString BuildChain(int chainId, const std::shared_ptr &status); QString BuildChainInternal(int chainId, const QList> &ents, const std::shared_ptr &status); QString WriteVPNSingBoxConfig(); QString WriteVPNLinuxScript(const QString &configPath); } // namespace NekoGui ================================================ FILE: db/Database.cpp ================================================ #include "Database.hpp" #include "fmt/includes.h" #include #include #include namespace NekoGui { ProfileManager *profileManager = new ProfileManager(); ProfileManager::ProfileManager() : JsonStore("groups/pm.json") { _add(new configItem("groups", &groupsTabOrder, itemType::integerList)); } QList filterIntJsonFile(const QString &path) { QList result; QDir dr(path); auto entryList = dr.entryList(QDir::Files); for (auto e: entryList) { e = e.toLower(); if (!e.endsWith(".json", Qt::CaseInsensitive)) continue; e = e.remove(".json", Qt::CaseInsensitive); bool ok; auto id = e.toInt(&ok); if (ok) { result << id; } } std::sort(result.begin(), result.end()); return result; } void ProfileManager::LoadManager() { JsonStore::Load(); // profiles = {}; groups = {}; profilesIdOrder = filterIntJsonFile("profiles"); groupsIdOrder = filterIntJsonFile("groups"); // Load Proxys QList delProfile; for (auto id: profilesIdOrder) { auto ent = LoadProxyEntity(QStringLiteral("profiles/%1.json").arg(id)); // Corrupted profile? if (ent == nullptr || ent->bean == nullptr || ent->bean->version == -114514) { delProfile << id; continue; } profiles[id] = ent; } // Clear Corrupted profile for (auto id: delProfile) { DeleteProfile(id); } // Load Groups auto loadedOrder = groupsTabOrder; groupsTabOrder = {}; for (auto id: groupsIdOrder) { auto ent = LoadGroup(QStringLiteral("groups/%1.json").arg(id)); // Corrupted group? if (ent->id != id) { continue; } // Ensure order contains every group if (!loadedOrder.contains(id)) { loadedOrder << id; } groups[id] = ent; } // Ensure groups contains order for (auto id: loadedOrder) { if (groups.count(id)) { groupsTabOrder << id; } } // First setup if (groups.empty()) { auto defaultGroup = NekoGui::ProfileManager::NewGroup(); defaultGroup->name = QObject::tr("Default"); NekoGui::profileManager->AddGroup(defaultGroup); } // if (dataStore->flag_reorder) { { // remove all (contains orphan) for (const auto &profile: profiles) { QFile::remove(profile.second->fn); } } std::map gidOld2New; { int i = 0; int ii = 0; QList newProfilesIdOrder; std::map> newProfiles; for (auto gid: groupsTabOrder) { auto group = GetGroup(gid); gidOld2New[gid] = ii++; for (auto const &profile: group->ProfilesWithOrder()) { auto oldId = profile->id; auto newId = i++; profile->id = newId; profile->gid = gidOld2New[gid]; profile->fn = QStringLiteral("profiles/%1.json").arg(newId); profile->Save(); newProfiles[newId] = profile; newProfilesIdOrder << newId; } group->order = {}; group->Save(); } profiles = newProfiles; profilesIdOrder = newProfilesIdOrder; } { QList newGroupsIdOrder; std::map> newGroups; for (auto oldGid: groupsTabOrder) { auto newId = gidOld2New[oldGid]; auto group = groups[oldGid]; QFile::remove(group->fn); group->id = newId; group->fn = QStringLiteral("groups/%1.json").arg(newId); group->Save(); newGroups[newId] = group; newGroupsIdOrder << newId; } groups = newGroups; groupsIdOrder = newGroupsIdOrder; groupsTabOrder = newGroupsIdOrder; } MessageBoxInfo(software_name, "Profiles and groups reorder complete."); } } void ProfileManager::SaveManager() { JsonStore::Save(); } std::shared_ptr ProfileManager::LoadProxyEntity(const QString &jsonPath) { // Load type ProxyEntity ent0(nullptr, nullptr); ent0.fn = jsonPath; auto validJson = ent0.Load(); auto type = ent0.type; // Load content std::shared_ptr ent; bool validType = validJson; if (validType) { ent = NewProxyEntity(type); validType = ent->bean->version != -114514; } if (validType) { ent->load_control_must = true; ent->fn = jsonPath; ent->Load(); } return ent; } // 新建的不给 fn 和 id std::shared_ptr ProfileManager::NewProxyEntity(const QString &type) { NekoGui_fmt::AbstractBean *bean; if (type == "socks") { bean = new NekoGui_fmt::SocksHttpBean(NekoGui_fmt::SocksHttpBean::type_Socks5); } else if (type == "http") { bean = new NekoGui_fmt::SocksHttpBean(NekoGui_fmt::SocksHttpBean::type_HTTP); } else if (type == "shadowsocks") { bean = new NekoGui_fmt::ShadowSocksBean(); } else if (type == "chain") { bean = new NekoGui_fmt::ChainBean(); } else if (type == "vmess") { bean = new NekoGui_fmt::VMessBean(); } else if (type == "trojan") { bean = new NekoGui_fmt::TrojanVLESSBean(NekoGui_fmt::TrojanVLESSBean::proxy_Trojan); } else if (type == "vless") { bean = new NekoGui_fmt::TrojanVLESSBean(NekoGui_fmt::TrojanVLESSBean::proxy_VLESS); } else if (type == "naive") { bean = new NekoGui_fmt::NaiveBean(); } else if (type == "hysteria2") { bean = new NekoGui_fmt::QUICBean(NekoGui_fmt::QUICBean::proxy_Hysteria2); } else if (type == "tuic") { bean = new NekoGui_fmt::QUICBean(NekoGui_fmt::QUICBean::proxy_TUIC); } else if (type == "custom") { bean = new NekoGui_fmt::CustomBean(); } else { bean = new NekoGui_fmt::AbstractBean(-114514); } auto ent = std::make_shared(bean, type); return ent; } std::shared_ptr ProfileManager::NewGroup() { auto ent = std::make_shared(); return ent; } // ProxyEntity ProxyEntity::ProxyEntity(NekoGui_fmt::AbstractBean *bean, const QString &type_) { if (type_ != nullptr) this->type = type_; _add(new configItem("type", &type, itemType::string)); _add(new configItem("id", &id, itemType::integer)); _add(new configItem("gid", &gid, itemType::integer)); _add(new configItem("yc", &latency, itemType::integer)); _add(new configItem("report", &full_test_report, itemType::string)); // 可以不关联 bean,只加载 ProxyEntity 的信息 if (bean != nullptr) { this->bean = std::shared_ptr(bean); // 有虚函数就要在这里 dynamic_cast _add(new configItem("bean", dynamic_cast(bean), itemType::jsonStore)); _add(new configItem("traffic", dynamic_cast(traffic_data.get()), itemType::jsonStore)); } }; QString ProxyEntity::DisplayLatency() const { if (latency < 0) { return QObject::tr("Unavailable"); } else if (latency > 0) { return UNICODE_LRO + QStringLiteral("%1 ms").arg(latency); } else { return ""; } } QColor ProxyEntity::DisplayLatencyColor() const { if (latency < 0) { return Qt::red; } else if (latency > 0) { auto greenMs = dataStore->test_latency_url.startsWith("https://") ? 200 : 100; if (latency < greenMs) { return Qt::darkGreen; } else { return Qt::darkYellow; } } else { return {}; } } // Profile int ProfileManager::NewProfileID() const { if (profiles.empty()) { return 0; } else { return profilesIdOrder.last() + 1; } } bool ProfileManager::AddProfile(const std::shared_ptr &ent, int gid) { if (ent->id >= 0) { return false; } ent->gid = gid < 0 ? dataStore->current_group : gid; ent->id = NewProfileID(); profiles[ent->id] = ent; profilesIdOrder.push_back(ent->id); ent->fn = QStringLiteral("profiles/%1.json").arg(ent->id); ent->Save(); return true; } void ProfileManager::DeleteProfile(int id) { if (id < 0) return; if (dataStore->started_id == id) return; profiles.erase(id); profilesIdOrder.removeAll(id); QFile(QStringLiteral("profiles/%1.json").arg(id)).remove(); } void ProfileManager::MoveProfile(const std::shared_ptr &ent, int gid) { if (gid == ent->gid || gid < 0) return; auto oldGroup = GetGroup(ent->gid); if (oldGroup != nullptr && !oldGroup->order.isEmpty()) { oldGroup->order.removeAll(ent->id); oldGroup->Save(); } auto newGroup = GetGroup(gid); if (newGroup != nullptr && !newGroup->order.isEmpty()) { newGroup->order.push_back(ent->id); newGroup->Save(); } ent->gid = gid; ent->Save(); } std::shared_ptr ProfileManager::GetProfile(int id) { return profiles.count(id) ? profiles[id] : nullptr; } // Group Group::Group() { _add(new configItem("id", &id, itemType::integer)); _add(new configItem("front_proxy_id", &front_proxy_id, itemType::integer)); _add(new configItem("archive", &archive, itemType::boolean)); _add(new configItem("skip_auto_update", &skip_auto_update, itemType::boolean)); _add(new configItem("name", &name, itemType::string)); _add(new configItem("order", &order, itemType::integerList)); _add(new configItem("url", &url, itemType::string)); _add(new configItem("info", &info, itemType::string)); _add(new configItem("lastup", &sub_last_update, itemType::integer64)); _add(new configItem("manually_column_width", &manually_column_width, itemType::boolean)); _add(new configItem("column_width", &column_width, itemType::integerList)); } std::shared_ptr ProfileManager::LoadGroup(const QString &jsonPath) { auto ent = std::make_shared(); ent->fn = jsonPath; ent->Load(); return ent; } int ProfileManager::NewGroupID() const { if (groups.empty()) { return 0; } else { return groupsIdOrder.last() + 1; } } bool ProfileManager::AddGroup(const std::shared_ptr &ent) { if (ent->id >= 0) { return false; } ent->id = NewGroupID(); groups[ent->id] = ent; groupsIdOrder.push_back(ent->id); groupsTabOrder.push_back(ent->id); ent->fn = QStringLiteral("groups/%1.json").arg(ent->id); ent->Save(); return true; } void ProfileManager::DeleteGroup(int gid) { if (groups.size() <= 1) return; QList toDelete; for (const auto &[id, profile]: profiles) { if (profile->gid == gid) toDelete += id; // map访问中,不能操作 } for (const auto &id: toDelete) { DeleteProfile(id); } groups.erase(gid); groupsIdOrder.removeAll(gid); groupsTabOrder.removeAll(gid); QFile(QStringLiteral("groups/%1.json").arg(gid)).remove(); } std::shared_ptr ProfileManager::GetGroup(int id) { return groups.count(id) ? groups[id] : nullptr; } std::shared_ptr ProfileManager::CurrentGroup() { return GetGroup(dataStore->current_group); } QList> Group::Profiles() const { QList> ret; for (const auto &[_, profile]: profileManager->profiles) { if (id == profile->gid) ret += profile; } return ret; } QList> Group::ProfilesWithOrder() const { if (order.isEmpty()) { return Profiles(); } else { QList> ret; for (auto _id: order) { auto ent = profileManager->GetProfile(_id); if (ent != nullptr) ret += ent; } return ret; } } } // namespace NekoGui ================================================ FILE: db/Database.hpp ================================================ #pragma once #include "main/NekoGui.hpp" #include "ProxyEntity.hpp" #include "Group.hpp" namespace NekoGui { class ProfileManager : private JsonStore { public: // JsonStore // order -> id QList groupsTabOrder; // Manager std::map> profiles; std::map> groups; ProfileManager(); // LoadManager Reset and loads profiles & groups void LoadManager(); void SaveManager(); [[nodiscard]] static std::shared_ptr NewProxyEntity(const QString &type); [[nodiscard]] static std::shared_ptr NewGroup(); bool AddProfile(const std::shared_ptr &ent, int gid = -1); void DeleteProfile(int id); void MoveProfile(const std::shared_ptr &ent, int gid); std::shared_ptr GetProfile(int id); bool AddGroup(const std::shared_ptr &ent); void DeleteGroup(int gid); std::shared_ptr GetGroup(int id); std::shared_ptr CurrentGroup(); private: // sort by id QList profilesIdOrder; QList groupsIdOrder; [[nodiscard]] int NewProfileID() const; [[nodiscard]] int NewGroupID() const; static std::shared_ptr LoadProxyEntity(const QString &jsonPath); static std::shared_ptr LoadGroup(const QString &jsonPath); }; extern ProfileManager *profileManager; } // namespace NekoGui ================================================ FILE: db/Group.hpp ================================================ #pragma once #include "main/NekoGui.hpp" #include "ProxyEntity.hpp" namespace NekoGui { class Group : public JsonStore { public: int id = -1; bool archive = false; bool skip_auto_update = false; QString name = ""; QString url = ""; QString info = ""; qint64 sub_last_update = 0; int front_proxy_id = -1; // list ui bool manually_column_width = false; QList column_width; QList order; Group(); // 按 id 顺序 [[nodiscard]] QList> Profiles() const; // 按 显示 顺序 [[nodiscard]] QList> ProfilesWithOrder() const; }; } // namespace NekoGui ================================================ FILE: db/ProfileFilter.cpp ================================================ #include "ProfileFilter.hpp" namespace NekoGui { QString ProfileFilter_ent_key(const std::shared_ptr &ent, bool by_address) { by_address &= ent->type != "custom"; return by_address ? (ent->bean->DisplayAddress() + ent->bean->DisplayType()) : QJsonObject2QString(ent->bean->ToJson({"c_cfg", "c_out"}), true) + ent->bean->DisplayType(); } void ProfileFilter::Uniq(const QList> &in, QList> &out, bool by_address, bool keep_last) { QMap> hashMap; for (const auto &ent: in) { QString key = ProfileFilter_ent_key(ent, by_address); if (hashMap.contains(key)) { if (keep_last) { out.removeAll(hashMap[key]); hashMap[key] = ent; out += ent; } } else { hashMap[key] = ent; out += ent; } } } void ProfileFilter::Common(const QList> &src, const QList> &dst, QList> &outSrc, QList> &outDst, bool by_address) { QMap> hashMap; for (const auto &ent: src) { QString key = ProfileFilter_ent_key(ent, by_address); hashMap[key] = ent; } for (const auto &ent: dst) { QString key = ProfileFilter_ent_key(ent, by_address); if (hashMap.contains(key)) { outDst += ent; outSrc += hashMap[key]; } } } void ProfileFilter::OnlyInSrc(const QList> &src, const QList> &dst, QList> &out, bool by_address) { QMap hashMap; for (const auto &ent: dst) { QString key = ProfileFilter_ent_key(ent, by_address); hashMap[key] = true; } for (const auto &ent: src) { QString key = ProfileFilter_ent_key(ent, by_address); if (!hashMap.contains(key)) out += ent; } } void ProfileFilter::OnlyInSrc_ByPointer(const QList> &src, const QList> &dst, QList> &out) { for (const auto &ent: src) { if (!dst.contains(ent)) out += ent; } } } // namespace NekoGui ================================================ FILE: db/ProfileFilter.hpp ================================================ #pragma once #include "ProxyEntity.hpp" namespace NekoGui { class ProfileFilter { public: static void Uniq( const QList> &in, QList> &out, bool by_address = false, // def by bean bool keep_last = false // def keep first ); static void Common( const QList> &src, const QList> &dst, QList> &outSrc, QList> &outDst, bool by_address = false // def by bean ); static void OnlyInSrc( const QList> &src, const QList> &dst, QList> &out, bool by_address = false // def by bean ); static void OnlyInSrc_ByPointer( const QList> &src, const QList> &dst, QList> &out); }; } // namespace NekoGui ================================================ FILE: db/ProxyEntity.hpp ================================================ #pragma once #include "main/NekoGui.hpp" #include "db/traffic/TrafficData.hpp" #include "fmt/AbstractBean.hpp" namespace NekoGui_fmt { class SocksHttpBean; class ShadowSocksBean; class VMessBean; class TrojanVLESSBean; class NaiveBean; class QUICBean; class CustomBean; class ChainBean; }; // namespace NekoGui_fmt namespace NekoGui { class ProxyEntity : public JsonStore { public: QString type; int id = -1; int gid = 0; int latency = 0; std::shared_ptr bean; std::shared_ptr traffic_data = std::make_shared(""); QString full_test_report; ProxyEntity(NekoGui_fmt::AbstractBean *bean, const QString &type_); [[nodiscard]] QString DisplayLatency() const; [[nodiscard]] QColor DisplayLatencyColor() const; [[nodiscard]] NekoGui_fmt::ChainBean *ChainBean() const { return (NekoGui_fmt::ChainBean *) bean.get(); }; [[nodiscard]] NekoGui_fmt::SocksHttpBean *SocksHTTPBean() const { return (NekoGui_fmt::SocksHttpBean *) bean.get(); }; [[nodiscard]] NekoGui_fmt::ShadowSocksBean *ShadowSocksBean() const { return (NekoGui_fmt::ShadowSocksBean *) bean.get(); }; [[nodiscard]] NekoGui_fmt::VMessBean *VMessBean() const { return (NekoGui_fmt::VMessBean *) bean.get(); }; [[nodiscard]] NekoGui_fmt::TrojanVLESSBean *TrojanVLESSBean() const { return (NekoGui_fmt::TrojanVLESSBean *) bean.get(); }; [[nodiscard]] NekoGui_fmt::NaiveBean *NaiveBean() const { return (NekoGui_fmt::NaiveBean *) bean.get(); }; [[nodiscard]] NekoGui_fmt::QUICBean *QUICBean() const { return (NekoGui_fmt::QUICBean *) bean.get(); }; [[nodiscard]] NekoGui_fmt::CustomBean *CustomBean() const { return (NekoGui_fmt::CustomBean *) bean.get(); }; }; } // namespace NekoGui ================================================ FILE: db/traffic/TrafficData.hpp ================================================ #pragma once #include "main/NekoGui.hpp" namespace NekoGui_traffic { class TrafficData : public JsonStore { public: int id = -1; // ent id std::string tag; long long downlink = 0; long long uplink = 0; long long downlink_rate = 0; long long uplink_rate = 0; long long last_update; explicit TrafficData(std::string tag) { this->tag = std::move(tag); _add(new configItem("dl", &downlink, itemType::integer64)); _add(new configItem("ul", &uplink, itemType::integer64)); }; void Reset() { downlink = 0; uplink = 0; downlink_rate = 0; uplink_rate = 0; } [[nodiscard]] QString DisplaySpeed() const { return UNICODE_LRO + QStringLiteral("%1↑ %2↓").arg(ReadableSize(uplink_rate), ReadableSize(downlink_rate)); } [[nodiscard]] QString DisplayTraffic() const { if (downlink + uplink == 0) return ""; return UNICODE_LRO + QStringLiteral("%1↑ %2↓").arg(ReadableSize(uplink), ReadableSize(downlink)); } }; } // namespace NekoGui_traffic ================================================ FILE: db/traffic/TrafficLooper.cpp ================================================ #include "TrafficLooper.hpp" #include "rpc/gRPC.h" #include "ui/mainwindow_interface.h" #include #include #include #include #include namespace NekoGui_traffic { TrafficLooper *trafficLooper = new TrafficLooper; QElapsedTimer elapsedTimer; TrafficData *TrafficLooper::update_stats(TrafficData *item) { #ifndef NKR_NO_GRPC // last update auto now = elapsedTimer.elapsed(); auto interval = now - item->last_update; item->last_update = now; if (interval <= 0) return nullptr; // query auto uplink = NekoGui_rpc::defaultClient->QueryStats(item->tag, "uplink"); auto downlink = NekoGui_rpc::defaultClient->QueryStats(item->tag, "downlink"); // add diff item->downlink += downlink; item->uplink += uplink; item->downlink_rate = downlink * 1000 / interval; item->uplink_rate = uplink * 1000 / interval; // return diff auto ret = new TrafficData(item->tag); ret->downlink = downlink; ret->uplink = uplink; ret->downlink_rate = item->downlink_rate; ret->uplink_rate = item->uplink_rate; return ret; #endif return nullptr; } QJsonArray TrafficLooper::get_connection_list() { #ifndef NKR_NO_GRPC auto str = NekoGui_rpc::defaultClient->ListConnections(); QJsonDocument jsonDocument = QJsonDocument::fromJson(str.c_str()); return jsonDocument.array(); #else return QJsonArray{}; #endif } void TrafficLooper::UpdateAll() { std::map updated; // tag to diff for (const auto &item: this->items) { auto data = item.get(); auto diff = updated[data->tag]; // 避免重复查询一个 outbound tag if (diff == nullptr) { diff = update_stats(data); updated[data->tag] = diff; } else { data->uplink += diff->uplink; data->downlink += diff->downlink; data->uplink_rate = diff->uplink_rate; data->downlink_rate = diff->downlink_rate; } } updated[bypass->tag] = update_stats(bypass); // for (const auto &pair: updated) { delete pair.second; } } void TrafficLooper::Loop() { elapsedTimer.start(); while (true) { auto sleep_ms = NekoGui::dataStore->traffic_loop_interval; if (sleep_ms < 500 || sleep_ms > 5000) sleep_ms = 1000; QThread::msleep(sleep_ms); if (NekoGui::dataStore->traffic_loop_interval == 0) continue; // user disabled // profile start and stop if (!loop_enabled) { // 停止 if (looping) { looping = false; runOnUiThread([=] { auto m = GetMainWindow(); m->refresh_status("STOP"); }); } continue; } else { // 开始 if (!looping) { looping = true; } } // do update loop_mutex.lock(); UpdateAll(); // do conn list update QJsonArray conn_list; if (NekoGui::dataStore->connection_statistics) { conn_list = get_connection_list(); } loop_mutex.unlock(); // post to UI runOnUiThread([=] { auto m = GetMainWindow(); if (proxy != nullptr) { m->refresh_status(QObject::tr("Proxy: %1\nDirect: %2").arg(proxy->DisplaySpeed(), bypass->DisplaySpeed())); } for (const auto &item: items) { if (item->id < 0) continue; m->refresh_proxy_list(item->id); } if (NekoGui::dataStore->connection_statistics) { m->refresh_connection_list(conn_list); } }); } } } // namespace NekoGui_traffic ================================================ FILE: db/traffic/TrafficLooper.hpp ================================================ #pragma once #include #include #include #include "TrafficData.hpp" namespace NekoGui_traffic { class TrafficLooper { public: bool loop_enabled = false; bool looping = false; QMutex loop_mutex; QList> items; TrafficData *proxy = nullptr; void UpdateAll(); void Loop(); private: TrafficData *bypass = new TrafficData("bypass"); [[nodiscard]] static TrafficData *update_stats(TrafficData *item); [[nodiscard]] static QJsonArray get_connection_list(); }; extern TrafficLooper *trafficLooper; } // namespace NekoGui_traffic ================================================ FILE: docs/Build_Core.md ================================================ ## 构建 nekobox_core ### 目录结构 ``` | nekoray | go/cmd/* | sing-box-extra | sing-box | ...... ``` ### 常规构建 1. `bash libs/get_source.sh` (自动下载目录结构,自动 checkout commit) 2. `GOOS=windows GOARCH=amd64 bash libs/build_go.sh` 具体支持的 GOOS 和 GOARCH 请看 `libs/build_go.sh` 非官方构建无需编译 `updater` `launcher` ### sing-box tags 具体使用的 tags 请看 `libs/build_go.sh` ================================================ FILE: docs/Build_Linux.md ================================================ 在 Linux 下编译 Nekoray ## git clone 源码 ``` git clone https://github.com/MatsuriDayo/nekoray.git --recursive ``` ## 简单编译法 条件: 1. C++ 依赖:`protobuf yaml-cpp zxing-cpp` 已用包管理器安装,并符合版本要求。 2. 已安装 `qtbase` `qtsvg` `qttools` `qtx11extras` 3. 已安装 Qt `5.12.x` 或 `5.15.x` 4. 系统为 `x86-64-linux-gnu` ```shell mkdir build cd build cmake -GNinja .. ninja ``` 编译完成后得到 `nekobox` 解压 Release 的压缩包,替换其中的 `nekobox`,删除 `launcher` 即可使用。 ## 复杂编译法 ### CMake 参数 | CMake 参数 | 默认值 | 含义 | |-------------------|-------------------|-----------------------| | QT_VERSION_MAJOR | 5 | QT版本 | | NKR_NO_EXTERNAL | | 不包含外部 C/C++ 依赖 (以下所有) | | NKR_NO_YAML | | 不包含 yaml-cpp | | NKR_NO_QHOTKEY | | 不包含 qhotkey | | NKR_NO_ZXING | | 不包含 zxing | | NKR_NO_GRPC | | 不包含 gRPC | | NKR_PACKAGE | | 编译 package 版本 (aur) | | NKR_LIBS | ./libs/deps/built | 依赖搜索目录 | | NKR_DISABLE_LIBS | | 禁用 NKR_LIBS | 1. `NKR_LIBS` 的值会被追加到 `CMAKE_PREFIX_PATH` 2. `NKR_PACKAGE` 打开后,`NKR_LIBS` 的默认值为 `./libs/deps/package` ,具体依赖请看 `build_deps_all.sh` 3. `NKR_PACKAGE` 打开后,应用将使用 appdata 目录存放配置,自动更新等功能将被禁用。 ### C++ 部分 当您的发行版没有上面几个 C++ 依赖包,或者版本不符合要求时,可以参考 `build_deps_all.sh` 编译脚本自行编译。 条件: 已安装 Qt `5.12.x` 或 `5.15.x` #### 编译安装 C/C++ 依赖 (这一步可能要挂梯) ```shell ./libs/build_deps_all.sh ``` #### 编译本体 ```shell mkdir build cd build cmake -GNinja .. ninja ``` 编译完成后得到 `nekobox` ### Go 部分编译 请看 [Build_Core.md](./Build_Core.md) ================================================ FILE: docs/Build_Windows.md ================================================ 在 Windows 下编译 Nekoray ### git clone 源码 ``` git clone https://github.com/MatsuriDayo/nekoray.git --recursive ``` ### 安装 Visual Studio 从微软官网安装,可以使用 2019 和 2022 版本,安装 Win32 C++ 开发环境。 安装好后可以在「开始」菜单找到 `x64 Native Tools Command Prompt` 本文之后的命令均在该 cmd 内执行。`cmake` `ninja` 等工具使用 VS 自带的即可。 ### 下载 Qt SDK 目前 Windows Release 使用的版本是 Qt 6.5.x 下载解压后,将 bin 目录添加到环境变量。 #### Release 编译用到的 Qt 包下载 (MSVC2019 x86_64) https://github.com/MatsuriDayo/nekoray_qt_runtime/releases/download/20220503/Qt6.5.0-Windows-x86_64-VS2022-17.5.5-20230507.7z #### 官方签名版 Qt 5.15.2 (可选,已知有内存泄漏的BUG) 在此下载 `qtbase` `qtsvg` `qttools` 的包并解压到同一个目录。 https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt5_5152/qt.qt5.5152.win64_msvc2019_64/ ### C++ 部分编译 #### 编译安装 C/C++ 依赖 (这一步可能要挂梯) ```shell bash ./libs/build_deps_all.sh ``` 目前只有 bash 脚本,没有批处理或 powershell,如果 Windows 没有带 bash 建议自行安装。 CMake 参数等细节与 Linux 大同小异,有问题可以参照 Build_Linux 文档。 #### 编译本体 请根据你的 QT Sdk 的位置替换命令 ```shell mkdir build cd build cmake -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=D:/path/to/qt/5.15.2/msvc2019_64 .. ninja ``` 编译完成后得到 `nekobox.exe` 最后运行 `windeployqt nekobox.exe` 自动复制所需 DLL 等文件到当前目录 ### Go 部分编译 请看 [Build_Core.md](./Build_Core.md) ================================================ FILE: docs/RunFlags.md ================================================ # 运行参数 - `-many` 无视同目录正在运行的实例,强行开启新的实例。 - `-appdata` 开启后配置文件会指定目录,未指定目录则使用共享目录,无法多开和自动升级。 - `-flag_reorder` 进行重新整理配置文件的顺序,并删除损坏和孤立的配置。 ================================================ FILE: docs/Run_Linux.md ================================================ ## Linux 安装 ### Debian 系发行版 使用 Debian 系发行版时,推荐使用 .deb 包安装: ```shell sudo apt install ./nekoray-*-debian-x64.deb ``` 安装完成后,桌面快捷方式启动自带参数 `-appdata`,如果想要直接启动并使用之前的配置,注意附带本参数。 ### Arch 系发行版 使用 Arch 系发行版时,推荐从 ```aur``` 或 ```archlinuxcn``` 安装: #### AUR ##### 最新稳定版 ```shell [yay/paru] -S nekoray ``` ##### 最新 Git 版 (开发版) ```shell [yay/paru] -S nekoray-git ``` #### archlinuxcn ##### 最新稳定版 ```shell sudo pacman -S nekoray ``` ##### 最新 Git 版 (开发版) ```shell sudo pacman -S nekoray-git ``` ### 其他发行版 下载 .zip 文件,解压到合适的路径,开箱即用。 或下载 .AppImage,并使用 `chmod +x nekoray-*-AppImage-x64.AppImage` 给予可执行权限。 具体使用方法见下文。 ## Linux 运行 **使用 Linux 系统相信您已具备基本的排错能力, 本项目不提供特定发行版/架构的支持,预编译文件不能满足您的需求时,请自行编译/适配。** 已知部分 Linux 发行版无法使用、非 x86_64 暂无适配,可以尝试自行编译。 目前 Release 便携包解压后,有两种使用方法: 1. System: 若要使用系统的 Qt5 运行库,请执行 `./nekoray` 2. Bundle: 若要使用预编译的 Qt 运行库,请执行 `./launcher` ### Bundle 要求:已安装主流的发行版和 xcb 桌面环境。 运行: `./launcher` 或 部分系统可双击打开 launcher 参数 * `./launcher -- -appdata` ( `--` 后的参数传递给主程序 ) * `-debug` Debug mode Ubuntu 22.04: `sudo apt install libxcb-xinerama0` ### System 要求:已安装主流的发行版和 xcb 桌面环境,已安装 Qt5.12 ~ Qt5.15 环境。 运行: `./nekoray` 或 部分系统可双击打开。如果无法运行,建议使用 Bundle 版。 ================================================ FILE: docs/readme.md ================================================ # 技术文档 # Technical documentation 1. Build GUI: `Build_*.md` 2. Build Core: `Build_Core.md` ================================================ FILE: fmt/AbstractBean.cpp ================================================ #include "includes.h" #include #include #include namespace NekoGui_fmt { AbstractBean::AbstractBean(int version) { this->version = version; _add(new configItem("_v", &this->version, itemType::integer)); _add(new configItem("name", &name, itemType::string)); _add(new configItem("addr", &serverAddress, itemType::string)); _add(new configItem("port", &serverPort, itemType::integer)); _add(new configItem("c_cfg", &custom_config, itemType::string)); _add(new configItem("c_out", &custom_outbound, itemType::string)); } QString AbstractBean::ToNekorayShareLink(const QString &type) { auto b = ToJson(); QUrl url; url.setScheme("nekoray"); url.setHost(type); url.setFragment(QJsonObject2QString(b, true) .toUtf8() .toBase64(QByteArray::Base64UrlEncoding)); return url.toString(); } QString AbstractBean::DisplayAddress() { return ::DisplayAddress(serverAddress, serverPort); } QString AbstractBean::DisplayName() { if (name.isEmpty()) { return DisplayAddress(); } return name; } QString AbstractBean::DisplayTypeAndName() { return QStringLiteral("[%1] %2").arg(DisplayType(), DisplayName()); } void AbstractBean::ResolveDomainToIP(const std::function &onFinished) { bool noResolve = false; if (dynamic_cast(this) != nullptr) noResolve = true; if (dynamic_cast(this) != nullptr) noResolve = true; if (dynamic_cast(this) != nullptr) noResolve = true; if (IsIpAddress(serverAddress)) noResolve = true; if (noResolve) { onFinished(); return; } #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) // TODO older QT QHostInfo::lookupHost(serverAddress, QApplication::instance(), [=](const QHostInfo &host) { auto addr = host.addresses(); if (!addr.isEmpty()) { auto domain = serverAddress; auto stream = GetStreamSettings(this); // replace serverAddress serverAddress = addr.first().toString(); // replace ws tls if (stream != nullptr) { if (stream->security == "tls" && stream->sni.isEmpty()) { stream->sni = domain; } if (stream->network == "ws" && stream->host.isEmpty()) { stream->host = domain; } } } onFinished(); }); #endif } } // namespace NekoGui_fmt ================================================ FILE: fmt/AbstractBean.hpp ================================================ #pragma once #include #include #include "main/NekoGui.hpp" namespace NekoGui_fmt { struct CoreObjOutboundBuildResult { public: QJsonObject outbound; QString error; }; struct ExternalBuildResult { public: QString program; QStringList env; QStringList arguments; // QString tag; // QString error; QString config_export; }; class AbstractBean : public JsonStore { public: int version; QString name = ""; QString serverAddress = "127.0.0.1"; int serverPort = 1080; QString custom_config = ""; QString custom_outbound = ""; explicit AbstractBean(int version); // QString ToNekorayShareLink(const QString &type); void ResolveDomainToIP(const std::function &onFinished); // [[nodiscard]] virtual QString DisplayAddress(); [[nodiscard]] virtual QString DisplayName(); virtual QString DisplayCoreType() { return software_core_name; }; virtual QString DisplayType() { return {}; }; virtual QString DisplayTypeAndName(); // virtual int NeedExternal(bool isFirstProfile) { return 0; }; virtual CoreObjOutboundBuildResult BuildCoreObjSingBox() { return {}; }; virtual ExternalBuildResult BuildExternal(int mapping_port, int socks_port, int external_stat) { return {}; }; virtual QString ToShareLink() { return {}; }; }; } // namespace NekoGui_fmt ================================================ FILE: fmt/Bean2CoreObj_box.cpp ================================================ #include "db/ProxyEntity.hpp" #include "fmt/includes.h" namespace NekoGui_fmt { void V2rayStreamSettings::BuildStreamSettingsSingBox(QJsonObject *outbound) { // https://sing-box.sagernet.org/configuration/shared/v2ray-transport if (network != "tcp") { QJsonObject transport{{"type", network}}; if (network == "ws") { if (!host.isEmpty()) transport["headers"] = QJsonObject{{"Host", host}}; // ws path & ed auto pathWithoutEd = SubStrBefore(path, "?ed="); if (!pathWithoutEd.isEmpty()) transport["path"] = pathWithoutEd; if (pathWithoutEd != path) { auto ed = SubStrAfter(path, "?ed=").toInt(); if (ed > 0) { transport["max_early_data"] = ed; transport["early_data_header_name"] = "Sec-WebSocket-Protocol"; } } if (ws_early_data_length > 0) { transport["max_early_data"] = ws_early_data_length; transport["early_data_header_name"] = ws_early_data_name; } } else if (network == "http") { if (!path.isEmpty()) transport["path"] = path; if (!host.isEmpty()) transport["host"] = QList2QJsonArray(host.split(",")); } else if (network == "grpc") { if (!path.isEmpty()) transport["service_name"] = path; } else if (network == "httpupgrade") { if (!path.isEmpty()) transport["path"] = path; if (!host.isEmpty()) transport["host"] = host; } outbound->insert("transport", transport); } else if (header_type == "http") { // TCP + headerType QJsonObject transport{ {"type", "http"}, {"method", "GET"}, {"path", path}, {"headers", QJsonObject{{"Host", QList2QJsonArray(host.split(","))}}}, }; outbound->insert("transport", transport); } // 对应字段 tls if (security == "tls") { QJsonObject tls{{"enabled", true}}; if (allow_insecure || NekoGui::dataStore->skip_cert) tls["insecure"] = true; if (!sni.trimmed().isEmpty()) tls["server_name"] = sni; if (!certificate.trimmed().isEmpty()) { tls["certificate"] = certificate.trimmed(); } if (!alpn.trimmed().isEmpty()) { tls["alpn"] = QList2QJsonArray(alpn.split(",")); } QString fp = utlsFingerprint; if (!reality_pbk.trimmed().isEmpty()) { tls["reality"] = QJsonObject{ {"enabled", true}, {"public_key", reality_pbk}, {"short_id", reality_sid.split(",")[0]}, }; if (fp.isEmpty()) fp = "random"; } if (!fp.isEmpty()) { tls["utls"] = QJsonObject{ {"enabled", true}, {"fingerprint", fp}, }; } outbound->insert("tls", tls); } if (outbound->value("type").toString() == "vmess" || outbound->value("type").toString() == "vless") { outbound->insert("packet_encoding", packet_encoding); } } CoreObjOutboundBuildResult SocksHttpBean::BuildCoreObjSingBox() { CoreObjOutboundBuildResult result; QJsonObject outbound; outbound["type"] = socks_http_type == type_HTTP ? "http" : "socks"; if (socks_http_type == type_Socks4) outbound["version"] = "4"; outbound["server"] = serverAddress; outbound["server_port"] = serverPort; if (!username.isEmpty() && !password.isEmpty()) { outbound["username"] = username; outbound["password"] = password; } stream->BuildStreamSettingsSingBox(&outbound); result.outbound = outbound; return result; } CoreObjOutboundBuildResult ShadowSocksBean::BuildCoreObjSingBox() { CoreObjOutboundBuildResult result; QJsonObject outbound{{"type", "shadowsocks"}}; outbound["server"] = serverAddress; outbound["server_port"] = serverPort; outbound["method"] = method; outbound["password"] = password; if (uot != 0) { QJsonObject udp_over_tcp{ {"enabled", true}, {"version", uot}, }; outbound["udp_over_tcp"] = udp_over_tcp; } else { outbound["udp_over_tcp"] = false; } if (!plugin.trimmed().isEmpty()) { outbound["plugin"] = SubStrBefore(plugin, ";"); outbound["plugin_opts"] = SubStrAfter(plugin, ";"); } stream->BuildStreamSettingsSingBox(&outbound); result.outbound = outbound; return result; } CoreObjOutboundBuildResult VMessBean::BuildCoreObjSingBox() { CoreObjOutboundBuildResult result; QJsonObject outbound{ {"type", "vmess"}, {"server", serverAddress}, {"server_port", serverPort}, {"uuid", uuid.trimmed()}, {"alter_id", aid}, {"security", security}, }; stream->BuildStreamSettingsSingBox(&outbound); result.outbound = outbound; return result; } CoreObjOutboundBuildResult TrojanVLESSBean::BuildCoreObjSingBox() { CoreObjOutboundBuildResult result; QJsonObject outbound{ {"type", proxy_type == proxy_VLESS ? "vless" : "trojan"}, {"server", serverAddress}, {"server_port", serverPort}, }; QJsonObject settings; if (proxy_type == proxy_VLESS) { if (flow.right(7) == "-udp443") { // 检查末尾是否包含"-udp443",如果是,则删去 flow.chop(7); } else if (flow == "none") { // 不使用 flow flow = ""; } outbound["uuid"] = password.trimmed(); outbound["flow"] = flow; } else { outbound["password"] = password; } stream->BuildStreamSettingsSingBox(&outbound); result.outbound = outbound; return result; } CoreObjOutboundBuildResult QUICBean::BuildCoreObjSingBox() { CoreObjOutboundBuildResult result; QJsonObject coreTlsObj{ {"enabled", true}, {"disable_sni", disableSni}, {"insecure", allowInsecure}, {"certificate", caText.trimmed()}, {"server_name", sni}, }; if (!alpn.trimmed().isEmpty()) coreTlsObj["alpn"] = QList2QJsonArray(alpn.split(",")); if (proxy_type == proxy_Hysteria2) coreTlsObj["alpn"] = "h3"; QJsonObject outbound{ {"server", serverAddress}, {"server_port", serverPort}, {"tls", coreTlsObj}, }; if (proxy_type == proxy_Hysteria2) { outbound["type"] = "hysteria2"; outbound["password"] = password; outbound["up_mbps"] = uploadMbps; outbound["down_mbps"] = downloadMbps; if (!hopPort.trimmed().isEmpty()) { outbound["hop_ports"] = hopPort; outbound["hop_interval"] = hopInterval; } if (!obfsPassword.isEmpty()) { outbound["obfs"] = QJsonObject{ {"type", "salamander"}, {"password", obfsPassword}, }; } } else if (proxy_type == proxy_TUIC) { outbound["type"] = "tuic"; outbound["uuid"] = uuid; outbound["password"] = password; outbound["congestion_control"] = congestionControl; if (uos) { outbound["udp_over_stream"] = true; } else { outbound["udp_relay_mode"] = udpRelayMode; } outbound["zero_rtt_handshake"] = zeroRttHandshake; if (!heartbeat.trimmed().isEmpty()) outbound["heartbeat"] = heartbeat; } result.outbound = outbound; return result; } CoreObjOutboundBuildResult CustomBean::BuildCoreObjSingBox() { CoreObjOutboundBuildResult result; if (core == "internal") { result.outbound = QString2QJsonObject(config_simple); } return result; } } // namespace NekoGui_fmt ================================================ FILE: fmt/Bean2External.cpp ================================================ #include "db/ProxyEntity.hpp" #include "fmt/includes.h" #include #include #include #include #define WriteTempFile(fn, data) \ QDir dir; \ if (!dir.exists("temp")) dir.mkdir("temp"); \ QFile f(QStringLiteral("temp/") + fn); \ bool ok = f.open(QIODevice::WriteOnly | QIODevice::Truncate); \ if (ok) { \ f.write(data); \ } else { \ result.error = f.errorString(); \ } \ f.close(); \ auto TempFile = QFileInfo(f).absoluteFilePath(); namespace NekoGui_fmt { // -1: Cannot use this config // 0: Internal // 1: Mapping External // 2: Direct External int NaiveBean::NeedExternal(bool isFirstProfile) { if (isFirstProfile) { if (NekoGui::dataStore->spmode_vpn) { return 1; } return 2; } return 1; } int QUICBean::NeedExternal(bool isFirstProfile) { auto extCore = [=] { if (isFirstProfile) { if (NekoGui::dataStore->spmode_vpn && hopPort.trimmed().isEmpty()) { return 1; } return 2; } else { if (!hopPort.trimmed().isEmpty()) { return -1; } } return 1; }; if (!forceExternal) { // sing-box support return 0; } else { // external core support return extCore(); } } int CustomBean::NeedExternal(bool isFirstProfile) { if (core == "internal" || core == "internal-full") return 0; return 1; } ExternalBuildResult NaiveBean::BuildExternal(int mapping_port, int socks_port, int external_stat) { ExternalBuildResult result{NekoGui::dataStore->extraCore->Get("naive")}; auto is_direct = external_stat == 2; auto domain_address = sni.isEmpty() ? serverAddress : sni; auto connect_address = is_direct ? serverAddress : "127.0.0.1"; auto connect_port = is_direct ? serverPort : mapping_port; domain_address = WrapIPV6Host(domain_address); connect_address = WrapIPV6Host(connect_address); auto proxy_url = QUrl(); proxy_url.setScheme(protocol); proxy_url.setUserName(username); proxy_url.setPassword(password); proxy_url.setPort(connect_port); proxy_url.setHost(domain_address); if (!disable_log) result.arguments += "--log"; result.arguments += "--listen=socks://127.0.0.1:" + Int2String(socks_port); result.arguments += "--proxy=" + proxy_url.toString(QUrl::FullyEncoded); if (domain_address != connect_address) result.arguments += "--host-resolver-rules=MAP " + domain_address + " " + connect_address; if (insecure_concurrency > 0) result.arguments += "--insecure-concurrency=" + Int2String(insecure_concurrency); if (!extra_headers.trimmed().isEmpty()) result.arguments += "--extra-headers=" + extra_headers; if (!certificate.trimmed().isEmpty()) { WriteTempFile("naive_" + GetRandomString(10) + ".crt", certificate.toUtf8()); result.env += "SSL_CERT_FILE=" + TempFile; } auto config_export = QStringList{result.program}; config_export += result.arguments; result.config_export = QStringList2Command(config_export); return result; } ExternalBuildResult QUICBean::BuildExternal(int mapping_port, int socks_port, int external_stat) { if (proxy_type == proxy_TUIC) { ExternalBuildResult result{NekoGui::dataStore->extraCore->Get("tuic")}; QJsonObject relay; relay["uuid"] = uuid; relay["password"] = password; relay["udp_relay_mode"] = udpRelayMode; relay["congestion_control"] = congestionControl; relay["zero_rtt_handshake"] = zeroRttHandshake; relay["disable_sni"] = disableSni; if (!heartbeat.trimmed().isEmpty()) relay["heartbeat"] = heartbeat; if (!alpn.trimmed().isEmpty()) relay["alpn"] = QList2QJsonArray(alpn.split(",")); if (!caText.trimmed().isEmpty()) { WriteTempFile("tuic_" + GetRandomString(10) + ".crt", caText.toUtf8()); QJsonArray certificate; certificate.append(TempFile); relay["certificates"] = certificate; } // The most confused part of TUIC...... if (serverAddress == sni) { relay["server"] = serverAddress + ":" + Int2String(serverPort); } else { relay["server"] = sni + ":" + Int2String(serverPort); relay["ip"] = serverAddress; } QJsonObject local{ {"server", "127.0.0.1:" + Int2String(socks_port)}, }; QJsonObject config{ {"relay", relay}, {"local", local}, }; // result.config_export = QJsonObject2QString(config, false); WriteTempFile("tuic_" + GetRandomString(10) + ".json", result.config_export.toUtf8()); result.arguments = QStringList{"-c", TempFile}; return result; } else if (proxy_type == proxy_Hysteria2) { ExternalBuildResult result{NekoGui::dataStore->extraCore->Get("hysteria2")}; QJsonObject config; auto server = serverAddress; if (!hopPort.trimmed().isEmpty()) { server = WrapIPV6Host(server) + ":" + hopPort; } else { server = WrapIPV6Host(server) + ":" + Int2String(serverPort); } QJsonObject transport; transport["type"] = "udp"; transport["udp"] = QJsonObject{ {"hopInterval", QString::number(hopInterval) + "s"}, }; config["transport"] = transport; config["server"] = server; config["socks5"] = QJsonObject{ {"listen", "127.0.0.1:" + Int2String(socks_port)}, {"disableUDP", false}, }; config["auth"] = password; QJsonObject bandwidth; if (uploadMbps > 0) bandwidth["up"] = Int2String(uploadMbps) + " mbps"; if (downloadMbps > 0) bandwidth["down"] = Int2String(downloadMbps) + " mbps"; config["bandwidth"] = bandwidth; QJsonObject quic; if (streamReceiveWindow > 0) quic["initStreamReceiveWindow"] = streamReceiveWindow; if (connectionReceiveWindow > 0) quic["initConnReceiveWindow"] = connectionReceiveWindow; if (disableMtuDiscovery) quic["disablePathMTUDiscovery"] = true; config["quic"] = quic; config["fastOpen"] = true; config["lazy"] = true; if (!obfsPassword.isEmpty()) { QJsonObject obfs; obfs["type"] = "salamander"; obfs["salamander"] = QJsonObject{ {"password", obfsPassword}, }; config["obfs"] = obfs; } QJsonObject tls; auto sniGen = sni; if (sni.isEmpty() && !IsIpAddress(serverAddress)) sniGen = serverAddress; tls["sni"] = sniGen; if (allowInsecure) tls["insecure"] = true; if (!caText.trimmed().isEmpty()) { WriteTempFile("hysteria2_" + GetRandomString(10) + ".crt", caText.toUtf8()); QJsonArray certificate; certificate.append(TempFile); tls["certificates"] = certificate; } config["tls"] = tls; result.config_export = QJsonObject2QString(config, false); WriteTempFile("hysteria2_" + GetRandomString(10) + ".json", result.config_export.toUtf8()); result.arguments = QStringList{"-c", TempFile}; return result; } ExternalBuildResult e; e.error = "unknown type"; return e; } ExternalBuildResult CustomBean::BuildExternal(int mapping_port, int socks_port, int external_stat) { ExternalBuildResult result{NekoGui::dataStore->extraCore->Get(core)}; result.arguments = command; // TODO split? for (int i = 0; i < result.arguments.length(); i++) { auto arg = result.arguments[i]; arg = arg.replace("%mapping_port%", Int2String(mapping_port)); arg = arg.replace("%socks_port%", Int2String(socks_port)); arg = arg.replace("%server_addr%", serverAddress); arg = arg.replace("%server_port%", Int2String(serverPort)); result.arguments[i] = arg; } if (!config_simple.trimmed().isEmpty()) { auto config = config_simple; config = config.replace("%mapping_port%", Int2String(mapping_port)); config = config.replace("%socks_port%", Int2String(socks_port)); config = config.replace("%server_addr%", serverAddress); config = config.replace("%server_port%", Int2String(serverPort)); // suffix QString suffix; if (!config_suffix.isEmpty()) { suffix = "." + config_suffix; } else if (!QString2QJsonObject(config).isEmpty()) { // trojan-go: unsupported config format: xxx.tmp. use .yaml or .json instead. suffix = ".json"; } // write config WriteTempFile("custom_" + GetRandomString(10) + suffix, config.toUtf8()); for (int i = 0; i < result.arguments.count(); i++) { result.arguments[i] = result.arguments[i].replace("%config%", TempFile); } result.config_export = config; } return result; } } // namespace NekoGui_fmt ================================================ FILE: fmt/Bean2Link.cpp ================================================ #include "db/ProxyEntity.hpp" #include "fmt/includes.h" #include namespace NekoGui_fmt { QString SocksHttpBean::ToShareLink() { QUrl url; if (socks_http_type == type_HTTP) { // http if (stream->security == "tls") { url.setScheme("https"); } else { url.setScheme("http"); } } else { url.setScheme(QStringLiteral("socks%1").arg(socks_http_type)); } if (!name.isEmpty()) url.setFragment(name); if (!username.isEmpty()) url.setUserName(username); if (!password.isEmpty()) url.setPassword(password); url.setHost(serverAddress); url.setPort(serverPort); return url.toString(QUrl::FullyEncoded); } QString TrojanVLESSBean::ToShareLink() { QUrl url; QUrlQuery query; url.setScheme(proxy_type == proxy_VLESS ? "vless" : "trojan"); url.setUserName(password); url.setHost(serverAddress); url.setPort(serverPort); if (!name.isEmpty()) url.setFragment(name); // security auto security = stream->security; if (security == "tls" && !stream->reality_pbk.trimmed().isEmpty()) security = "reality"; query.addQueryItem("security", security); if (!stream->sni.isEmpty()) query.addQueryItem("sni", stream->sni); if (!stream->alpn.isEmpty()) query.addQueryItem("alpn", stream->alpn); if (stream->allow_insecure) query.addQueryItem("allowInsecure", "1"); if (!stream->utlsFingerprint.isEmpty()) query.addQueryItem("fp", stream->utlsFingerprint); if (security == "reality") { query.addQueryItem("pbk", stream->reality_pbk); if (!stream->reality_sid.isEmpty()) query.addQueryItem("sid", stream->reality_sid); if (!stream->reality_spx.isEmpty()) query.addQueryItem("spx", stream->reality_spx); } // type query.addQueryItem("type", stream->network); if (stream->network == "ws" || stream->network == "http" || stream->network == "httpupgrade") { if (!stream->path.isEmpty()) query.addQueryItem("path", stream->path); if (!stream->host.isEmpty()) query.addQueryItem("host", stream->host); } else if (stream->network == "grpc") { if (!stream->path.isEmpty()) query.addQueryItem("serviceName", stream->path); } else if (stream->network == "tcp") { if (stream->header_type == "http") { if (!stream->path.isEmpty()) query.addQueryItem("path", stream->path); query.addQueryItem("headerType", "http"); query.addQueryItem("host", stream->host); } } // protocol if (proxy_type == proxy_VLESS) { if (!flow.isEmpty()) { query.addQueryItem("flow", flow); } query.addQueryItem("encryption", "none"); } url.setQuery(query); return url.toString(QUrl::FullyEncoded); } const char* fixShadowsocksUserNameEncodeMagic = "fixShadowsocksUserNameEncodeMagic-holder-for-QUrl"; QString ShadowSocksBean::ToShareLink() { QUrl url; url.setScheme("ss"); if (method.startsWith("2022-")) { url.setUserName(fixShadowsocksUserNameEncodeMagic); } else { auto method_password = method + ":" + password; url.setUserName(method_password.toUtf8().toBase64(QByteArray::Base64Option::Base64UrlEncoding)); } url.setHost(serverAddress); url.setPort(serverPort); if (!name.isEmpty()) url.setFragment(name); QUrlQuery q; if (!plugin.isEmpty()) q.addQueryItem("plugin", plugin); if (!q.isEmpty()) url.setQuery(q); // auto link = url.toString(QUrl::FullyEncoded); link = link.replace(fixShadowsocksUserNameEncodeMagic, method + ":" + QUrl::toPercentEncoding(password)); return link; } QString VMessBean::ToShareLink() { if (NekoGui::dataStore->old_share_link_format) { // v2rayN format QJsonObject N{ {"v", "2"}, {"ps", name}, {"add", serverAddress}, {"port", Int2String(serverPort)}, {"id", uuid}, {"aid", Int2String(aid)}, {"net", stream->network}, {"host", stream->host}, {"path", stream->path}, {"type", stream->header_type}, {"scy", security}, {"tls", stream->security == "tls" ? "tls" : ""}, {"sni", stream->sni}, }; return "vmess://" + QJsonObject2QString(N, true).toUtf8().toBase64(); } else { // ducksoft format QUrl url; QUrlQuery query; url.setScheme("vmess"); url.setUserName(uuid); url.setHost(serverAddress); url.setPort(serverPort); if (!name.isEmpty()) url.setFragment(name); query.addQueryItem("encryption", security); // security auto security = stream->security; if (security == "tls" && !stream->reality_pbk.trimmed().isEmpty()) security = "reality"; query.addQueryItem("security", security); if (!stream->sni.isEmpty()) query.addQueryItem("sni", stream->sni); if (stream->allow_insecure) query.addQueryItem("allowInsecure", "1"); if (stream->utlsFingerprint.isEmpty()) { query.addQueryItem("fp", NekoGui::dataStore->utlsFingerprint); } else { query.addQueryItem("fp", stream->utlsFingerprint); } if (security == "reality") { query.addQueryItem("pbk", stream->reality_pbk); if (!stream->reality_sid.isEmpty()) query.addQueryItem("sid", stream->reality_sid); if (!stream->reality_spx.isEmpty()) query.addQueryItem("spx", stream->reality_spx); } // type query.addQueryItem("type", stream->network); if (stream->network == "ws" || stream->network == "http" || stream->network == "httpupgrade") { if (!stream->path.isEmpty()) query.addQueryItem("path", stream->path); if (!stream->host.isEmpty()) query.addQueryItem("host", stream->host); } else if (stream->network == "grpc") { if (!stream->path.isEmpty()) query.addQueryItem("serviceName", stream->path); } else if (stream->network == "tcp") { if (stream->header_type == "http") { query.addQueryItem("headerType", "http"); query.addQueryItem("host", stream->host); } } url.setQuery(query); return url.toString(QUrl::FullyEncoded); } } QString NaiveBean::ToShareLink() { QUrl url; url.setScheme("naive+" + protocol); url.setUserName(username); url.setPassword(password); url.setHost(serverAddress); url.setPort(serverPort); if (!name.isEmpty()) url.setFragment(name); return url.toString(QUrl::FullyEncoded); } QString QUICBean::ToShareLink() { QUrl url; if (proxy_type == proxy_TUIC) { url.setScheme("tuic"); url.setUserName(uuid); url.setPassword(password); url.setHost(serverAddress); url.setPort(serverPort); QUrlQuery q; if (!congestionControl.isEmpty()) q.addQueryItem("congestion_control", congestionControl); if (!alpn.isEmpty()) q.addQueryItem("alpn", alpn); if (!sni.isEmpty()) q.addQueryItem("sni", sni); if (!udpRelayMode.isEmpty()) q.addQueryItem("udp_relay_mode", udpRelayMode); if (allowInsecure) q.addQueryItem("allow_insecure", "1"); if (disableSni) q.addQueryItem("disable_sni", "1"); if (!q.isEmpty()) url.setQuery(q); if (!name.isEmpty()) url.setFragment(name); } else if (proxy_type == proxy_Hysteria2) { url.setScheme("hy2"); url.setHost(serverAddress); url.setPort(serverPort); if (password.contains(":")) { url.setUserName(SubStrBefore(password, ":")); url.setPassword(SubStrAfter(password, ":")); } else { url.setUserName(password); } QUrlQuery q; if (!obfsPassword.isEmpty()) { q.addQueryItem("obfs", "salamander"); q.addQueryItem("obfs-password", obfsPassword); } if (!hopPort.trimmed().isEmpty()) q.addQueryItem("mport", hopPort); if (allowInsecure) q.addQueryItem("insecure", "1"); if (!sni.isEmpty()) q.addQueryItem("sni", sni); if (!q.isEmpty()) url.setQuery(q); if (!name.isEmpty()) url.setFragment(name); } return url.toString(QUrl::FullyEncoded); } } // namespace NekoGui_fmt ================================================ FILE: fmt/ChainBean.hpp ================================================ #pragma once #include "main/NekoGui.hpp" namespace NekoGui_fmt { class ChainBean : public AbstractBean { public: QList list; // in to out ChainBean() : AbstractBean(0) { _add(new configItem("list", &list, itemType::integerList)); }; QString DisplayType() override { return QObject::tr("Chain Proxy"); }; QString DisplayAddress() override { return ""; }; }; } // namespace NekoGui_fmt ================================================ FILE: fmt/CustomBean.hpp ================================================ #pragma once #include "fmt/AbstractBean.hpp" namespace NekoGui_fmt { class CustomBean : public AbstractBean { public: QString core; QList command; QString config_suffix; QString config_simple; int mapping_port = 0; int socks_port = 0; CustomBean() : AbstractBean(0) { _add(new configItem("core", &core, itemType::string)); _add(new configItem("cmd", &command, itemType::stringList)); _add(new configItem("cs", &config_simple, itemType::string)); _add(new configItem("cs_suffix", &config_suffix, itemType::string)); _add(new configItem("mapping_port", &mapping_port, itemType::integer)); _add(new configItem("socks_port", &socks_port, itemType::integer)); }; QString DisplayType() override { if (core == "internal") { auto obj = QString2QJsonObject(config_simple); return obj["type"].toString(); } else if (core == "internal-full") { return software_core_name + " config"; } return core; }; QString DisplayCoreType() override { return NeedExternal(true) == 0 ? software_core_name : core; }; QString DisplayAddress() override { if (core == "internal") { auto obj = QString2QJsonObject(config_simple); return ::DisplayAddress(obj["server"].toString(), obj["server_port"].toInt()); } else if (core == "internal-full") { return {}; } return AbstractBean::DisplayAddress(); }; int NeedExternal(bool isFirstProfile) override; ExternalBuildResult BuildExternal(int mapping_port, int socks_port, int external_stat) override; CoreObjOutboundBuildResult BuildCoreObjSingBox() override; }; } // namespace NekoGui_fmt ================================================ FILE: fmt/Link2Bean.cpp ================================================ #include "db/ProxyEntity.hpp" #include "fmt/includes.h" #include namespace NekoGui_fmt { #define DECODE_V2RAY_N_1 \ QString linkN = DecodeB64IfValid(SubStrBefore(SubStrAfter(link, "://"), "#"), QByteArray::Base64Option::Base64UrlEncoding); \ if (linkN.isEmpty()) return false; \ auto hasRemarks = link.contains("#"); \ if (hasRemarks) linkN += "#" + SubStrAfter(link, "#"); \ auto url = QUrl("https://" + linkN); bool SocksHttpBean::TryParseLink(const QString &link) { auto url = QUrl(link); if (!url.isValid()) return false; auto query = GetQuery(url); if (link.startsWith("socks4")) socks_http_type = type_Socks4; if (link.startsWith("http")) socks_http_type = type_HTTP; name = url.fragment(QUrl::FullyDecoded); serverAddress = url.host(); serverPort = url.port(); username = url.userName(); password = url.password(); if (serverPort == -1) serverPort = socks_http_type == type_HTTP ? 443 : 1080; // v2rayN fmt if (password.isEmpty() && !username.isEmpty()) { QString n = DecodeB64IfValid(username); if (!n.isEmpty()) { username = SubStrBefore(n, ":"); password = SubStrAfter(n, ":"); } } stream->security = GetQueryValue(query, "security", ""); stream->sni = GetQueryValue(query, "sni"); if (link.startsWith("https")) stream->security = "tls"; return !serverAddress.isEmpty(); } bool TrojanVLESSBean::TryParseLink(const QString &link) { auto url = QUrl(link); if (!url.isValid()) return false; auto query = GetQuery(url); name = url.fragment(QUrl::FullyDecoded); serverAddress = url.host(); serverPort = url.port(); password = url.userName(); if (serverPort == -1) serverPort = 443; // security auto type = GetQueryValue(query, "type", "tcp"); if (type == "h2") { type = "http"; } stream->network = type; if (proxy_type == proxy_Trojan) { stream->security = GetQueryValue(query, "security", "tls").replace("reality", "tls").replace("none", ""); } else { stream->security = GetQueryValue(query, "security", "").replace("reality", "tls").replace("none", ""); } auto sni1 = GetQueryValue(query, "sni"); auto sni2 = GetQueryValue(query, "peer"); if (!sni1.isEmpty()) stream->sni = sni1; if (!sni2.isEmpty()) stream->sni = sni2; stream->alpn = GetQueryValue(query, "alpn"); if (!query.queryItemValue("allowInsecure").isEmpty()) stream->allow_insecure = true; stream->reality_pbk = GetQueryValue(query, "pbk", ""); stream->reality_sid = GetQueryValue(query, "sid", ""); stream->reality_spx = GetQueryValue(query, "spx", ""); stream->utlsFingerprint = GetQueryValue(query, "fp", ""); if (stream->utlsFingerprint.isEmpty()) { stream->utlsFingerprint = NekoGui::dataStore->utlsFingerprint; } // type if (stream->network == "ws") { stream->path = GetQueryValue(query, "path", ""); stream->host = GetQueryValue(query, "host", ""); } else if (stream->network == "http") { stream->path = GetQueryValue(query, "path", ""); stream->host = GetQueryValue(query, "host", "").replace("|", ","); } else if (stream->network == "httpupgrade") { stream->path = GetQueryValue(query, "path", ""); stream->host = GetQueryValue(query, "host", ""); } else if (stream->network == "grpc") { stream->path = GetQueryValue(query, "serviceName", ""); } else if (stream->network == "tcp") { if (GetQueryValue(query, "headerType") == "http") { stream->header_type = "http"; stream->host = GetQueryValue(query, "host", ""); stream->path = GetQueryValue(query, "path", ""); } } // protocol if (proxy_type == proxy_VLESS) { flow = GetQueryValue(query, "flow", ""); } return !(password.isEmpty() || serverAddress.isEmpty()); } bool ShadowSocksBean::TryParseLink(const QString &link) { if (SubStrBefore(link, "#").contains("@")) { // SS auto url = QUrl(link); if (!url.isValid()) return false; name = url.fragment(QUrl::FullyDecoded); serverAddress = url.host(); serverPort = url.port(); if (url.password().isEmpty()) { // traditional format auto method_password = DecodeB64IfValid(url.userName(), QByteArray::Base64Option::Base64UrlEncoding); if (method_password.isEmpty()) return false; method = SubStrBefore(method_password, ":"); password = SubStrAfter(method_password, ":"); } else { // 2022 format method = url.userName(); password = url.password(); } auto query = GetQuery(url); plugin = query.queryItemValue("plugin").replace("simple-obfs;", "obfs-local;"); } else { // v2rayN DECODE_V2RAY_N_1 if (hasRemarks) name = url.fragment(QUrl::FullyDecoded); serverAddress = url.host(); serverPort = url.port(); method = url.userName(); password = url.password(); } return !(serverAddress.isEmpty() || method.isEmpty() || password.isEmpty()); } bool VMessBean::TryParseLink(const QString &link) { // V2RayN Format auto linkN = DecodeB64IfValid(SubStrAfter(link, "vmess://")); if (!linkN.isEmpty()) { auto objN = QString2QJsonObject(linkN); if (objN.isEmpty()) return false; // REQUIRED uuid = objN["id"].toString(); serverAddress = objN["add"].toString(); serverPort = objN["port"].toVariant().toInt(); // OPTIONAL name = objN["ps"].toString(); aid = objN["aid"].toVariant().toInt(); stream->host = objN["host"].toString(); stream->path = objN["path"].toString(); stream->sni = objN["sni"].toString(); stream->header_type = objN["type"].toString(); auto net = objN["net"].toString(); if (!net.isEmpty()) { if (net == "h2") { net = "http"; } stream->network = net; } auto scy = objN["scy"].toString(); if (!scy.isEmpty()) security = scy; // TLS (XTLS?) stream->security = objN["tls"].toString(); // TODO quic & kcp return true; } else { // https://github.com/XTLS/Xray-core/discussions/716 auto url = QUrl(link); if (!url.isValid()) return false; auto query = GetQuery(url); name = url.fragment(QUrl::FullyDecoded); serverAddress = url.host(); serverPort = url.port(); uuid = url.userName(); if (serverPort == -1) serverPort = 443; aid = 0; // “此分享标准仅针对 VMess AEAD 和 VLESS。” security = GetQueryValue(query, "encryption", "auto"); // security auto type = GetQueryValue(query, "type", "tcp"); if (type == "h2") { type = "http"; } stream->network = type; stream->security = GetQueryValue(query, "security", "tls").replace("reality", "tls"); auto sni1 = GetQueryValue(query, "sni"); auto sni2 = GetQueryValue(query, "peer"); if (!sni1.isEmpty()) stream->sni = sni1; if (!sni2.isEmpty()) stream->sni = sni2; if (!query.queryItemValue("allowInsecure").isEmpty()) stream->allow_insecure = true; stream->reality_pbk = GetQueryValue(query, "pbk", ""); stream->reality_sid = GetQueryValue(query, "sid", ""); stream->reality_spx = GetQueryValue(query, "spx", ""); stream->utlsFingerprint = GetQueryValue(query, "fp", ""); if (stream->utlsFingerprint.isEmpty()) { stream->utlsFingerprint = NekoGui::dataStore->utlsFingerprint; } // type if (stream->network == "ws") { stream->path = GetQueryValue(query, "path", ""); stream->host = GetQueryValue(query, "host", ""); } else if (stream->network == "http") { stream->path = GetQueryValue(query, "path", ""); stream->host = GetQueryValue(query, "host", "").replace("|", ","); } else if (stream->network == "httpupgrade") { stream->path = GetQueryValue(query, "path", ""); stream->host = GetQueryValue(query, "host", ""); } else if (stream->network == "grpc") { stream->path = GetQueryValue(query, "serviceName", ""); } else if (stream->network == "tcp") { if (GetQueryValue(query, "headerType") == "http") { stream->header_type = "http"; stream->path = GetQueryValue(query, "path", ""); stream->host = GetQueryValue(query, "host", ""); } } return !(uuid.isEmpty() || serverAddress.isEmpty()); } return false; } bool NaiveBean::TryParseLink(const QString &link) { auto url = QUrl(link); if (!url.isValid()) return false; protocol = url.scheme().replace("naive+", ""); if (protocol != "https" && protocol != "quic") return false; name = url.fragment(QUrl::FullyDecoded); serverAddress = url.host(); serverPort = url.port(); username = url.userName(); password = url.password(); return !(username.isEmpty() || password.isEmpty() || serverAddress.isEmpty()); } bool QUICBean::TryParseLink(const QString &link) { auto url = QUrl(link); auto query = QUrlQuery(url.query()); if (url.host().isEmpty() || url.port() == -1) return false; if (url.scheme() == "tuic") { // by daeuniverse // https://github.com/daeuniverse/dae/discussions/182 name = url.fragment(QUrl::FullyDecoded); serverAddress = url.host(); if (serverPort == -1) serverPort = 443; serverPort = url.port(); uuid = url.userName(); password = url.password(); congestionControl = query.queryItemValue("congestion_control"); alpn = query.queryItemValue("alpn"); sni = query.queryItemValue("sni"); udpRelayMode = query.queryItemValue("udp_relay_mode"); allowInsecure = query.queryItemValue("allow_insecure") == "1"; disableSni = query.queryItemValue("disable_sni") == "1"; } else if (QStringList{"hy2", "hysteria2"}.contains(url.scheme())) { name = url.fragment(QUrl::FullyDecoded); serverAddress = url.host(); serverPort = url.port(); hopPort = query.queryItemValue("mport"); obfsPassword = query.queryItemValue("obfs-password"); allowInsecure = QStringList{"1", "true"}.contains(query.queryItemValue("insecure")); if (url.password().isEmpty()) { password = url.userName(); } else { password = url.userName() + ":" + url.password(); } sni = query.queryItemValue("sni"); } return true; } } // namespace NekoGui_fmt ================================================ FILE: fmt/NaiveBean.hpp ================================================ #pragma once #include "fmt/AbstractBean.hpp" namespace NekoGui_fmt { class NaiveBean : public AbstractBean { public: QString username = ""; QString password = ""; QString protocol = "https"; QString extra_headers = ""; QString sni = ""; QString certificate = ""; int insecure_concurrency = 0; bool disable_log = false; NaiveBean() : AbstractBean(0) { _add(new configItem("username", &username, itemType::string)); _add(new configItem("password", &password, itemType::string)); _add(new configItem("protocol", &protocol, itemType::string)); _add(new configItem("extra_headers", &extra_headers, itemType::string)); _add(new configItem("sni", &sni, itemType::string)); _add(new configItem("certificate", &certificate, itemType::string)); _add(new configItem("insecure_concurrency", &insecure_concurrency, itemType::integer)); _add(new configItem("disable_log", &disable_log, itemType::boolean)); }; QString DisplayCoreType() override { return "Naive"; }; QString DisplayType() override { return "Naive"; }; int NeedExternal(bool isFirstProfile) override; ExternalBuildResult BuildExternal(int mapping_port, int socks_port, int external_stat) override; bool TryParseLink(const QString &link); QString ToShareLink() override; }; } // namespace NekoGui_fmt ================================================ FILE: fmt/Preset.hpp ================================================ #pragma once namespace Preset { namespace SingBox { inline QStringList VpnImplementation = {"gvisor", "system", "mixed"}; inline QStringList DomainStrategy = {"", "ipv4_only", "ipv6_only", "prefer_ipv4", "prefer_ipv6"}; inline QStringList UtlsFingerPrint = {"", "chrome", "firefox", "edge", "safari", "360", "qq", "ios", "android", "random", "randomized"}; inline QStringList ShadowsocksMethods = {"2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305", "none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "rc4-md5", "chacha20-ietf", "xchacha20"}; inline QStringList Flows = {"xtls-rprx-vision"}; } // namespace SingBox namespace Windows { inline QStringList system_proxy_format{"{ip}:{http_port}", "socks={ip}:{socks_port}", "http={ip}:{http_port};https={ip}:{http_port};ftp={ip}:{http_port};socks={ip}:{socks_port}", "http=http://{ip}:{http_port};https=http://{ip}:{http_port}"}; } // namespace Windows } // namespace Preset ================================================ FILE: fmt/QUICBean.hpp ================================================ #pragma once #include "fmt/AbstractBean.hpp" namespace NekoGui_fmt { class QUICBean : public AbstractBean { public: // static constexpr int proxy_Hysteria = 0; static constexpr int proxy_TUIC = 1; static constexpr int proxy_Hysteria2 = 3; int proxy_type = proxy_Hysteria2; bool forceExternal = false; // Hysteria 2 QString obfsPassword = ""; int uploadMbps = 0; int downloadMbps = 0; qint64 streamReceiveWindow = 0; qint64 connectionReceiveWindow = 0; bool disableMtuDiscovery = false; int hopInterval = 10; QString hopPort = ""; // TUIC QString uuid = ""; QString congestionControl = "bbr"; QString udpRelayMode = "native"; bool zeroRttHandshake = false; QString heartbeat = "10s"; bool uos = false; // HY2&TUIC QString password = ""; // TLS bool allowInsecure = false; QString sni = ""; QString alpn = ""; QString caText = ""; bool disableSni = false; explicit QUICBean(int _proxy_type) : AbstractBean(0) { proxy_type = _proxy_type; if (proxy_type == proxy_Hysteria2) { _add(new configItem("obfsPassword", &obfsPassword, itemType::string)); _add(new configItem("uploadMbps", &uploadMbps, itemType::integer)); _add(new configItem("downloadMbps", &downloadMbps, itemType::integer)); _add(new configItem("streamReceiveWindow", &streamReceiveWindow, itemType::integer64)); _add(new configItem("connectionReceiveWindow", &connectionReceiveWindow, itemType::integer64)); _add(new configItem("disableMtuDiscovery", &disableMtuDiscovery, itemType::boolean)); _add(new configItem("hopInterval", &hopInterval, itemType::integer)); _add(new configItem("hopPort", &hopPort, itemType::string)); _add(new configItem("password", &password, itemType::string)); } else if (proxy_type == proxy_TUIC) { _add(new configItem("uuid", &uuid, itemType::string)); _add(new configItem("password", &password, itemType::string)); _add(new configItem("congestionControl", &congestionControl, itemType::string)); _add(new configItem("udpRelayMode", &udpRelayMode, itemType::string)); _add(new configItem("zeroRttHandshake", &zeroRttHandshake, itemType::boolean)); _add(new configItem("heartbeat", &heartbeat, itemType::string)); _add(new configItem("uos", &uos, itemType::boolean)); } _add(new configItem("forceExternal", &forceExternal, itemType::boolean)); // TLS _add(new configItem("allowInsecure", &allowInsecure, itemType::boolean)); _add(new configItem("sni", &sni, itemType::string)); _add(new configItem("alpn", &alpn, itemType::string)); _add(new configItem("caText", &caText, itemType::string)); _add(new configItem("disableSni", &disableSni, itemType::boolean)); }; QString DisplayAddress() override { if (!hopPort.trimmed().isEmpty()) return WrapIPV6Host(serverAddress) + ":" + hopPort; return ::DisplayAddress(serverAddress, serverPort); } QString DisplayCoreType() override { if (NeedExternal(true) == 0) { return software_core_name; } else if (proxy_type == proxy_TUIC) { return "tuic"; } else { return "hysteria2"; } } QString DisplayType() override { if (proxy_type == proxy_TUIC) { return "TUIC"; } else { return "Hysteria2"; } }; int NeedExternal(bool isFirstProfile) override; ExternalBuildResult BuildExternal(int mapping_port, int socks_port, int external_stat) override; CoreObjOutboundBuildResult BuildCoreObjSingBox() override; bool TryParseLink(const QString &link); QString ToShareLink() override; }; } // namespace NekoGui_fmt ================================================ FILE: fmt/ShadowSocksBean.hpp ================================================ #pragma once #include "fmt/AbstractBean.hpp" #include "fmt/V2RayStreamSettings.hpp" namespace NekoGui_fmt { class ShadowSocksBean : public AbstractBean { public: QString method = "aes-128-gcm"; QString password = ""; QString plugin = ""; int uot = 0; std::shared_ptr stream = std::make_shared(); ShadowSocksBean() : AbstractBean(0) { _add(new configItem("method", &method, itemType::string)); _add(new configItem("pass", &password, itemType::string)); _add(new configItem("plugin", &plugin, itemType::string)); _add(new configItem("uot", &uot, itemType::integer)); _add(new configItem("stream", dynamic_cast(stream.get()), itemType::jsonStore)); }; QString DisplayType() override { return "Shadowsocks"; }; CoreObjOutboundBuildResult BuildCoreObjSingBox() override; bool TryParseLink(const QString &link); QString ToShareLink() override; }; } // namespace NekoGui_fmt ================================================ FILE: fmt/SocksHttpBean.hpp ================================================ #pragma once #include "fmt/AbstractBean.hpp" #include "fmt/V2RayStreamSettings.hpp" namespace NekoGui_fmt { class SocksHttpBean : public AbstractBean { public: static constexpr int type_HTTP = -80; static constexpr int type_Socks4 = 4; static constexpr int type_Socks5 = 5; int socks_http_type = type_Socks5; QString username = ""; QString password = ""; std::shared_ptr stream = std::make_shared(); explicit SocksHttpBean(int _socks_http_type) : AbstractBean(0) { this->socks_http_type = _socks_http_type; _add(new configItem("v", &socks_http_type, itemType::integer)); _add(new configItem("username", &username, itemType::string)); _add(new configItem("password", &password, itemType::string)); _add(new configItem("stream", dynamic_cast(stream.get()), itemType::jsonStore)); }; QString DisplayType() override { return socks_http_type == type_HTTP ? "HTTP" : "Socks"; }; CoreObjOutboundBuildResult BuildCoreObjSingBox() override; bool TryParseLink(const QString &link); QString ToShareLink() override; }; } // namespace NekoGui_fmt ================================================ FILE: fmt/TrojanVLESSBean.hpp ================================================ #pragma once #include "fmt/AbstractBean.hpp" #include "fmt/V2RayStreamSettings.hpp" namespace NekoGui_fmt { class TrojanVLESSBean : public AbstractBean { public: static constexpr int proxy_Trojan = 0; static constexpr int proxy_VLESS = 1; int proxy_type = proxy_Trojan; QString password = ""; QString flow = ""; std::shared_ptr stream = std::make_shared(); explicit TrojanVLESSBean(int _proxy_type) : AbstractBean(0) { proxy_type = _proxy_type; _add(new configItem("pass", &password, itemType::string)); _add(new configItem("flow", &flow, itemType::string)); _add(new configItem("stream", dynamic_cast(stream.get()), itemType::jsonStore)); }; QString DisplayType() override { return proxy_type == proxy_VLESS ? "VLESS" : "Trojan"; }; CoreObjOutboundBuildResult BuildCoreObjSingBox() override; bool TryParseLink(const QString &link); QString ToShareLink() override; }; } // namespace NekoGui_fmt ================================================ FILE: fmt/V2RayStreamSettings.hpp ================================================ #pragma once #include "AbstractBean.hpp" namespace NekoGui_fmt { class V2rayStreamSettings : public JsonStore { public: QString network = "tcp"; QString security = ""; QString packet_encoding = ""; // ws/http/grpc/tcp-http/httpupgrade QString path = ""; QString host = ""; // kcp/quic/tcp-http QString header_type = ""; // tls QString sni = ""; QString alpn = ""; QString certificate = ""; QString utlsFingerprint = ""; bool allow_insecure = false; // ws early data QString ws_early_data_name = ""; int ws_early_data_length = 0; // reality QString reality_pbk = ""; QString reality_sid = ""; QString reality_spx = ""; // multiplex int multiplex_status = 0; V2rayStreamSettings() : JsonStore() { _add(new configItem("net", &network, itemType::string)); _add(new configItem("sec", &security, itemType::string)); _add(new configItem("pac_enc", &packet_encoding, itemType::string)); _add(new configItem("path", &path, itemType::string)); _add(new configItem("host", &host, itemType::string)); _add(new configItem("sni", &sni, itemType::string)); _add(new configItem("alpn", &alpn, itemType::string)); _add(new configItem("cert", &certificate, itemType::string)); _add(new configItem("insecure", &allow_insecure, itemType::boolean)); _add(new configItem("h_type", &header_type, itemType::string)); _add(new configItem("ed_name", &ws_early_data_name, itemType::string)); _add(new configItem("ed_len", &ws_early_data_length, itemType::integer)); _add(new configItem("utls", &utlsFingerprint, itemType::string)); _add(new configItem("pbk", &reality_pbk, itemType::string)); _add(new configItem("sid", &reality_sid, itemType::string)); _add(new configItem("spx", &reality_spx, itemType::string)); _add(new configItem("mux_s", &multiplex_status, itemType::integer)); } void BuildStreamSettingsSingBox(QJsonObject *outbound); }; inline V2rayStreamSettings *GetStreamSettings(AbstractBean *bean) { if (bean == nullptr) return nullptr; auto stream_item = bean->_get("stream"); if (stream_item != nullptr) { auto stream_store = (JsonStore *) stream_item->ptr; auto stream = (NekoGui_fmt::V2rayStreamSettings *) stream_store; return stream; } return nullptr; } } // namespace NekoGui_fmt ================================================ FILE: fmt/VMessBean.hpp ================================================ #pragma once #include "fmt/AbstractBean.hpp" #include "fmt/V2RayStreamSettings.hpp" namespace NekoGui_fmt { class VMessBean : public AbstractBean { public: QString uuid = ""; int aid = 0; QString security = "auto"; std::shared_ptr stream = std::make_shared(); VMessBean() : AbstractBean(0) { _add(new configItem("id", &uuid, itemType::string)); _add(new configItem("aid", &aid, itemType::integer)); _add(new configItem("sec", &security, itemType::string)); _add(new configItem("stream", dynamic_cast(stream.get()), itemType::jsonStore)); }; QString DisplayType() override { return "VMess"; }; CoreObjOutboundBuildResult BuildCoreObjSingBox() override; bool TryParseLink(const QString &link); QString ToShareLink() override; }; } // namespace NekoGui_fmt ================================================ FILE: fmt/includes.h ================================================ #pragma once #include "SocksHttpBean.hpp" #include "ShadowSocksBean.hpp" #include "ChainBean.hpp" #include "VMessBean.hpp" #include "TrojanVLESSBean.hpp" #include "NaiveBean.hpp" #include "QUICBean.hpp" #include "CustomBean.hpp" ================================================ FILE: go/.gitignore ================================================ *.log *.pem *.json *.exe *.dat /cmd/nekoray_core/nekoray_core /cmd/nekobox_core/nekobox_core *.db ================================================ FILE: go/cmd/nekobox_core/core_box.go ================================================ package main import ( "context" "net" "net/http" "github.com/matsuridayo/libneko/neko_common" "github.com/matsuridayo/libneko/neko_log" box "github.com/sagernet/sing-box" "github.com/sagernet/sing-box/boxapi" boxmain "github.com/sagernet/sing-box/cmd/sing-box" ) var instance *box.Box var instance_cancel context.CancelFunc func setupCore() { boxmain.SetDisableColor(true) // neko_log.SetupLog(50*1024, "./neko.log") // neko_common.GetCurrentInstance = func() interface{} { return instance } neko_common.DialContext = func(ctx context.Context, specifiedInstance interface{}, network, addr string) (net.Conn, error) { if i, ok := specifiedInstance.(*box.Box); ok { return boxapi.DialContext(ctx, i, network, addr) } if instance != nil { return boxapi.DialContext(ctx, instance, network, addr) } return neko_common.DialContextSystem(ctx, network, addr) } neko_common.DialUDP = func(ctx context.Context, specifiedInstance interface{}) (net.PacketConn, error) { if i, ok := specifiedInstance.(*box.Box); ok { return boxapi.DialUDP(ctx, i) } if instance != nil { return boxapi.DialUDP(ctx, instance) } return neko_common.DialUDPSystem(ctx) } neko_common.CreateProxyHttpClient = func(specifiedInstance interface{}) *http.Client { if i, ok := specifiedInstance.(*box.Box); ok { return boxapi.CreateProxyHttpClient(i) } return boxapi.CreateProxyHttpClient(instance) } } ================================================ FILE: go/cmd/nekobox_core/go.mod ================================================ module nekobox_core go 1.19 require ( github.com/matsuridayo/libneko v1.0.0 // replaced github.com/sagernet/sing-box v1.0.0 // replaced // github.com/sagernet/sing-dns v1.0.0 // indirect; replaced grpc_server v1.0.0 ) require ( berty.tech/go-libtor v1.0.385 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect github.com/ajg/form v1.5.1 // indirect github.com/andybalholm/brotli v1.0.6 // indirect github.com/caddyserver/certmagic v0.20.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/cretz/bine v0.2.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gaukas/godicttls v0.0.4 // indirect github.com/go-chi/chi/v5 v5.0.12 // indirect github.com/go-chi/cors v1.2.1 // indirect github.com/go-chi/render v1.0.3 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect github.com/gobwas/httphead v0.1.0 // indirect github.com/gobwas/pool v0.2.1 // indirect github.com/gofrs/uuid/v5 v5.2.0 // indirect github.com/google/btree v1.1.2 // indirect github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 // indirect github.com/josharian/native v1.1.0 // indirect github.com/klauspost/compress v1.17.4 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/libdns/alidns v1.0.3 // indirect github.com/libdns/cloudflare v0.1.1 // indirect github.com/libdns/libdns v0.2.2 // indirect github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d // indirect github.com/mholt/acmez v1.2.0 // indirect github.com/miekg/dns v1.1.59 // indirect github.com/onsi/ginkgo/v2 v2.9.7 // indirect github.com/ooni/go-libtor v1.1.8 // indirect github.com/oschwald/maxminddb-golang v1.12.0 // indirect github.com/pierrec/lz4/v4 v4.1.14 // indirect github.com/quic-go/qpack v0.4.0 // indirect github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // indirect github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f // indirect github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba // indirect github.com/sagernet/quic-go v0.47.0-beta.2 // indirect github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect github.com/sagernet/sing v0.4.3 // indirect github.com/sagernet/sing-dns v0.2.3 // indirect github.com/sagernet/sing-mux v0.2.0 // indirect github.com/sagernet/sing-quic v0.2.2 // indirect github.com/sagernet/sing-shadowsocks v0.2.7 // indirect github.com/sagernet/sing-shadowsocks2 v0.2.0 // indirect github.com/sagernet/sing-shadowtls v0.1.4 // indirect github.com/sagernet/sing-tun v0.3.3 // indirect github.com/sagernet/sing-vmess v0.1.12 // indirect github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect github.com/sagernet/utls v1.5.4 // indirect github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 // indirect github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect github.com/spf13/cobra v1.8.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 // indirect github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 // indirect github.com/zeebo/blake3 v0.2.3 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect golang.org/x/crypto v0.23.0 // indirect golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect golang.org/x/mod v0.18.0 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.18.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/grpc v1.63.2 // indirect google.golang.org/protobuf v1.33.0 // indirect lukechampine.com/blake3 v1.3.0 // indirect ) replace grpc_server => ../../grpc_server replace github.com/matsuridayo/libneko => ../../../../libneko replace github.com/sagernet/sing-box => ../../../../sing-box replace github.com/sagernet/sing-quic => ../../../../sing-quic // replace github.com/sagernet/sing => ../../../../sing // replace github.com/sagernet/sing-dns => ../../../../sing-dns ================================================ FILE: go/cmd/nekobox_core/go.sum ================================================ berty.tech/go-libtor v1.0.385 h1:RWK94C3hZj6Z2GdvePpHJLnWYobFr3bY/OdUJ5aoEXw= berty.tech/go-libtor v1.0.385/go.mod h1:9swOOQVb+kmvuAlsgWUK/4c52pm69AdbJsxLzk+fJEw= cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cretz/bine v0.1.0/go.mod h1:6PF6fWAvYtwjRGkAuDEJeWNOv3a2hUouSP/yRYXmvHw= github.com/cretz/bine v0.2.0 h1:8GiDRGlTgz+o8H9DSnsl+5MeBK4HsExxgl6WgzOCuZo= github.com/cretz/bine v0.2.0/go.mod h1:WU4o9QR9wWp8AVKtTM1XD5vUHkEqnf2vVSo6dBqbetI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4= github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gofrs/uuid/v5 v5.2.0 h1:qw1GMx6/y8vhVsx626ImfKMuS5CvJmhIKKtuyvfajMM= github.com/gofrs/uuid/v5 v5.2.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a h1:fEBsGL/sjAuJrgah5XqmmYsTLzJp/TO9Lhy39gkverk= github.com/google/pprof v0.0.0-20231101202521-4ca4178f5c7a/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2 h1:9K06NfxkBh25x56yVhWWlKFE8YpicaSfHwoV8SFbueA= github.com/insomniacslk/dhcp v0.0.0-20231206064809-8c70d406f6d2/go.mod h1:3A9PQ1cunSDF/1rbTq99Ts4pVnycWg+vlPkfeD2NLFI= github.com/josharian/native v1.0.1-0.20221213033349-c1e37c09b531/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ= github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE= github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054= github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU= github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d h1:j9LtzkYstLFoNvXW824QQeN7Y26uPL5249kzWKbzO9U= github.com/metacubex/tfo-go v0.0.0-20240821025650-e9be0afd5e7d/go.mod h1:c7bVFM9f5+VzeZ/6Kg77T/jrg1Xp8QpqlSHvG/aXVts= github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk= github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss= github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0= github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= github.com/ooni/go-libtor v1.1.8 h1:Wo3V3DVTxl5vZdxtQakqYP+DAHx7pPtAFSl1bnAa08w= github.com/ooni/go-libtor v1.1.8/go.mod h1:q1YyLwRD9GeMyeerVvwc0vJ2YgwDLTp2bdVcrh/JXyI= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0= github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY= github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k= github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y/6ZHJWrnNLoiNnSJaow6DPb8VW2I= github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0= github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba h1:EY5AS7CCtfmARNv2zXUOrsEMPFDGYxaw65JzA2p51Vk= github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= github.com/sagernet/quic-go v0.47.0-beta.2 h1:1tCGWFOSaXIeuQaHrwOMJIYvlupjTcaVInGQw5ArULU= github.com/sagernet/quic-go v0.47.0-beta.2/go.mod h1:bLVKvElSEMNv7pu7SZHscW02TYigzQ5lQu3Nh4wNh8Q= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= github.com/sagernet/sing v0.4.3 h1:Ty/NAiNnVd6844k7ujlL5lkzydhcTH5Psc432jXA4Y8= github.com/sagernet/sing v0.4.3/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls= github.com/sagernet/sing-dns v0.2.3 h1:YzeBUn2tR38F7HtvGEQ0kLRLmZWMEgi/+7wqa4Twb1k= github.com/sagernet/sing-dns v0.2.3/go.mod h1:BJpJv6XLnrUbSyIntOT6DG9FW0f4fETmPAHvNjOprLg= github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= github.com/sagernet/sing-shadowsocks v0.2.7 h1:zaopR1tbHEw5Nk6FAkM05wCslV6ahVegEZaKMv9ipx8= github.com/sagernet/sing-shadowsocks v0.2.7/go.mod h1:0rIKJZBR65Qi0zwdKezt4s57y/Tl1ofkaq6NlkzVuyE= github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wKFHi+8XwgADg= github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= github.com/sagernet/sing-tun v0.3.3 h1:LZnQNmfGcNG2KPTPkLgc+Lo7k606QJVkPp2DnjriwUk= github.com/sagernet/sing-tun v0.3.3/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ= github.com/sagernet/sing-vmess v0.1.12 h1:2gFD8JJb+eTFMoa8FIVMnknEi+vCSfaiTXTfEYAYAPg= github.com/sagernet/sing-vmess v0.1.12/go.mod h1:luTSsfyBGAc9VhtCqwjR+dt1QgqBhuYBCONB/POhF8I= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7/go.mod h1:FP9X2xjT/Az1EsG/orYYoC+5MojWnuI7hrffz8fGwwo= github.com/sagernet/utls v1.5.4 h1:KmsEGbB2dKUtCNC+44NwAdNAqnqQ6GA4pTO0Yik56co= github.com/sagernet/utls v1.5.4/go.mod h1:CTGxPWExIloRipK3XFpYv0OVyhO8kk3XCGW/ieyTh1s= github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8 h1:R0OMYAScomNAVpTfbHFpxqJpvwuhxSRi+g6z7gZhABs= github.com/sagernet/wireguard-go v0.0.0-20231215174105-89dec3b2f3e8/go.mod h1:K4J7/npM+VAMUeUmTa2JaA02JmyheP0GpRBOUvn3ecc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923 h1:tHNk7XK9GkmKUR6Gh8gVBKXc2MVSZ4G/NnWLtzw4gNA= github.com/u-root/uio v0.0.0-20230220225925-ffce2a382923/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74 h1:gga7acRE695APm9hlsSMoOoE65U4/TcqNj90mc69Rlg= github.com/vishvananda/netns v0.0.0-20211101163701-50045581ed74/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190404164418-38d8ce5564a5/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6 h1:CawjfCvYQH2OU3/TnxLx97WDSUDRABfT18pCOYwc2GE= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230429144221-925a1e7659e6/go.mod h1:3rxYc4HtVcSG9gVaTs2GEBdehh+sYPOwKtyUWEOTb80= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= ================================================ FILE: go/cmd/nekobox_core/grpc_box.go ================================================ package main import ( "context" "errors" "fmt" "grpc_server" "grpc_server/gen" "github.com/matsuridayo/libneko/neko_common" "github.com/matsuridayo/libneko/neko_log" "github.com/matsuridayo/libneko/speedtest" box "github.com/sagernet/sing-box" "github.com/sagernet/sing-box/boxapi" boxmain "github.com/sagernet/sing-box/cmd/sing-box" "log" "github.com/sagernet/sing-box/option" ) type server struct { grpc_server.BaseServer } func (s *server) Start(ctx context.Context, in *gen.LoadConfigReq) (out *gen.ErrorResp, _ error) { var err error defer func() { out = &gen.ErrorResp{} if err != nil { out.Error = err.Error() instance = nil } }() if neko_common.Debug { log.Println("Start:", in.CoreConfig) } if instance != nil { err = errors.New("instance already started") return } instance, instance_cancel, err = boxmain.Create([]byte(in.CoreConfig)) if instance != nil { // Logger instance.SetLogWritter(neko_log.LogWriter) // V2ray Service if in.StatsOutbounds != nil { instance.Router().SetV2RayServer(boxapi.NewSbV2rayServer(option.V2RayStatsServiceOptions{ Enabled: true, Outbounds: in.StatsOutbounds, })) } } return } func (s *server) Stop(ctx context.Context, in *gen.EmptyReq) (out *gen.ErrorResp, _ error) { var err error defer func() { out = &gen.ErrorResp{} if err != nil { out.Error = err.Error() } }() if instance == nil { return } instance_cancel() instance.Close() instance = nil return } func (s *server) Test(ctx context.Context, in *gen.TestReq) (out *gen.TestResp, _ error) { var err error out = &gen.TestResp{Ms: 0} defer func() { if err != nil { out.Error = err.Error() } }() if in.Mode == gen.TestMode_UrlTest { var i *box.Box var cancel context.CancelFunc if in.Config != nil { // Test instance i, cancel, err = boxmain.Create([]byte(in.Config.CoreConfig)) if i != nil { defer i.Close() defer cancel() } if err != nil { return } } else { // Test running instance i = instance if i == nil { return } } // Latency out.Ms, err = speedtest.UrlTest(boxapi.CreateProxyHttpClient(i), in.Url, in.Timeout, speedtest.UrlTestStandard_RTT) } else if in.Mode == gen.TestMode_TcpPing { out.Ms, err = speedtest.TcpPing(in.Address, in.Timeout) } else if in.Mode == gen.TestMode_FullTest { i, cancel, err := boxmain.Create([]byte(in.Config.CoreConfig)) if i != nil { defer i.Close() defer cancel() } if err != nil { return } return grpc_server.DoFullTest(ctx, in, i) } return } func (s *server) QueryStats(ctx context.Context, in *gen.QueryStatsReq) (out *gen.QueryStatsResp, _ error) { out = &gen.QueryStatsResp{} if instance != nil { if ss, ok := instance.Router().V2RayServer().(*boxapi.SbV2rayServer); ok { out.Traffic = ss.QueryStats(fmt.Sprintf("outbound>>>%s>>>traffic>>>%s", in.Tag, in.Direct)) } } return } func (s *server) ListConnections(ctx context.Context, in *gen.EmptyReq) (*gen.ListConnectionsResp, error) { out := &gen.ListConnectionsResp{ // TODO upstream api } return out, nil } ================================================ FILE: go/cmd/nekobox_core/main.go ================================================ package main import ( "fmt" "os" _ "unsafe" "grpc_server" "github.com/matsuridayo/libneko/neko_common" boxmain "github.com/sagernet/sing-box/cmd/sing-box" "github.com/sagernet/sing-box/constant" ) func main() { fmt.Println("sing-box:", constant.Version, "NekoBox:", neko_common.Version_neko) fmt.Println() // nekobox_core if len(os.Args) > 1 && os.Args[1] == "nekobox" { neko_common.RunMode = neko_common.RunMode_NekoBox_Core grpc_server.RunCore(setupCore, &server{}) return } // sing-box boxmain.Main() } ================================================ FILE: go/cmd/updater/.gitignore ================================================ /updater /launcher ================================================ FILE: go/cmd/updater/go.mod ================================================ module updater go 1.18 require github.com/codeclysm/extract v2.2.0+incompatible require ( github.com/h2non/filetype v1.1.3 // indirect github.com/juju/errors v0.0.0-20220331221717-b38fca44723b // indirect github.com/stretchr/testify v1.7.1 // indirect ) ================================================ FILE: go/cmd/updater/go.sum ================================================ github.com/codeclysm/extract v2.2.0+incompatible h1:q3wyckoA30bhUSiwdQezMqVhwd8+WGE64/GL//LtUhI= github.com/codeclysm/extract v2.2.0+incompatible/go.mod h1:2nhFMPHiU9At61hz+12bfrlpXSUrOnK+wR+KlGO4Uks= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/juju/errors v0.0.0-20220331221717-b38fca44723b h1:AxFeSQJfcm2O3ov1wqAkTKYFsnMw2g1B4PkYujfAdkY= github.com/juju/errors v0.0.0-20220331221717-b38fca44723b/go.mod h1:jMGj9DWF/qbo91ODcfJq6z/RYc3FX3taCBZMCcpI4Ls= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= ================================================ FILE: go/cmd/updater/launcher.go ================================================ //go:build !linux package main import ( "log" "runtime" ) func Launcher() { log.Fatalln("launcher is not for your platform", runtime.GOOS) } ================================================ FILE: go/cmd/updater/launcher_linux.go ================================================ package main import ( "flag" "log" "os" "os/exec" "path/filepath" ) var local_qt_theme bool func Launcher() { log.Println("Running as launcher") wd, _ := filepath.Abs(".") _debug := flag.Bool("debug", false, "Debug mode") flag.Parse() cmd := exec.Command("./nekobox", flag.Args()...) ld_env := "LD_LIBRARY_PATH=" + filepath.Join(wd, "./usr/lib") qt_plugin_env := "QT_PLUGIN_PATH=" + filepath.Join(wd, "./usr/plugins") // Qt 5.12 abi is usually compatible with system Qt 5.15 // But use package Qt 5.12 by default. cmd.Env = os.Environ() cmd.Env = append(cmd.Env, "NKR_FROM_LAUNCHER=1") cmd.Env = append(cmd.Env, ld_env, qt_plugin_env) log.Println(ld_env, qt_plugin_env, cmd) if *_debug { cmd.Env = append(cmd.Env, "QT_DEBUG_PLUGINS=1") cmd.Stdin = os.Stdin cmd.Stderr = os.Stderr cmd.Stdout = os.Stdout cmd.Run() } else { cmd.Start() } } ================================================ FILE: go/cmd/updater/main.go ================================================ package main import ( "io/ioutil" "log" "os" "os/exec" "path/filepath" "runtime" "strings" "time" ) func main() { // update & launcher exe, err := os.Executable() if err != nil { panic(err.Error()) } wd := filepath.Dir(exe) os.Chdir(wd) exe = filepath.Base(os.Args[0]) log.Println("exe:", exe, "exe dir:", wd) if strings.HasPrefix(strings.ToLower(exe), "updater") { if runtime.GOOS == "windows" { if strings.HasPrefix(strings.ToLower(exe), "updater.old") { // 2. "updater.old" update files time.Sleep(time.Second) Updater() // 3. start exec.Command("./nekobox.exe").Start() } else { // 1. main prog quit and run "updater.exe" Copy("./updater.exe", "./updater.old") exec.Command("./updater.old", os.Args[1:]...).Start() } } else { // 1. update files Updater() // 2. start if os.Getenv("NKR_FROM_LAUNCHER") == "1" { Launcher() } else { exec.Command("./nekobox").Start() } } return } else if strings.HasPrefix(strings.ToLower(exe), "launcher") { Launcher() return } log.Fatalf("wrong name") } func Copy(src string, dst string) { // Read all content of src to data data, _ := ioutil.ReadFile(src) // Write data to dst ioutil.WriteFile(dst, data, 0644) } ================================================ FILE: go/cmd/updater/msgbox.go ================================================ //go:build !windows package main func MessageBoxPlain(title, caption string) int { return 0 } ================================================ FILE: go/cmd/updater/msgbox_windows.go ================================================ package main import ( "syscall" "unsafe" ) // MessageBoxPlain of Win32 API. func MessageBoxPlain(title, caption string) int { const ( NULL = 0 MB_OK = 0 ) return MessageBox(NULL, caption, title, MB_OK) } // MessageBox of Win32 API. func MessageBox(hwnd uintptr, caption, title string, flags uint) int { ret, _, _ := syscall.NewLazyDLL("user32.dll").NewProc("MessageBoxW").Call( uintptr(hwnd), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(caption))), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(title))), uintptr(flags)) return int(ret) } ================================================ FILE: go/cmd/updater/updater.go ================================================ package main import ( "context" "log" "os" "path/filepath" "runtime" "strings" "github.com/codeclysm/extract" ) func Updater() { pre_cleanup := func() { if runtime.GOOS == "linux" { os.RemoveAll("./usr") } os.RemoveAll("./nekoray_update") } // find update package var updatePackagePath string if len(os.Args) == 2 && Exist(os.Args[1]) { updatePackagePath = os.Args[1] } else if Exist("./nekoray.zip") { updatePackagePath = "./nekoray.zip" } else if Exist("./nekoray.tar.gz") { updatePackagePath = "./nekoray.tar.gz" } else { log.Fatalln("no update") } log.Println("updating from", updatePackagePath) // extract update package if strings.HasSuffix(updatePackagePath, ".zip") { pre_cleanup() f, err := os.Open(updatePackagePath) if err != nil { log.Fatalln(err.Error()) } err = extract.Zip(context.Background(), f, "./nekoray_update", nil) if err != nil { log.Fatalln(err.Error()) } f.Close() } else if strings.HasSuffix(updatePackagePath, ".tar.gz") { pre_cleanup() f, err := os.Open(updatePackagePath) if err != nil { log.Fatalln(err.Error()) } err = extract.Gz(context.Background(), f, "./nekoray_update", nil) if err != nil { log.Fatalln(err.Error()) } f.Close() } // remove old file removeAll("./*.dll") removeAll("./*.dmp") // update move err := Mv("./nekoray_update/nekoray", "./") if err != nil { MessageBoxPlain("NekoGui Updater", "Update failed. Please close the running instance and run the updater again.\n\n"+err.Error()) log.Fatalln(err.Error()) } os.RemoveAll("./nekoray_update") os.RemoveAll("./nekoray.zip") os.RemoveAll("./nekoray.tar.gz") // nekoray -> nekobox os.Remove("./nekoray.exe") os.Remove("./nekoray.png") os.Remove("./nekoray_core.exe") } func Exist(path string) bool { _, err := os.Stat(path) return err == nil } func FindExist(paths []string) string { for _, path := range paths { if Exist(path) { return path } } return "" } func Mv(src, dst string) error { s, err := os.Stat(src) if err != nil { return err } if s.IsDir() { es, err := os.ReadDir(src) if err != nil { return err } for _, e := range es { err = Mv(filepath.Join(src, e.Name()), filepath.Join(dst, e.Name())) if err != nil { return err } } } else { err = os.MkdirAll(filepath.Dir(dst), 0755) if err != nil { return err } err = os.Rename(src, dst) if err != nil { return err } } return nil } func removeAll(glob string) { files, _ := filepath.Glob(glob) for _, f := range files { os.Remove(f) } } ================================================ FILE: go/grpc_server/auth/auth.go ================================================ package auth import ( "context" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" ) // Authenticator exposes a function for authenticating requests. type Authenticator struct { Token string } // Authenticate checks that a token exists and is valid. It stores the user // metadata in the returned context and removes the token from the context. func (a Authenticator) Authenticate(ctx context.Context) (newCtx context.Context, err error) { auth, err := extractHeader(ctx, "nekoray_auth") if err != nil { return ctx, err } if auth != a.Token { return ctx, status.Error(codes.Unauthenticated, "invalid token") } return purgeHeader(ctx, "nekoray_auth"), nil } func extractHeader(ctx context.Context, header string) (string, error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { return "", status.Error(codes.Unauthenticated, "no headers in request") } authHeaders, ok := md[header] if !ok { return "", status.Error(codes.Unauthenticated, "no header in request") } if len(authHeaders) != 1 { return "", status.Error(codes.Unauthenticated, "more than 1 header in request") } return authHeaders[0], nil } func purgeHeader(ctx context.Context, header string) context.Context { md, _ := metadata.FromIncomingContext(ctx) mdCopy := md.Copy() mdCopy[header] = nil return metadata.NewIncomingContext(ctx, mdCopy) } ================================================ FILE: go/grpc_server/fulltest.go ================================================ package grpc_server import ( "context" "encoding/hex" "fmt" "grpc_server/gen" "io" "log" "math" "net" "net/http" "strings" "time" "github.com/matsuridayo/libneko/neko_common" "github.com/matsuridayo/libneko/speedtest" ) const ( KiB = 1024 MiB = 1024 * KiB ) func getBetweenStr(str, start, end string) string { n := strings.Index(str, start) if n == -1 { n = 0 } str = string([]byte(str)[n:]) m := strings.Index(str, end) if m == -1 { m = len(str) } str = string([]byte(str)[:m]) return str[len(start):] } func DoFullTest(ctx context.Context, in *gen.TestReq, instance interface{}) (out *gen.TestResp, _ error) { out = &gen.TestResp{} httpClient := neko_common.CreateProxyHttpClient(instance) // Latency var latency string if in.FullLatency { t, _ := speedtest.UrlTest(httpClient, in.Url, in.Timeout, speedtest.UrlTestStandard_RTT) out.Ms = t if t > 0 { latency = fmt.Sprint(t, "ms") } else { latency = "Error" } } // UDP Latency var udpLatency string if in.FullUdpLatency { ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) result := make(chan string) go func() { var startTime = time.Now() pc, err := neko_common.DialContext(ctx, instance, "udp", "8.8.8.8:53") if err == nil { defer pc.Close() dnsPacket, _ := hex.DecodeString("0000010000010000000000000377777706676f6f676c6503636f6d0000010001") _, err = pc.Write(dnsPacket) if err == nil { var buf [1400]byte _, err = pc.Read(buf[:]) } } if err == nil { var endTime = time.Now() result <- fmt.Sprint(endTime.Sub(startTime).Abs().Milliseconds(), "ms") } else { log.Println("UDP Latency test error:", err) result <- "Error" } close(result) }() select { case <-ctx.Done(): udpLatency = "Timeout" case r := <-result: udpLatency = r } cancel() } // 入口 IP var in_ip string if in.FullInOut { _in_ip, err := net.ResolveIPAddr("ip", in.InAddress) if err == nil { in_ip = _in_ip.String() } else { in_ip = err.Error() } } // 出口 IP var out_ip string if in.FullInOut { resp, err := httpClient.Get("https://www.cloudflare.com/cdn-cgi/trace") if err == nil { b, _ := io.ReadAll(resp.Body) out_ip = getBetweenStr(string(b), "ip=", "\n") resp.Body.Close() } else { out_ip = "Error" } } // 下载 var speed string if in.FullSpeed { if in.FullSpeedTimeout <= 0 { in.FullSpeedTimeout = 30 } ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(in.FullSpeedTimeout)) result := make(chan string) var bodyClose io.Closer go func() { req, _ := http.NewRequestWithContext(ctx, "GET", in.FullSpeedUrl, nil) resp, err := httpClient.Do(req) if err == nil && resp != nil && resp.Body != nil { bodyClose = resp.Body defer resp.Body.Close() timeStart := time.Now() n, _ := io.Copy(io.Discard, resp.Body) timeEnd := time.Now() duration := math.Max(timeEnd.Sub(timeStart).Seconds(), 0.000001) resultSpeed := (float64(n) / duration) / MiB result <- fmt.Sprintf("%.2fMiB/s", resultSpeed) } else { result <- "Error" } close(result) }() select { case <-ctx.Done(): speed = "Timeout" case s := <-result: speed = s } cancel() if bodyClose != nil { bodyClose.Close() } } fr := make([]string, 0) if latency != "" { fr = append(fr, fmt.Sprintf("Latency: %s", latency)) } if udpLatency != "" { fr = append(fr, fmt.Sprintf("UDPLatency: %s", udpLatency)) } if speed != "" { fr = append(fr, fmt.Sprintf("Speed: %s", speed)) } if in_ip != "" { fr = append(fr, fmt.Sprintf("In: %s", in_ip)) } if out_ip != "" { fr = append(fr, fmt.Sprintf("Out: %s", out_ip)) } out.FullReport = strings.Join(fr, " / ") return } ================================================ FILE: go/grpc_server/gen/libcore.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.28.1 // protoc v4.23.3 // source: libcore.proto package gen import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" sync "sync" ) const ( // Verify that this generated code is sufficiently up-to-date. _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) // Verify that runtime/protoimpl is sufficiently up-to-date. _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) type TestMode int32 const ( TestMode_TcpPing TestMode = 0 TestMode_UrlTest TestMode = 1 TestMode_FullTest TestMode = 2 ) // Enum value maps for TestMode. var ( TestMode_name = map[int32]string{ 0: "TcpPing", 1: "UrlTest", 2: "FullTest", } TestMode_value = map[string]int32{ "TcpPing": 0, "UrlTest": 1, "FullTest": 2, } ) func (x TestMode) Enum() *TestMode { p := new(TestMode) *p = x return p } func (x TestMode) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (TestMode) Descriptor() protoreflect.EnumDescriptor { return file_libcore_proto_enumTypes[0].Descriptor() } func (TestMode) Type() protoreflect.EnumType { return &file_libcore_proto_enumTypes[0] } func (x TestMode) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use TestMode.Descriptor instead. func (TestMode) EnumDescriptor() ([]byte, []int) { return file_libcore_proto_rawDescGZIP(), []int{0} } type UpdateAction int32 const ( UpdateAction_Check UpdateAction = 0 UpdateAction_Download UpdateAction = 1 ) // Enum value maps for UpdateAction. var ( UpdateAction_name = map[int32]string{ 0: "Check", 1: "Download", } UpdateAction_value = map[string]int32{ "Check": 0, "Download": 1, } ) func (x UpdateAction) Enum() *UpdateAction { p := new(UpdateAction) *p = x return p } func (x UpdateAction) String() string { return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) } func (UpdateAction) Descriptor() protoreflect.EnumDescriptor { return file_libcore_proto_enumTypes[1].Descriptor() } func (UpdateAction) Type() protoreflect.EnumType { return &file_libcore_proto_enumTypes[1] } func (x UpdateAction) Number() protoreflect.EnumNumber { return protoreflect.EnumNumber(x) } // Deprecated: Use UpdateAction.Descriptor instead. func (UpdateAction) EnumDescriptor() ([]byte, []int) { return file_libcore_proto_rawDescGZIP(), []int{1} } type EmptyReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *EmptyReq) Reset() { *x = EmptyReq{} if protoimpl.UnsafeEnabled { mi := &file_libcore_proto_msgTypes[0] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *EmptyReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*EmptyReq) ProtoMessage() {} func (x *EmptyReq) ProtoReflect() protoreflect.Message { mi := &file_libcore_proto_msgTypes[0] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use EmptyReq.ProtoReflect.Descriptor instead. func (*EmptyReq) Descriptor() ([]byte, []int) { return file_libcore_proto_rawDescGZIP(), []int{0} } type EmptyResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields } func (x *EmptyResp) Reset() { *x = EmptyResp{} if protoimpl.UnsafeEnabled { mi := &file_libcore_proto_msgTypes[1] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *EmptyResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*EmptyResp) ProtoMessage() {} func (x *EmptyResp) ProtoReflect() protoreflect.Message { mi := &file_libcore_proto_msgTypes[1] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use EmptyResp.ProtoReflect.Descriptor instead. func (*EmptyResp) Descriptor() ([]byte, []int) { return file_libcore_proto_rawDescGZIP(), []int{1} } type ErrorResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` } func (x *ErrorResp) Reset() { *x = ErrorResp{} if protoimpl.UnsafeEnabled { mi := &file_libcore_proto_msgTypes[2] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ErrorResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*ErrorResp) ProtoMessage() {} func (x *ErrorResp) ProtoReflect() protoreflect.Message { mi := &file_libcore_proto_msgTypes[2] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ErrorResp.ProtoReflect.Descriptor instead. func (*ErrorResp) Descriptor() ([]byte, []int) { return file_libcore_proto_rawDescGZIP(), []int{2} } func (x *ErrorResp) GetError() string { if x != nil { return x.Error } return "" } type LoadConfigReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields CoreConfig string `protobuf:"bytes,1,opt,name=core_config,json=coreConfig,proto3" json:"core_config,omitempty"` EnableNekorayConnections bool `protobuf:"varint,2,opt,name=enable_nekoray_connections,json=enableNekorayConnections,proto3" json:"enable_nekoray_connections,omitempty"` StatsOutbounds []string `protobuf:"bytes,3,rep,name=stats_outbounds,json=statsOutbounds,proto3" json:"stats_outbounds,omitempty"` } func (x *LoadConfigReq) Reset() { *x = LoadConfigReq{} if protoimpl.UnsafeEnabled { mi := &file_libcore_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *LoadConfigReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*LoadConfigReq) ProtoMessage() {} func (x *LoadConfigReq) ProtoReflect() protoreflect.Message { mi := &file_libcore_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use LoadConfigReq.ProtoReflect.Descriptor instead. func (*LoadConfigReq) Descriptor() ([]byte, []int) { return file_libcore_proto_rawDescGZIP(), []int{3} } func (x *LoadConfigReq) GetCoreConfig() string { if x != nil { return x.CoreConfig } return "" } func (x *LoadConfigReq) GetEnableNekorayConnections() bool { if x != nil { return x.EnableNekorayConnections } return false } func (x *LoadConfigReq) GetStatsOutbounds() []string { if x != nil { return x.StatsOutbounds } return nil } type TestReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Mode TestMode `protobuf:"varint,1,opt,name=mode,proto3,enum=libcore.TestMode" json:"mode,omitempty"` Timeout int32 `protobuf:"varint,6,opt,name=timeout,proto3" json:"timeout,omitempty"` // TcpPing Address string `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"` // UrlTest Config *LoadConfigReq `protobuf:"bytes,3,opt,name=config,proto3" json:"config,omitempty"` Inbound string `protobuf:"bytes,4,opt,name=inbound,proto3" json:"inbound,omitempty"` Url string `protobuf:"bytes,5,opt,name=url,proto3" json:"url,omitempty"` // FullTest InAddress string `protobuf:"bytes,7,opt,name=in_address,json=inAddress,proto3" json:"in_address,omitempty"` FullLatency bool `protobuf:"varint,8,opt,name=full_latency,json=fullLatency,proto3" json:"full_latency,omitempty"` FullSpeed bool `protobuf:"varint,9,opt,name=full_speed,json=fullSpeed,proto3" json:"full_speed,omitempty"` FullSpeedUrl string `protobuf:"bytes,13,opt,name=full_speed_url,json=fullSpeedUrl,proto3" json:"full_speed_url,omitempty"` FullSpeedTimeout int32 `protobuf:"varint,14,opt,name=full_speed_timeout,json=fullSpeedTimeout,proto3" json:"full_speed_timeout,omitempty"` FullInOut bool `protobuf:"varint,10,opt,name=full_in_out,json=fullInOut,proto3" json:"full_in_out,omitempty"` FullUdpLatency bool `protobuf:"varint,12,opt,name=full_udp_latency,json=fullUdpLatency,proto3" json:"full_udp_latency,omitempty"` // Deprecated: Do not use. FullNat bool `protobuf:"varint,11,opt,name=full_nat,json=fullNat,proto3" json:"full_nat,omitempty"` } func (x *TestReq) Reset() { *x = TestReq{} if protoimpl.UnsafeEnabled { mi := &file_libcore_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *TestReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*TestReq) ProtoMessage() {} func (x *TestReq) ProtoReflect() protoreflect.Message { mi := &file_libcore_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TestReq.ProtoReflect.Descriptor instead. func (*TestReq) Descriptor() ([]byte, []int) { return file_libcore_proto_rawDescGZIP(), []int{4} } func (x *TestReq) GetMode() TestMode { if x != nil { return x.Mode } return TestMode_TcpPing } func (x *TestReq) GetTimeout() int32 { if x != nil { return x.Timeout } return 0 } func (x *TestReq) GetAddress() string { if x != nil { return x.Address } return "" } func (x *TestReq) GetConfig() *LoadConfigReq { if x != nil { return x.Config } return nil } func (x *TestReq) GetInbound() string { if x != nil { return x.Inbound } return "" } func (x *TestReq) GetUrl() string { if x != nil { return x.Url } return "" } func (x *TestReq) GetInAddress() string { if x != nil { return x.InAddress } return "" } func (x *TestReq) GetFullLatency() bool { if x != nil { return x.FullLatency } return false } func (x *TestReq) GetFullSpeed() bool { if x != nil { return x.FullSpeed } return false } func (x *TestReq) GetFullSpeedUrl() string { if x != nil { return x.FullSpeedUrl } return "" } func (x *TestReq) GetFullSpeedTimeout() int32 { if x != nil { return x.FullSpeedTimeout } return 0 } func (x *TestReq) GetFullInOut() bool { if x != nil { return x.FullInOut } return false } func (x *TestReq) GetFullUdpLatency() bool { if x != nil { return x.FullUdpLatency } return false } // Deprecated: Do not use. func (x *TestReq) GetFullNat() bool { if x != nil { return x.FullNat } return false } type TestResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` Ms int32 `protobuf:"varint,2,opt,name=ms,proto3" json:"ms,omitempty"` FullReport string `protobuf:"bytes,3,opt,name=full_report,json=fullReport,proto3" json:"full_report,omitempty"` } func (x *TestResp) Reset() { *x = TestResp{} if protoimpl.UnsafeEnabled { mi := &file_libcore_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *TestResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*TestResp) ProtoMessage() {} func (x *TestResp) ProtoReflect() protoreflect.Message { mi := &file_libcore_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use TestResp.ProtoReflect.Descriptor instead. func (*TestResp) Descriptor() ([]byte, []int) { return file_libcore_proto_rawDescGZIP(), []int{5} } func (x *TestResp) GetError() string { if x != nil { return x.Error } return "" } func (x *TestResp) GetMs() int32 { if x != nil { return x.Ms } return 0 } func (x *TestResp) GetFullReport() string { if x != nil { return x.FullReport } return "" } type QueryStatsReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` Direct string `protobuf:"bytes,2,opt,name=direct,proto3" json:"direct,omitempty"` } func (x *QueryStatsReq) Reset() { *x = QueryStatsReq{} if protoimpl.UnsafeEnabled { mi := &file_libcore_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *QueryStatsReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*QueryStatsReq) ProtoMessage() {} func (x *QueryStatsReq) ProtoReflect() protoreflect.Message { mi := &file_libcore_proto_msgTypes[6] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use QueryStatsReq.ProtoReflect.Descriptor instead. func (*QueryStatsReq) Descriptor() ([]byte, []int) { return file_libcore_proto_rawDescGZIP(), []int{6} } func (x *QueryStatsReq) GetTag() string { if x != nil { return x.Tag } return "" } func (x *QueryStatsReq) GetDirect() string { if x != nil { return x.Direct } return "" } type QueryStatsResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Traffic int64 `protobuf:"varint,1,opt,name=traffic,proto3" json:"traffic,omitempty"` } func (x *QueryStatsResp) Reset() { *x = QueryStatsResp{} if protoimpl.UnsafeEnabled { mi := &file_libcore_proto_msgTypes[7] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *QueryStatsResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*QueryStatsResp) ProtoMessage() {} func (x *QueryStatsResp) ProtoReflect() protoreflect.Message { mi := &file_libcore_proto_msgTypes[7] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use QueryStatsResp.ProtoReflect.Descriptor instead. func (*QueryStatsResp) Descriptor() ([]byte, []int) { return file_libcore_proto_rawDescGZIP(), []int{7} } func (x *QueryStatsResp) GetTraffic() int64 { if x != nil { return x.Traffic } return 0 } type UpdateReq struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Action UpdateAction `protobuf:"varint,1,opt,name=action,proto3,enum=libcore.UpdateAction" json:"action,omitempty"` CheckPreRelease bool `protobuf:"varint,2,opt,name=check_pre_release,json=checkPreRelease,proto3" json:"check_pre_release,omitempty"` } func (x *UpdateReq) Reset() { *x = UpdateReq{} if protoimpl.UnsafeEnabled { mi := &file_libcore_proto_msgTypes[8] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *UpdateReq) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdateReq) ProtoMessage() {} func (x *UpdateReq) ProtoReflect() protoreflect.Message { mi := &file_libcore_proto_msgTypes[8] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UpdateReq.ProtoReflect.Descriptor instead. func (*UpdateReq) Descriptor() ([]byte, []int) { return file_libcore_proto_rawDescGZIP(), []int{8} } func (x *UpdateReq) GetAction() UpdateAction { if x != nil { return x.Action } return UpdateAction_Check } func (x *UpdateReq) GetCheckPreRelease() bool { if x != nil { return x.CheckPreRelease } return false } type UpdateResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` AssetsName string `protobuf:"bytes,2,opt,name=assets_name,json=assetsName,proto3" json:"assets_name,omitempty"` DownloadUrl string `protobuf:"bytes,3,opt,name=download_url,json=downloadUrl,proto3" json:"download_url,omitempty"` ReleaseUrl string `protobuf:"bytes,4,opt,name=release_url,json=releaseUrl,proto3" json:"release_url,omitempty"` ReleaseNote string `protobuf:"bytes,5,opt,name=release_note,json=releaseNote,proto3" json:"release_note,omitempty"` IsPreRelease bool `protobuf:"varint,6,opt,name=is_pre_release,json=isPreRelease,proto3" json:"is_pre_release,omitempty"` } func (x *UpdateResp) Reset() { *x = UpdateResp{} if protoimpl.UnsafeEnabled { mi := &file_libcore_proto_msgTypes[9] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *UpdateResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*UpdateResp) ProtoMessage() {} func (x *UpdateResp) ProtoReflect() protoreflect.Message { mi := &file_libcore_proto_msgTypes[9] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use UpdateResp.ProtoReflect.Descriptor instead. func (*UpdateResp) Descriptor() ([]byte, []int) { return file_libcore_proto_rawDescGZIP(), []int{9} } func (x *UpdateResp) GetError() string { if x != nil { return x.Error } return "" } func (x *UpdateResp) GetAssetsName() string { if x != nil { return x.AssetsName } return "" } func (x *UpdateResp) GetDownloadUrl() string { if x != nil { return x.DownloadUrl } return "" } func (x *UpdateResp) GetReleaseUrl() string { if x != nil { return x.ReleaseUrl } return "" } func (x *UpdateResp) GetReleaseNote() string { if x != nil { return x.ReleaseNote } return "" } func (x *UpdateResp) GetIsPreRelease() bool { if x != nil { return x.IsPreRelease } return false } type ListConnectionsResp struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields NekorayConnectionsJson string `protobuf:"bytes,1,opt,name=nekoray_connections_json,json=nekorayConnectionsJson,proto3" json:"nekoray_connections_json,omitempty"` } func (x *ListConnectionsResp) Reset() { *x = ListConnectionsResp{} if protoimpl.UnsafeEnabled { mi := &file_libcore_proto_msgTypes[10] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } } func (x *ListConnectionsResp) String() string { return protoimpl.X.MessageStringOf(x) } func (*ListConnectionsResp) ProtoMessage() {} func (x *ListConnectionsResp) ProtoReflect() protoreflect.Message { mi := &file_libcore_proto_msgTypes[10] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) } return ms } return mi.MessageOf(x) } // Deprecated: Use ListConnectionsResp.ProtoReflect.Descriptor instead. func (*ListConnectionsResp) Descriptor() ([]byte, []int) { return file_libcore_proto_rawDescGZIP(), []int{10} } func (x *ListConnectionsResp) GetNekorayConnectionsJson() string { if x != nil { return x.NekorayConnectionsJson } return "" } var File_libcore_proto protoreflect.FileDescriptor var file_libcore_proto_rawDesc = []byte{ 0x0a, 0x0d, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x07, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x22, 0x0a, 0x0a, 0x08, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x22, 0x0b, 0x0a, 0x09, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x22, 0x21, 0x0a, 0x09, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, 0x97, 0x01, 0x0a, 0x0d, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x12, 0x1f, 0x0a, 0x0b, 0x63, 0x6f, 0x72, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x72, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3c, 0x0a, 0x1a, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6e, 0x65, 0x6b, 0x6f, 0x72, 0x61, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x4e, 0x65, 0x6b, 0x6f, 0x72, 0x61, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x5f, 0x6f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x74, 0x61, 0x74, 0x73, 0x4f, 0x75, 0x74, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x73, 0x22, 0xde, 0x03, 0x0a, 0x07, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x12, 0x25, 0x0a, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x04, 0x6d, 0x6f, 0x64, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2e, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x69, 0x6e, 0x62, 0x6f, 0x75, 0x6e, 0x64, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x6e, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x69, 0x6e, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x66, 0x75, 0x6c, 0x6c, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1d, 0x0a, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x73, 0x70, 0x65, 0x65, 0x64, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x70, 0x65, 0x65, 0x64, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x73, 0x70, 0x65, 0x65, 0x64, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x70, 0x65, 0x65, 0x64, 0x55, 0x72, 0x6c, 0x12, 0x2c, 0x0a, 0x12, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x73, 0x70, 0x65, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x05, 0x52, 0x10, 0x66, 0x75, 0x6c, 0x6c, 0x53, 0x70, 0x65, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x1e, 0x0a, 0x0b, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x69, 0x6e, 0x5f, 0x6f, 0x75, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x66, 0x75, 0x6c, 0x6c, 0x49, 0x6e, 0x4f, 0x75, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x75, 0x64, 0x70, 0x5f, 0x6c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x66, 0x75, 0x6c, 0x6c, 0x55, 0x64, 0x70, 0x4c, 0x61, 0x74, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x1d, 0x0a, 0x08, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x6e, 0x61, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x42, 0x02, 0x18, 0x01, 0x52, 0x07, 0x66, 0x75, 0x6c, 0x6c, 0x4e, 0x61, 0x74, 0x22, 0x51, 0x0a, 0x08, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x02, 0x6d, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x66, 0x75, 0x6c, 0x6c, 0x5f, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x66, 0x75, 0x6c, 0x6c, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x39, 0x0a, 0x0d, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x22, 0x2a, 0x0a, 0x0e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x07, 0x74, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x22, 0x66, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x12, 0x2d, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x15, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x0a, 0x11, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x70, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x50, 0x72, 0x65, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x22, 0xd0, 0x01, 0x0a, 0x0a, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1f, 0x0a, 0x0b, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x61, 0x73, 0x73, 0x65, 0x74, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x72, 0x6c, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x21, 0x0a, 0x0c, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x6e, 0x6f, 0x74, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x4e, 0x6f, 0x74, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x69, 0x73, 0x5f, 0x70, 0x72, 0x65, 0x5f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x69, 0x73, 0x50, 0x72, 0x65, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x22, 0x4f, 0x0a, 0x13, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x12, 0x38, 0x0a, 0x18, 0x6e, 0x65, 0x6b, 0x6f, 0x72, 0x61, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x6e, 0x65, 0x6b, 0x6f, 0x72, 0x61, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4a, 0x73, 0x6f, 0x6e, 0x2a, 0x32, 0x0a, 0x08, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x63, 0x70, 0x50, 0x69, 0x6e, 0x67, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x72, 0x6c, 0x54, 0x65, 0x73, 0x74, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x46, 0x75, 0x6c, 0x6c, 0x54, 0x65, 0x73, 0x74, 0x10, 0x02, 0x2a, 0x27, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x6f, 0x77, 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x10, 0x01, 0x32, 0x94, 0x03, 0x0a, 0x0e, 0x4c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x45, 0x78, 0x69, 0x74, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x33, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x13, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x35, 0x0a, 0x05, 0x53, 0x74, 0x61, 0x72, 0x74, 0x12, 0x16, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x6f, 0x61, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x2f, 0x0a, 0x04, 0x53, 0x74, 0x6f, 0x70, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x2d, 0x0a, 0x04, 0x54, 0x65, 0x73, 0x74, 0x12, 0x10, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x0a, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x12, 0x16, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x17, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x51, 0x75, 0x65, 0x72, 0x79, 0x53, 0x74, 0x61, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1c, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x42, 0x11, 0x5a, 0x0f, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( file_libcore_proto_rawDescOnce sync.Once file_libcore_proto_rawDescData = file_libcore_proto_rawDesc ) func file_libcore_proto_rawDescGZIP() []byte { file_libcore_proto_rawDescOnce.Do(func() { file_libcore_proto_rawDescData = protoimpl.X.CompressGZIP(file_libcore_proto_rawDescData) }) return file_libcore_proto_rawDescData } var file_libcore_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_libcore_proto_msgTypes = make([]protoimpl.MessageInfo, 11) var file_libcore_proto_goTypes = []interface{}{ (TestMode)(0), // 0: libcore.TestMode (UpdateAction)(0), // 1: libcore.UpdateAction (*EmptyReq)(nil), // 2: libcore.EmptyReq (*EmptyResp)(nil), // 3: libcore.EmptyResp (*ErrorResp)(nil), // 4: libcore.ErrorResp (*LoadConfigReq)(nil), // 5: libcore.LoadConfigReq (*TestReq)(nil), // 6: libcore.TestReq (*TestResp)(nil), // 7: libcore.TestResp (*QueryStatsReq)(nil), // 8: libcore.QueryStatsReq (*QueryStatsResp)(nil), // 9: libcore.QueryStatsResp (*UpdateReq)(nil), // 10: libcore.UpdateReq (*UpdateResp)(nil), // 11: libcore.UpdateResp (*ListConnectionsResp)(nil), // 12: libcore.ListConnectionsResp } var file_libcore_proto_depIdxs = []int32{ 0, // 0: libcore.TestReq.mode:type_name -> libcore.TestMode 5, // 1: libcore.TestReq.config:type_name -> libcore.LoadConfigReq 1, // 2: libcore.UpdateReq.action:type_name -> libcore.UpdateAction 2, // 3: libcore.LibcoreService.Exit:input_type -> libcore.EmptyReq 10, // 4: libcore.LibcoreService.Update:input_type -> libcore.UpdateReq 5, // 5: libcore.LibcoreService.Start:input_type -> libcore.LoadConfigReq 2, // 6: libcore.LibcoreService.Stop:input_type -> libcore.EmptyReq 6, // 7: libcore.LibcoreService.Test:input_type -> libcore.TestReq 8, // 8: libcore.LibcoreService.QueryStats:input_type -> libcore.QueryStatsReq 2, // 9: libcore.LibcoreService.ListConnections:input_type -> libcore.EmptyReq 3, // 10: libcore.LibcoreService.Exit:output_type -> libcore.EmptyResp 11, // 11: libcore.LibcoreService.Update:output_type -> libcore.UpdateResp 4, // 12: libcore.LibcoreService.Start:output_type -> libcore.ErrorResp 4, // 13: libcore.LibcoreService.Stop:output_type -> libcore.ErrorResp 7, // 14: libcore.LibcoreService.Test:output_type -> libcore.TestResp 9, // 15: libcore.LibcoreService.QueryStats:output_type -> libcore.QueryStatsResp 12, // 16: libcore.LibcoreService.ListConnections:output_type -> libcore.ListConnectionsResp 10, // [10:17] is the sub-list for method output_type 3, // [3:10] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name 3, // [3:3] is the sub-list for extension extendee 0, // [0:3] is the sub-list for field type_name } func init() { file_libcore_proto_init() } func file_libcore_proto_init() { if File_libcore_proto != nil { return } if !protoimpl.UnsafeEnabled { file_libcore_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*EmptyReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_libcore_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*EmptyResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_libcore_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ErrorResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_libcore_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*LoadConfigReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_libcore_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TestReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_libcore_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*TestResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_libcore_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*QueryStatsReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_libcore_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*QueryStatsResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_libcore_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UpdateReq); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_libcore_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*UpdateResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } file_libcore_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ListConnectionsResp); i { case 0: return &v.state case 1: return &v.sizeCache case 2: return &v.unknownFields default: return nil } } } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_libcore_proto_rawDesc, NumEnums: 2, NumMessages: 11, NumExtensions: 0, NumServices: 1, }, GoTypes: file_libcore_proto_goTypes, DependencyIndexes: file_libcore_proto_depIdxs, EnumInfos: file_libcore_proto_enumTypes, MessageInfos: file_libcore_proto_msgTypes, }.Build() File_libcore_proto = out.File file_libcore_proto_rawDesc = nil file_libcore_proto_goTypes = nil file_libcore_proto_depIdxs = nil } ================================================ FILE: go/grpc_server/gen/libcore.proto ================================================ syntax = "proto3"; package libcore; option go_package = "grpc_server/gen"; service LibcoreService { rpc Exit(EmptyReq) returns (EmptyResp) {} rpc Update(UpdateReq) returns (UpdateResp) {} // rpc Start(LoadConfigReq) returns (ErrorResp) {} rpc Stop(EmptyReq) returns (ErrorResp) {} rpc Test(TestReq) returns (TestResp) {} rpc QueryStats(QueryStatsReq) returns (QueryStatsResp) {} rpc ListConnections(EmptyReq) returns (ListConnectionsResp) {} } message EmptyReq {} message EmptyResp {} message ErrorResp { string error = 1; } message LoadConfigReq { string core_config = 1; bool enable_nekoray_connections = 2; repeated string stats_outbounds = 3; } enum TestMode { TcpPing = 0; UrlTest = 1; FullTest = 2; } message TestReq { TestMode mode = 1; int32 timeout = 6; // TcpPing string address = 2; // UrlTest LoadConfigReq config = 3; string inbound = 4; string url = 5; // FullTest string in_address = 7; bool full_latency = 8; bool full_speed = 9; string full_speed_url = 13; int32 full_speed_timeout = 14; bool full_in_out = 10; bool full_udp_latency = 12; // bool full_nat = 11 [deprecated = true]; } message TestResp { string error = 1; int32 ms = 2; string full_report = 3; } message QueryStatsReq{ string tag = 1; string direct = 2; } message QueryStatsResp{ int64 traffic = 1; } enum UpdateAction { Check = 0; Download = 1; } message UpdateReq { UpdateAction action = 1; bool check_pre_release = 2; } message UpdateResp { string error = 1; string assets_name = 2; string download_url = 3; string release_url = 4; string release_note = 5; bool is_pre_release = 6; } message ListConnectionsResp { string nekoray_connections_json = 1; } ================================================ FILE: go/grpc_server/gen/libcore_grpc.pb.go ================================================ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.2.0 // - protoc v4.23.3 // source: libcore.proto package gen import ( context "context" grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" ) // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. // Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion7 // LibcoreServiceClient is the client API for LibcoreService service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type LibcoreServiceClient interface { Exit(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*EmptyResp, error) Update(ctx context.Context, in *UpdateReq, opts ...grpc.CallOption) (*UpdateResp, error) Start(ctx context.Context, in *LoadConfigReq, opts ...grpc.CallOption) (*ErrorResp, error) Stop(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ErrorResp, error) Test(ctx context.Context, in *TestReq, opts ...grpc.CallOption) (*TestResp, error) QueryStats(ctx context.Context, in *QueryStatsReq, opts ...grpc.CallOption) (*QueryStatsResp, error) ListConnections(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ListConnectionsResp, error) } type libcoreServiceClient struct { cc grpc.ClientConnInterface } func NewLibcoreServiceClient(cc grpc.ClientConnInterface) LibcoreServiceClient { return &libcoreServiceClient{cc} } func (c *libcoreServiceClient) Exit(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*EmptyResp, error) { out := new(EmptyResp) err := c.cc.Invoke(ctx, "/libcore.LibcoreService/Exit", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *libcoreServiceClient) Update(ctx context.Context, in *UpdateReq, opts ...grpc.CallOption) (*UpdateResp, error) { out := new(UpdateResp) err := c.cc.Invoke(ctx, "/libcore.LibcoreService/Update", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *libcoreServiceClient) Start(ctx context.Context, in *LoadConfigReq, opts ...grpc.CallOption) (*ErrorResp, error) { out := new(ErrorResp) err := c.cc.Invoke(ctx, "/libcore.LibcoreService/Start", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *libcoreServiceClient) Stop(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ErrorResp, error) { out := new(ErrorResp) err := c.cc.Invoke(ctx, "/libcore.LibcoreService/Stop", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *libcoreServiceClient) Test(ctx context.Context, in *TestReq, opts ...grpc.CallOption) (*TestResp, error) { out := new(TestResp) err := c.cc.Invoke(ctx, "/libcore.LibcoreService/Test", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *libcoreServiceClient) QueryStats(ctx context.Context, in *QueryStatsReq, opts ...grpc.CallOption) (*QueryStatsResp, error) { out := new(QueryStatsResp) err := c.cc.Invoke(ctx, "/libcore.LibcoreService/QueryStats", in, out, opts...) if err != nil { return nil, err } return out, nil } func (c *libcoreServiceClient) ListConnections(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ListConnectionsResp, error) { out := new(ListConnectionsResp) err := c.cc.Invoke(ctx, "/libcore.LibcoreService/ListConnections", in, out, opts...) if err != nil { return nil, err } return out, nil } // LibcoreServiceServer is the server API for LibcoreService service. // All implementations must embed UnimplementedLibcoreServiceServer // for forward compatibility type LibcoreServiceServer interface { Exit(context.Context, *EmptyReq) (*EmptyResp, error) Update(context.Context, *UpdateReq) (*UpdateResp, error) Start(context.Context, *LoadConfigReq) (*ErrorResp, error) Stop(context.Context, *EmptyReq) (*ErrorResp, error) Test(context.Context, *TestReq) (*TestResp, error) QueryStats(context.Context, *QueryStatsReq) (*QueryStatsResp, error) ListConnections(context.Context, *EmptyReq) (*ListConnectionsResp, error) mustEmbedUnimplementedLibcoreServiceServer() } // UnimplementedLibcoreServiceServer must be embedded to have forward compatible implementations. type UnimplementedLibcoreServiceServer struct { } func (UnimplementedLibcoreServiceServer) Exit(context.Context, *EmptyReq) (*EmptyResp, error) { return nil, status.Errorf(codes.Unimplemented, "method Exit not implemented") } func (UnimplementedLibcoreServiceServer) Update(context.Context, *UpdateReq) (*UpdateResp, error) { return nil, status.Errorf(codes.Unimplemented, "method Update not implemented") } func (UnimplementedLibcoreServiceServer) Start(context.Context, *LoadConfigReq) (*ErrorResp, error) { return nil, status.Errorf(codes.Unimplemented, "method Start not implemented") } func (UnimplementedLibcoreServiceServer) Stop(context.Context, *EmptyReq) (*ErrorResp, error) { return nil, status.Errorf(codes.Unimplemented, "method Stop not implemented") } func (UnimplementedLibcoreServiceServer) Test(context.Context, *TestReq) (*TestResp, error) { return nil, status.Errorf(codes.Unimplemented, "method Test not implemented") } func (UnimplementedLibcoreServiceServer) QueryStats(context.Context, *QueryStatsReq) (*QueryStatsResp, error) { return nil, status.Errorf(codes.Unimplemented, "method QueryStats not implemented") } func (UnimplementedLibcoreServiceServer) ListConnections(context.Context, *EmptyReq) (*ListConnectionsResp, error) { return nil, status.Errorf(codes.Unimplemented, "method ListConnections not implemented") } func (UnimplementedLibcoreServiceServer) mustEmbedUnimplementedLibcoreServiceServer() {} // UnsafeLibcoreServiceServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to LibcoreServiceServer will // result in compilation errors. type UnsafeLibcoreServiceServer interface { mustEmbedUnimplementedLibcoreServiceServer() } func RegisterLibcoreServiceServer(s grpc.ServiceRegistrar, srv LibcoreServiceServer) { s.RegisterService(&LibcoreService_ServiceDesc, srv) } func _LibcoreService_Exit_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(EmptyReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(LibcoreServiceServer).Exit(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/libcore.LibcoreService/Exit", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(LibcoreServiceServer).Exit(ctx, req.(*EmptyReq)) } return interceptor(ctx, in, info, handler) } func _LibcoreService_Update_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(UpdateReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(LibcoreServiceServer).Update(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/libcore.LibcoreService/Update", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(LibcoreServiceServer).Update(ctx, req.(*UpdateReq)) } return interceptor(ctx, in, info, handler) } func _LibcoreService_Start_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(LoadConfigReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(LibcoreServiceServer).Start(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/libcore.LibcoreService/Start", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(LibcoreServiceServer).Start(ctx, req.(*LoadConfigReq)) } return interceptor(ctx, in, info, handler) } func _LibcoreService_Stop_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(EmptyReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(LibcoreServiceServer).Stop(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/libcore.LibcoreService/Stop", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(LibcoreServiceServer).Stop(ctx, req.(*EmptyReq)) } return interceptor(ctx, in, info, handler) } func _LibcoreService_Test_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(TestReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(LibcoreServiceServer).Test(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/libcore.LibcoreService/Test", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(LibcoreServiceServer).Test(ctx, req.(*TestReq)) } return interceptor(ctx, in, info, handler) } func _LibcoreService_QueryStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(QueryStatsReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(LibcoreServiceServer).QueryStats(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/libcore.LibcoreService/QueryStats", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(LibcoreServiceServer).QueryStats(ctx, req.(*QueryStatsReq)) } return interceptor(ctx, in, info, handler) } func _LibcoreService_ListConnections_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(EmptyReq) if err := dec(in); err != nil { return nil, err } if interceptor == nil { return srv.(LibcoreServiceServer).ListConnections(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, FullMethod: "/libcore.LibcoreService/ListConnections", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { return srv.(LibcoreServiceServer).ListConnections(ctx, req.(*EmptyReq)) } return interceptor(ctx, in, info, handler) } // LibcoreService_ServiceDesc is the grpc.ServiceDesc for LibcoreService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var LibcoreService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "libcore.LibcoreService", HandlerType: (*LibcoreServiceServer)(nil), Methods: []grpc.MethodDesc{ { MethodName: "Exit", Handler: _LibcoreService_Exit_Handler, }, { MethodName: "Update", Handler: _LibcoreService_Update_Handler, }, { MethodName: "Start", Handler: _LibcoreService_Start_Handler, }, { MethodName: "Stop", Handler: _LibcoreService_Stop_Handler, }, { MethodName: "Test", Handler: _LibcoreService_Test_Handler, }, { MethodName: "QueryStats", Handler: _LibcoreService_QueryStats_Handler, }, { MethodName: "ListConnections", Handler: _LibcoreService_ListConnections_Handler, }, }, Streams: []grpc.StreamDesc{}, Metadata: "libcore.proto", } ================================================ FILE: go/grpc_server/gen/update_proto.sh ================================================ protoc -I . --go_out=. --go_opt paths=source_relative --go-grpc_out=. --go-grpc_opt paths=source_relative libcore.proto # protoc -I . --cpp_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` libcore.proto ================================================ FILE: go/grpc_server/go.mod ================================================ module grpc_server go 1.19 require ( github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 github.com/matsuridayo/libneko v1.0.0 // replaced google.golang.org/grpc v1.49.0 google.golang.org/protobuf v1.28.1 ) require ( github.com/golang/protobuf v1.5.2 // indirect github.com/google/go-cmp v0.5.8 // indirect golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb // indirect ) replace github.com/matsuridayo/libneko v1.0.0 => ../../../libneko ================================================ FILE: go/grpc_server/go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb h1:ZrsicilzPCS/Xr8qtBZZLpy4P9TYXAfl49ctG1/5tgw= google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: go/grpc_server/grpc.go ================================================ package grpc_server import ( "bufio" "context" "flag" "fmt" "grpc_server/auth" "grpc_server/gen" "log" "net" "os" "runtime" "strconv" "strings" "syscall" "time" "github.com/matsuridayo/libneko/neko_common" grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth" "google.golang.org/grpc" ) type BaseServer struct { gen.LibcoreServiceServer } func (s *BaseServer) Exit(ctx context.Context, in *gen.EmptyReq) (out *gen.EmptyResp, _ error) { out = &gen.EmptyResp{} // Connection closed os.Exit(0) return } func RunCore(setupCore func(), server gen.LibcoreServiceServer) { _token := flag.String("token", "", "") _port := flag.Int("port", 19810, "") _debug := flag.Bool("debug", false, "") flag.CommandLine.Parse(os.Args[2:]) neko_common.Debug = *_debug go func() { parent, err := os.FindProcess(os.Getppid()) if err != nil { log.Fatalln("find parent:", err) } if runtime.GOOS == "windows" { state, err := parent.Wait() log.Fatalln("parent exited:", state, err) } else { for { time.Sleep(time.Second * 10) err = parent.Signal(syscall.Signal(0)) if err != nil { log.Fatalln("parent exited:", err) } } } }() // Libcore setupCore() // GRPC lis, err := net.Listen("tcp", "127.0.0.1:"+strconv.Itoa(*_port)) if err != nil { log.Fatalf("failed to listen: %v", err) } token := *_token if token == "" { os.Stderr.WriteString("Please set a token: ") s := bufio.NewScanner(os.Stdin) if s.Scan() { token = strings.TrimSpace(s.Text()) } } if token == "" { fmt.Println("You must set a token") os.Exit(0) } os.Stderr.WriteString("token is set\n") auther := auth.Authenticator{ Token: token, } s := grpc.NewServer( grpc.StreamInterceptor(grpc_auth.StreamServerInterceptor(auther.Authenticate)), grpc.UnaryInterceptor(grpc_auth.UnaryServerInterceptor(auther.Authenticate)), ) gen.RegisterLibcoreServiceServer(s, server) name := "nekobox_core" log.Printf("%s grpc server listening at %v\n", name, lis.Addr()) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } } ================================================ FILE: go/grpc_server/update.go ================================================ package grpc_server import ( "context" "encoding/json" "grpc_server/gen" "io" "net/http" "os" "runtime" "strings" "time" "github.com/matsuridayo/libneko/neko_common" ) var update_download_url string func (s *BaseServer) Update(ctx context.Context, in *gen.UpdateReq) (*gen.UpdateResp, error) { ret := &gen.UpdateResp{} client := neko_common.CreateProxyHttpClient(neko_common.GetCurrentInstance()) if in.Action == gen.UpdateAction_Check { // Check update ctx, cancel := context.WithTimeout(ctx, time.Second*10) defer cancel() req, _ := http.NewRequestWithContext(ctx, "GET", "https://api.github.com/repos/MatsuriDayo/nekoray/releases", nil) resp, err := client.Do(req) if err != nil { ret.Error = err.Error() return ret, nil } defer resp.Body.Close() v := []struct { HtmlUrl string `json:"html_url"` Assets []struct { Name string `json:"name"` BrowserDownloadUrl string `json:"browser_download_url"` } `json:"assets"` Prerelease bool `json:"prerelease"` Body string `json:"body"` }{} err = json.NewDecoder(resp.Body).Decode(&v) if err != nil { ret.Error = err.Error() return ret, nil } nowVer := strings.TrimLeft(neko_common.Version_neko, "nekoray-") var search string if runtime.GOOS == "windows" && runtime.GOARCH == "amd64" { search = "windows64" } else if runtime.GOOS == "linux" && runtime.GOARCH == "amd64" { search = "linux64" } else if runtime.GOOS == "darwin" { search = "macos-" + runtime.GOARCH } else { ret.Error = "Not official support platform" return ret, nil } for _, release := range v { if len(release.Assets) > 0 { for _, asset := range release.Assets { if strings.Contains(asset.Name, nowVer) { return ret, nil // No update } if strings.Contains(asset.Name, search) { if release.Prerelease && !in.CheckPreRelease { continue } update_download_url = asset.BrowserDownloadUrl ret.AssetsName = asset.Name ret.DownloadUrl = asset.BrowserDownloadUrl ret.ReleaseUrl = release.HtmlUrl ret.ReleaseNote = release.Body ret.IsPreRelease = release.Prerelease return ret, nil // update } } } } } else { // Download update if update_download_url == "" { ret.Error = "?" return ret, nil } req, _ := http.NewRequestWithContext(ctx, "GET", update_download_url, nil) resp, err := client.Do(req) if err != nil { ret.Error = err.Error() return ret, nil } defer resp.Body.Close() f, err := os.OpenFile("../nekoray.zip", os.O_TRUNC|os.O_CREATE|os.O_RDWR, 0644) if err != nil { ret.Error = err.Error() return ret, nil } defer f.Close() _, err = io.Copy(f, resp.Body) if err != nil { ret.Error = err.Error() return ret, nil } f.Sync() } return ret, nil } ================================================ FILE: libs/.gitignore ================================================ /deps* downloaded ================================================ FILE: libs/build_deps_all.sh ================================================ #!/bin/bash set -e cd libs # 参数 if [ -z $cmake ]; then cmake="cmake" fi if [ -z $deps ]; then deps="deps" fi # libs/deps/... mkdir -p $deps cd $deps if [ -z $NKR_PACKAGE ]; then INSTALL_PREFIX=$PWD/built else INSTALL_PREFIX=$PWD/package fi rm -rf $INSTALL_PREFIX mkdir -p $INSTALL_PREFIX #### clean #### clean() { rm -rf dl.zip yaml-* zxing-* protobuf } #### ZXing v2.0.0 #### curl -L -o dl.zip https://github.com/nu-book/zxing-cpp/archive/refs/tags/v2.0.0.zip unzip dl.zip cd zxing-* mkdir -p build cd build $cmake .. -GNinja -DBUILD_SHARED_LIBS=OFF -DCMAKE_BUILD_TYPE=Release -DBUILD_EXAMPLES=OFF -DBUILD_BLACKBOX_TESTS=OFF -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX ninja && ninja install cd ../.. #### yaml-cpp #### curl -L -o dl.zip https://github.com/jbeder/yaml-cpp/archive/refs/tags/yaml-cpp-0.7.0.zip unzip dl.zip cd yaml-* mkdir -p build cd build $cmake .. -GNinja -DBUILD_SHARED_LIBS=OFF -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX ninja && ninja install cd ../.. #### protobuf #### git clone --recurse-submodules -b v21.4 --depth 1 --shallow-submodules https://github.com/protocolbuffers/protobuf #备注:交叉编译要在 host 也安装 protobuf 并且版本一致,编译安装,同参数,安装到 /usr/local mkdir -p protobuf/build cd protobuf/build $cmake .. -GNinja \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_SHARED_LIBS=OFF \ -Dprotobuf_MSVC_STATIC_RUNTIME=OFF \ -Dprotobuf_BUILD_TESTS=OFF \ -DCMAKE_INSTALL_PREFIX=$INSTALL_PREFIX ninja && ninja install cd ../.. #### clean ================================================ FILE: libs/build_go.sh ================================================ #!/bin/bash set -e source libs/env_deploy.sh [ "$GOOS" == "windows" ] && [ "$GOARCH" == "amd64" ] && DEST=$DEPLOYMENT/windows64 || true [ "$GOOS" == "windows" ] && [ "$GOARCH" == "arm64" ] && DEST=$DEPLOYMENT/windows-arm64 || true [ "$GOOS" == "linux" ] && [ "$GOARCH" == "amd64" ] && DEST=$DEPLOYMENT/linux64 || true [ "$GOOS" == "linux" ] && [ "$GOARCH" == "arm64" ] && DEST=$DEPLOYMENT/linux-arm64 || true if [ -z $DEST ]; then echo "Please set GOOS GOARCH" exit 1 fi rm -rf $DEST mkdir -p $DEST export CGO_ENABLED=0 #### Go: updater #### pushd go/cmd/updater [ "$GOOS" == "darwin" ] || go build -o $DEST -trimpath -ldflags "-w -s" [ "$GOOS" == "linux" ] && mv $DEST/updater $DEST/launcher || true popd #### Go: nekobox_core #### pushd go/cmd/nekobox_core go build -v -o $DEST -trimpath -ldflags "-w -s -X github.com/matsuridayo/libneko/neko_common.Version_neko=$version_standalone" -tags "with_clash_api,with_gvisor,with_quic,with_wireguard,with_utls,with_ech" popd ================================================ FILE: libs/build_public_res.sh ================================================ #!/bin/bash set -e source libs/env_deploy.sh DEST=$DEPLOYMENT/public_res rm -rf $DEST mkdir -p $DEST #### Download geodata #### curl -fLso $DEST/geoip.dat "https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat" curl -fLso $DEST/geosite.dat "https://github.com/v2fly/domain-list-community/releases/latest/download/dlc.dat" curl -fLso $DEST/geoip.db "https://github.com/SagerNet/sing-geoip/releases/latest/download/geoip.db" curl -fLso $DEST/geosite.db "https://github.com/SagerNet/sing-geosite/releases/latest/download/geosite.db" #### copy res/public #### cp res/public/* $DEST ================================================ FILE: libs/deploy_linux64.sh ================================================ #!/bin/bash set -e source libs/env_deploy.sh DEST=$DEPLOYMENT/linux64 rm -rf $DEST mkdir -p $DEST #### copy binary #### cp $BUILD/nekobox $DEST #### Download: prebuilt runtime #### curl -Lso usr.zip https://github.com/MatsuriDayo/nekoray_qt_runtime/releases/download/20220503/20230202-5.12.8-ubuntu20.04-linux64.zip unzip usr.zip mv usr $DEST #### copy so #### # 5.11 looks buggy on new systems... exit USR_LIB=/usr/lib/x86_64-linux-gnu mkdir usr pushd usr mkdir lib pushd lib cp $USR_LIB/libQt5Core.so.5 . cp $USR_LIB/libQt5DBus.so.5 . cp $USR_LIB/libQt5Gui.so.5 . cp $USR_LIB/libQt5Network.so.5 . cp $USR_LIB/libQt5Svg.so.5 . cp $USR_LIB/libQt5Widgets.so.5 . cp $USR_LIB/libQt5X11Extras.so.5 . cp $USR_LIB/libQt5XcbQpa.so.5 . cp $USR_LIB/libdouble-conversion.so.? . cp $USR_LIB/libxcb-util.so.? . cp $USR_LIB/libicuuc.so.?? . cp $USR_LIB/libicui18n.so.?? . cp $USR_LIB/libicudata.so.?? . popd mkdir plugins pushd plugins cp -r $USR_LIB/qt5/plugins/bearer . cp -r $USR_LIB/qt5/plugins/iconengines . cp -r $USR_LIB/qt5/plugins/imageformats . cp -r $USR_LIB/qt5/plugins/platforminputcontexts . cp -r $USR_LIB/qt5/plugins/platforms . cp -r $USR_LIB/qt5/plugins/xcbglintegrations . popd popd mv usr $DEST ================================================ FILE: libs/deploy_windows64.sh ================================================ #!/bin/bash set -e source libs/env_deploy.sh DEST=$DEPLOYMENT/windows64 rm -rf $DEST mkdir -p $DEST #### copy exe #### cp $BUILD/nekobox.exe $DEST #### deploy qt & DLL runtime #### pushd $DEST windeployqt nekobox.exe --no-compiler-runtime --no-system-d3d-compiler --no-opengl-sw --verbose 2 rm -rf translations rm -rf libEGL.dll libGLESv2.dll Qt6Pdf.dll if [ "$DL_QT_VER" != "5.15" ]; then cp $SRC_ROOT/qtsdk/Qt/bin/libcrypto-3-x64.dll . cp $SRC_ROOT/qtsdk/Qt/bin/libssl-3-x64.dll . fi popd #### prepare deployment #### cp $BUILD/*.pdb $DEPLOYMENT ================================================ FILE: libs/download_qtsdk_win.sh ================================================ mkdir qtsdk cd qtsdk if [ "$DL_QT_VER" == "5.15" ]; then curl -LSO https://github.com/MatsuriDayo/nekoray_qt_runtime/releases/download/20220503/Qt5.15.7-Windows-x86_64-VS2019-16.11.20-20221103.7z else curl -LSO https://github.com/MatsuriDayo/nekoray_qt_runtime/releases/download/20220503/Qt6.7.2-Windows-x86_64-VS2022-17.10.3-20240621.7z fi 7z x *.7z rm *.7z mv Qt* Qt ================================================ FILE: libs/env_deploy.sh ================================================ SRC_ROOT="$PWD" DEPLOYMENT="$SRC_ROOT/deployment" BUILD="$SRC_ROOT/build" version_standalone="nekoray-"$(cat nekoray_version.txt) # 下次改 ================================================ FILE: libs/env_qtsdk.sh ================================================ echo "Setting Qt Sdk Dir to" "$1" export Qt5_DIR="$1" export Qt6_DIR=$Qt5_DIR export PATH=$PATH:$Qt5_DIR/bin export LD_LIBRARY_PATH=$Qt5_DIR/lib export PKG_CONFIG_PATH=$Qt5_DIR/lib/pkgconfig export QT_PLUGIN_PATH=$Qt5_DIR/plugins export QML2_IMPORT_PATH=$Qt5_DIR/lib/qml ================================================ FILE: libs/format_cpp.sh ================================================ #!/bin/sh git ls-files | grep -E "\.cpp|\.h" | grep -v "3rdparty" | xargs -n1 clang-format -i ================================================ FILE: libs/get_source.sh ================================================ #!/bin/bash set -e source libs/env_deploy.sh ENV_NEKORAY=1 source libs/get_source_env.sh pushd .. #### if [ ! -d "sing-box" ]; then git clone --no-checkout https://github.com/MatsuriDayo/sing-box.git fi pushd sing-box git checkout "$COMMIT_SING_BOX" popd #### if [ ! -d "sing-quic" ]; then git clone --no-checkout https://github.com/MatsuriDayo/sing-quic.git fi pushd sing-quic git checkout "$COMMIT_SING_QUIC" popd #### if [ ! -d "libneko" ]; then git clone --no-checkout https://github.com/MatsuriDayo/libneko.git fi pushd libneko git checkout "$COMMIT_LIBNEKO" popd #### popd ================================================ FILE: libs/get_source_env.sh ================================================ export COMMIT_SING_BOX="06557f6cef23160668122a17a818b378b5a216b5" export COMMIT_SING_QUIC="b49ce60d9b3622d5238fee96bfd3c5f6e3915b42" export COMMIT_LIBNEKO="1c47a3af71990a7b2192e03292b4d246c308ef0b" ================================================ FILE: libs/package_appimage.sh ================================================ #!/bin/bash sudo apt-get install fuse -y cp -r linux64 nekobox.AppDir # The file for Appimage rm nekobox.AppDir/launcher cat >nekobox.AppDir/nekobox.desktop <<-EOF [Desktop Entry] Name=nekobox Exec=echo "nekobox started" Icon=nekobox Type=Application Categories=Network EOF cat >nekobox.AppDir/AppRun <<-EOF #!/bin/bash echo "PATH: \${PATH}" echo "nekobox runing on: \$APPDIR" LD_LIBRARY_PATH=\${APPDIR}/usr/lib QT_PLUGIN_PATH=\${APPDIR}/usr/plugins \${APPDIR}/nekobox -appdata "\$@" EOF chmod +x nekobox.AppDir/AppRun # build curl -fLSO https://github.com/AppImage/AppImageKit/releases/latest/download/appimagetool-x86_64.AppImage chmod +x appimagetool-x86_64.AppImage ./appimagetool-x86_64.AppImage nekobox.AppDir # clean rm appimagetool-x86_64.AppImage rm -rf nekobox.AppDir ================================================ FILE: libs/package_debian.sh ================================================ #!/bin/bash version="$1" mkdir -p nekoray/DEBIAN mkdir -p nekoray/opt cp -r linux64 nekoray/opt/ mv nekoray/opt/linux64 nekoray/opt/nekoray rm -rf nekoray/opt/nekoray/usr rm nekoray/opt/nekoray/launcher # basic cat >nekoray/DEBIAN/control <<-EOF Package: nekoray Version: $version Architecture: amd64 Maintainer: MatsuriDayo nekoha_matsuri@protonmail.com Depends: libxcb-xinerama0, libqt5core5a, libqt5gui5, libqt5network5, libqt5widgets5, libqt5svg5, libqt5x11extras5, desktop-file-utils Description: Qt based cross-platform GUI proxy configuration manager (backend: v2ray / sing-box) EOF cat >nekoray/DEBIAN/postinst <<-EOF if [ ! -s /usr/share/applications/nekoray.desktop ]; then cat >/usr/share/applications/nekoray.desktop<<-END [Desktop Entry] Name=nekoray Comment=Qt based cross-platform GUI proxy configuration manager (backend: sing-box) Exec=sh -c "PATH=/opt/nekoray:\$PATH /opt/nekoray/nekobox -appdata" Icon=/opt/nekoray/nekobox.png Terminal=false Type=Application Categories=Network;Application; END fi setcap cap_net_admin=ep /opt/nekoray/nekobox_core update-desktop-database EOF sudo chmod 0755 nekoray/DEBIAN/postinst # desktop && PATH sudo dpkg-deb -Zxz --build nekoray ================================================ FILE: main/Const.hpp ================================================ #pragma once namespace NekoGui { namespace DomainMatcher { enum DomainMatcher { DEFAULT, MPH, }; } namespace SniffingMode { enum SniffingMode { DISABLE, FOR_ROUTING, FOR_DESTINATION, }; } namespace CoreType { enum CoreType { V2RAY, // DO NOT USE SING_BOX, }; } } // namespace NekoGui ================================================ FILE: main/GuiUtils.hpp ================================================ #pragma once // Dialogs #define Dialog_DialogBasicSettings "DialogBasicSettings" #define Dialog_DialogEditProfile "DialogEditProfile" #define Dialog_DialogManageGroups "DialogManageGroups" #define Dialog_DialogManageRoutes "DialogManageRoutes" // Utils #define QRegExpValidator_Number new QRegularExpressionValidator(QRegularExpression("^[0-9]+$"), this) // NekoRay Save&Load #define P_C_LOAD_STRING(a) CACHE.a = bean->a; #define P_C_SAVE_STRING(a) bean->a = CACHE.a; #define D_C_LOAD_STRING(a) CACHE.a = NekoGui::dataStore->a; #define D_C_SAVE_STRING(a) NekoGui::dataStore->a = CACHE.a; #define P_LOAD_STRING(a) ui->a->setText(bean->a); #define P_LOAD_STRING_PLAIN(a) ui->a->setPlainText(bean->a); #define P_SAVE_STRING(a) bean->a = ui->a->text(); #define P_SAVE_STRING_PLAIN(a) bean->a = ui->a->toPlainText(); #define D_LOAD_STRING(a) ui->a->setText(NekoGui::dataStore->a); #define D_LOAD_STRING_PLAIN(a) ui->a->setPlainText(NekoGui::dataStore->a); #define D_SAVE_STRING(a) NekoGui::dataStore->a = ui->a->text(); #define D_SAVE_STRING_PLAIN(a) NekoGui::dataStore->a = ui->a->toPlainText(); #define P_LOAD_INT(a) \ ui->a->setText(Int2String(bean->a)); \ ui->a->setValidator(QRegExpValidator_Number); #define P_SAVE_INT(a) bean->a = ui->a->text().toInt(); #define D_LOAD_INT(a) \ ui->a->setText(Int2String(NekoGui::dataStore->a)); \ ui->a->setValidator(QRegExpValidator_Number); #define D_SAVE_INT(a) NekoGui::dataStore->a = ui->a->text().toInt(); #define P_LOAD_COMBO_STRING(a) ui->a->setCurrentText(bean->a); #define P_SAVE_COMBO_STRING(a) bean->a = ui->a->currentText(); #define D_LOAD_COMBO_STRING(a) ui->a->setCurrentText(NekoGui::dataStore->a); #define D_SAVE_COMBO_STRING(a) NekoGui::dataStore->a = ui->a->currentText(); #define P_LOAD_COMBO_INT(a) ui->a->setCurrentIndex(bean->a); #define P_SAVE_COMBO_INT(a) bean->a = ui->a->currentIndex(); #define D_LOAD_BOOL(a) ui->a->setChecked(NekoGui::dataStore->a); #define D_SAVE_BOOL(a) NekoGui::dataStore->a = ui->a->isChecked(); #define P_LOAD_BOOL(a) ui->a->setChecked(bean->a); #define P_SAVE_BOOL(a) bean->a = ui->a->isChecked(); #define D_LOAD_INT_ENABLE(i, e) \ if (NekoGui::dataStore->i > 0) { \ ui->e->setChecked(true); \ ui->i->setText(Int2String(NekoGui::dataStore->i)); \ } else { \ ui->e->setChecked(false); \ ui->i->setText(Int2String(-NekoGui::dataStore->i)); \ } \ ui->i->setValidator(QRegExpValidator_Number); #define D_SAVE_INT_ENABLE(i, e) \ if (ui->e->isChecked()) { \ NekoGui::dataStore->i = ui->i->text().toInt(); \ } else { \ NekoGui::dataStore->i = -ui->i->text().toInt(); \ } #define C_EDIT_JSON_ALLOW_EMPTY(a) \ auto editor = new JsonEditor(QString2QJsonObject(CACHE.a), this); \ auto result = editor->OpenEditor(); \ CACHE.a = QJsonObject2QString(result, true); \ if (result.isEmpty()) CACHE.a = ""; \ editor->deleteLater(); // #define ADD_ASTERISK(parent) \ for (auto label: parent->findChildren()) { \ auto text = label->text(); \ if (!label->toolTip().isEmpty() && !text.endsWith("*")) { \ label->setText(text + "*"); \ } \ } \ for (auto checkBox: parent->findChildren()) { \ auto text = checkBox->text(); \ if (!checkBox->toolTip().isEmpty() && !text.endsWith("*")) { \ checkBox->setText(text + "*"); \ } \ } ================================================ FILE: main/HTTPRequestHelper.cpp ================================================ #include "HTTPRequestHelper.hpp" #include #include #include #include #include #include "main/NekoGui.hpp" namespace NekoGui_network { NekoHTTPResponse NetworkRequestHelper::HttpGet(const QUrl &url) { QNetworkRequest request; QNetworkAccessManager accessManager; request.setUrl(url); // Set proxy if (NekoGui::dataStore->sub_use_proxy) { QNetworkProxy p; // Note: sing-box mixed socks5 protocol error p.setType(QNetworkProxy::HttpProxy); p.setHostName("127.0.0.1"); p.setPort(NekoGui::dataStore->inbound_socks_port); if (NekoGui::dataStore->inbound_auth->NeedAuth()) { p.setUser(NekoGui::dataStore->inbound_auth->username); p.setPassword(NekoGui::dataStore->inbound_auth->password); } accessManager.setProxy(p); if (NekoGui::dataStore->started_id < 0) { return NekoHTTPResponse{QObject::tr("Request with proxy but no profile started.")}; } } if (accessManager.proxy().type() == QNetworkProxy::Socks5Proxy) { auto cap = accessManager.proxy().capabilities(); accessManager.proxy().setCapabilities(cap | QNetworkProxy::HostNameLookupCapability); } // Set attribute #if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); #endif request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, NekoGui::dataStore->GetUserAgent()); if (NekoGui::dataStore->sub_insecure) { QSslConfiguration c; c.setPeerVerifyMode(QSslSocket::PeerVerifyMode::VerifyNone); request.setSslConfiguration(c); } // auto _reply = accessManager.get(request); connect(_reply, &QNetworkReply::sslErrors, _reply, [](const QList &errors) { QStringList error_str; for (const auto &err: errors) { error_str << err.errorString(); } MW_show_log(QStringLiteral("SSL Errors: %1 %2").arg(error_str.join(","), NekoGui::dataStore->sub_insecure ? "(Ignored)" : "")); }); // Wait for response auto abortTimer = new QTimer; abortTimer->setSingleShot(true); abortTimer->setInterval(10000); QObject::connect(abortTimer, &QTimer::timeout, _reply, &QNetworkReply::abort); abortTimer->start(); { QEventLoop loop; QObject::connect(_reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); loop.exec(); } if (abortTimer != nullptr) { abortTimer->stop(); abortTimer->deleteLater(); } // auto result = NekoHTTPResponse{_reply->error() == QNetworkReply::NetworkError::NoError ? "" : _reply->errorString(), _reply->readAll(), _reply->rawHeaderPairs()}; _reply->deleteLater(); return result; } QString NetworkRequestHelper::GetHeader(const QList> &header, const QString &name) { for (const auto &p: header) { if (QString(p.first).toLower() == name.toLower()) return p.second; } return ""; } } // namespace NekoGui_network ================================================ FILE: main/HTTPRequestHelper.hpp ================================================ #pragma once #include #include #include #include #include namespace NekoGui_network { struct NekoHTTPResponse { QString error; QByteArray data; QList> header; }; class NetworkRequestHelper : QObject { Q_OBJECT explicit NetworkRequestHelper(QObject *parent) : QObject(parent){}; ~NetworkRequestHelper() override = default; ; public: static NekoHTTPResponse HttpGet(const QUrl &url); static QString GetHeader(const QList> &header, const QString &name); }; } // namespace NekoGui_network using namespace NekoGui_network; ================================================ FILE: main/NekoGui.cpp ================================================ #include "NekoGui.hpp" #include "fmt/Preset.hpp" #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include "sys/windows/guihelper.h" #else #ifdef Q_OS_LINUX #include #endif #include #endif namespace NekoGui_ConfigItem { // 添加关联 void JsonStore::_add(configItem *item) { _map.insert(item->name, std::shared_ptr(item)); } QString JsonStore::_name(void *p) { for (const auto &_item: _map) { if (_item->ptr == p) return _item->name; } return {}; } std::shared_ptr JsonStore::_get(const QString &name) { // 直接 [] 会设置一个 nullptr ,所以先判断是否存在 if (_map.contains(name)) { return _map[name]; } return nullptr; } void JsonStore::_setValue(const QString &name, void *p) { auto item = _get(name); if (item == nullptr) return; switch (item->type) { case itemType::string: *(QString *) item->ptr = *(QString *) p; break; case itemType::boolean: *(bool *) item->ptr = *(bool *) p; break; case itemType::integer: *(int *) item->ptr = *(int *) p; break; case itemType::integer64: *(long long *) item->ptr = *(long long *) p; break; // others... case stringList: case integerList: case jsonStore: break; } } QJsonObject JsonStore::ToJson(const QStringList &without) { QJsonObject object; for (const auto &_item: _map) { auto item = _item.get(); if (without.contains(item->name)) continue; switch (item->type) { case itemType::string: // Allow Empty if (!((QString *) item->ptr)->isEmpty()) { object.insert(item->name, *(QString *) item->ptr); } break; case itemType::integer: object.insert(item->name, *(int *) item->ptr); break; case itemType::integer64: object.insert(item->name, *(long long *) item->ptr); break; case itemType::boolean: object.insert(item->name, *(bool *) item->ptr); break; case itemType::stringList: object.insert(item->name, QList2QJsonArray(*(QList *) item->ptr)); break; case itemType::integerList: object.insert(item->name, QList2QJsonArray(*(QList *) item->ptr)); break; case itemType::jsonStore: // _add 时应关联对应 JsonStore 的指针 object.insert(item->name, ((JsonStore *) item->ptr)->ToJson()); break; } } return object; } QByteArray JsonStore::ToJsonBytes() { QJsonDocument document; document.setObject(ToJson()); return document.toJson(save_control_compact ? QJsonDocument::Compact : QJsonDocument::Indented); } void JsonStore::FromJson(QJsonObject object) { for (const auto &key: object.keys()) { if (_map.count(key) == 0) { continue; } auto value = object[key]; auto item = _map[key].get(); if (item == nullptr) continue; // 故意忽略 // 根据类型修改ptr的内容 switch (item->type) { case itemType::string: if (value.type() != QJsonValue::String) { continue; } *(QString *) item->ptr = value.toString(); break; case itemType::integer: if (value.type() != QJsonValue::Double) { continue; } *(int *) item->ptr = value.toInt(); break; case itemType::integer64: if (value.type() != QJsonValue::Double) { continue; } *(long long *) item->ptr = value.toDouble(); break; case itemType::boolean: if (value.type() != QJsonValue::Bool) { continue; } *(bool *) item->ptr = value.toBool(); break; case itemType::stringList: if (value.type() != QJsonValue::Array) { continue; } *(QList *) item->ptr = QJsonArray2QListString(value.toArray()); break; case itemType::integerList: if (value.type() != QJsonValue::Array) { continue; } *(QList *) item->ptr = QJsonArray2QListInt(value.toArray()); break; case itemType::jsonStore: if (value.type() != QJsonValue::Object) { continue; } ((JsonStore *) item->ptr)->FromJson(value.toObject()); break; } } if (callback_after_load != nullptr) callback_after_load(); } void JsonStore::FromJsonBytes(const QByteArray &data) { QJsonParseError error{}; auto document = QJsonDocument::fromJson(data, &error); if (error.error != error.NoError) { qDebug() << "QJsonParseError" << error.errorString(); return; } FromJson(document.object()); } bool JsonStore::Save() { if (callback_before_save != nullptr) callback_before_save(); if (save_control_no_save) return false; auto save_content = ToJsonBytes(); auto changed = last_save_content != save_content; last_save_content = save_content; QFile file; file.setFileName(fn); file.open(QIODevice::ReadWrite | QIODevice::Truncate); file.write(save_content); file.close(); return changed; } bool JsonStore::Load() { QFile file; file.setFileName(fn); if (!file.exists() && !load_control_must) { return false; } bool ok = file.open(QIODevice::ReadOnly); if (!ok) { MessageBoxWarning("error", "can not open config " + fn + "\n" + file.errorString()); } else { last_save_content = file.readAll(); FromJsonBytes(last_save_content); } file.close(); return ok; } } // namespace NekoGui_ConfigItem namespace NekoGui { DataStore *dataStore = new DataStore(); // datastore DataStore::DataStore() : JsonStore() { _add(new configItem("extraCore", dynamic_cast(extraCore), itemType::jsonStore)); _add(new configItem("inbound_auth", dynamic_cast(inbound_auth), itemType::jsonStore)); _add(new configItem("user_agent2", &user_agent, itemType::string)); _add(new configItem("test_url", &test_latency_url, itemType::string)); _add(new configItem("test_url_dl", &test_download_url, itemType::string)); _add(new configItem("test_dl_timeout", &test_download_timeout, itemType::integer)); _add(new configItem("current_group", ¤t_group, itemType::integer)); _add(new configItem("inbound_address", &inbound_address, itemType::string)); _add(new configItem("inbound_socks_port", &inbound_socks_port, itemType::integer)); _add(new configItem("log_level", &log_level, itemType::string)); _add(new configItem("mux_protocol", &mux_protocol, itemType::string)); _add(new configItem("mux_concurrency", &mux_concurrency, itemType::integer)); _add(new configItem("mux_padding", &mux_padding, itemType::boolean)); _add(new configItem("mux_default_on", &mux_default_on, itemType::boolean)); _add(new configItem("traffic_loop_interval", &traffic_loop_interval, itemType::integer)); _add(new configItem("test_concurrent", &test_concurrent, itemType::integer)); _add(new configItem("theme", &theme, itemType::string)); _add(new configItem("custom_inbound", &custom_inbound, itemType::string)); _add(new configItem("custom_route", &custom_route_global, itemType::string)); _add(new configItem("sub_use_proxy", &sub_use_proxy, itemType::boolean)); _add(new configItem("remember_id", &remember_id, itemType::integer)); _add(new configItem("remember_enable", &remember_enable, itemType::boolean)); _add(new configItem("language", &language, itemType::integer)); _add(new configItem("spmode2", &remember_spmode, itemType::stringList)); _add(new configItem("skip_cert", &skip_cert, itemType::boolean)); _add(new configItem("hk_mw", &hotkey_mainwindow, itemType::string)); _add(new configItem("hk_group", &hotkey_group, itemType::string)); _add(new configItem("hk_route", &hotkey_route, itemType::string)); _add(new configItem("hk_spmenu", &hotkey_system_proxy_menu, itemType::string)); _add(new configItem("fakedns", &fake_dns, itemType::boolean)); _add(new configItem("active_routing", &active_routing, itemType::string)); _add(new configItem("mw_size", &mw_size, itemType::string)); _add(new configItem("conn_stat", &connection_statistics, itemType::boolean)); _add(new configItem("vpn_impl", &vpn_implementation, itemType::integer)); _add(new configItem("vpn_mtu", &vpn_mtu, itemType::integer)); _add(new configItem("vpn_ipv6", &vpn_ipv6, itemType::boolean)); _add(new configItem("vpn_hide_console", &vpn_hide_console, itemType::boolean)); _add(new configItem("vpn_strict_route", &vpn_strict_route, itemType::boolean)); _add(new configItem("vpn_bypass_process", &vpn_rule_process, itemType::string)); _add(new configItem("vpn_bypass_cidr", &vpn_rule_cidr, itemType::string)); _add(new configItem("vpn_rule_white", &vpn_rule_white, itemType::boolean)); _add(new configItem("check_include_pre", &check_include_pre, itemType::boolean)); _add(new configItem("sp_format", &system_proxy_format, itemType::string)); _add(new configItem("sub_clear", &sub_clear, itemType::boolean)); _add(new configItem("sub_insecure", &sub_insecure, itemType::boolean)); _add(new configItem("sub_auto_update", &sub_auto_update, itemType::integer)); _add(new configItem("log_ignore", &log_ignore, itemType::stringList)); _add(new configItem("start_minimal", &start_minimal, itemType::boolean)); _add(new configItem("max_log_line", &max_log_line, itemType::integer)); _add(new configItem("splitter_state", &splitter_state, itemType::string)); _add(new configItem("utlsFingerprint", &utlsFingerprint, itemType::string)); _add(new configItem("core_box_clash_api", &core_box_clash_api, itemType::integer)); _add(new configItem("core_box_clash_api_secret", &core_box_clash_api_secret, itemType::string)); _add(new configItem("core_box_underlying_dns", &core_box_underlying_dns, itemType::string)); _add(new configItem("vpn_internal_tun", &vpn_internal_tun, itemType::boolean)); } void DataStore::UpdateStartedId(int id) { started_id = id; if (remember_enable) { remember_id = id; Save(); } else if (remember_id >= 0) { remember_id = -1919; Save(); } } QString DataStore::GetUserAgent(bool isDefault) const { if (user_agent.isEmpty()) { isDefault = true; } if (isDefault) { QString version = SubStrBefore(NKR_VERSION, "-"); if (!version.contains(".")) version = "2.0"; return "NekoBox/PC/" + version + " (Prefer ClashMeta Format)"; } return user_agent; } // preset routing Routing::Routing(int preset) : JsonStore() { if (preset == 1) { direct_ip = "geoip:cn\n" "geoip:private"; direct_domain = "geosite:cn"; proxy_ip = ""; proxy_domain = ""; block_ip = ""; block_domain = "geosite:category-ads-all\n" "domain:appcenter.ms\n" "domain:firebase.io\n" "domain:crashlytics.com\n"; } if (!Preset::SingBox::DomainStrategy.contains(domain_strategy)) domain_strategy = ""; if (!Preset::SingBox::DomainStrategy.contains(outbound_domain_strategy)) outbound_domain_strategy = ""; _add(new configItem("direct_ip", &this->direct_ip, itemType::string)); _add(new configItem("direct_domain", &this->direct_domain, itemType::string)); _add(new configItem("proxy_ip", &this->proxy_ip, itemType::string)); _add(new configItem("proxy_domain", &this->proxy_domain, itemType::string)); _add(new configItem("block_ip", &this->block_ip, itemType::string)); _add(new configItem("block_domain", &this->block_domain, itemType::string)); _add(new configItem("def_outbound", &this->def_outbound, itemType::string)); _add(new configItem("custom", &this->custom, itemType::string)); // _add(new configItem("remote_dns", &this->remote_dns, itemType::string)); _add(new configItem("remote_dns_strategy", &this->remote_dns_strategy, itemType::string)); _add(new configItem("direct_dns", &this->direct_dns, itemType::string)); _add(new configItem("direct_dns_strategy", &this->direct_dns_strategy, itemType::string)); _add(new configItem("domain_strategy", &this->domain_strategy, itemType::string)); _add(new configItem("outbound_domain_strategy", &this->outbound_domain_strategy, itemType::string)); _add(new configItem("dns_routing", &this->dns_routing, itemType::boolean)); _add(new configItem("sniffing_mode", &this->sniffing_mode, itemType::integer)); _add(new configItem("use_dns_object", &this->use_dns_object, itemType::boolean)); _add(new configItem("dns_object", &this->dns_object, itemType::string)); _add(new configItem("dns_final_out", &this->dns_final_out, itemType::string)); } QString Routing::DisplayRouting() const { return QStringLiteral("[Proxy] %1\n[Proxy] %2\n[Direct] %3\n[Direct] %4\n[Block] %5\n[Block] %6\n[Default Outbound] %7\n[DNS] %8") .arg(SplitLinesSkipSharp(proxy_domain).join(","), 10) .arg(SplitLinesSkipSharp(proxy_ip).join(","), 10) .arg(SplitLinesSkipSharp(direct_domain).join(","), 10) .arg(SplitLinesSkipSharp(direct_ip).join(","), 10) .arg(SplitLinesSkipSharp(block_domain).join(","), 10) .arg(SplitLinesSkipSharp(block_ip).join(","), 10) .arg(def_outbound) .arg(use_dns_object ? "DNS Object" : "Simple DNS"); } QStringList Routing::List() { QDir dr(ROUTES_PREFIX); return dr.entryList(QDir::Files); } bool Routing::SetToActive(const QString &name) { NekoGui::dataStore->routing = std::make_unique(); NekoGui::dataStore->routing->load_control_must = true; NekoGui::dataStore->routing->fn = ROUTES_PREFIX + name; auto ok = NekoGui::dataStore->routing->Load(); if (ok) { NekoGui::dataStore->active_routing = name; NekoGui::dataStore->Save(); } return ok; } // NO default extra core ExtraCore::ExtraCore() : JsonStore() { _add(new configItem("core_map", &this->core_map, itemType::string)); } QString ExtraCore::Get(const QString &id) const { auto obj = QString2QJsonObject(core_map); for (const auto &c: obj.keys()) { if (c == id) return obj[id].toString(); } return ""; } void ExtraCore::Set(const QString &id, const QString &path) { auto obj = QString2QJsonObject(core_map); obj[id] = path; core_map = QJsonObject2QString(obj, true); } void ExtraCore::Delete(const QString &id) { auto obj = QString2QJsonObject(core_map); obj.remove(id); core_map = QJsonObject2QString(obj, true); } InboundAuthorization::InboundAuthorization() : JsonStore() { _add(new configItem("user", &this->username, itemType::string)); _add(new configItem("pass", &this->password, itemType::string)); } bool InboundAuthorization::NeedAuth() const { return !username.trimmed().isEmpty() && !password.trimmed().isEmpty(); } // System Utils QString FindCoreAsset(const QString &name) { QStringList search{}; search << QApplication::applicationDirPath(); search << "/usr/share/sing-geoip"; search << "/usr/share/sing-geosite"; search << "/usr/share/sing-box"; search << "/usr/lib/nekobox"; search << "/usr/share/nekobox"; for (const auto &dir: search) { if (dir.isEmpty()) continue; QFileInfo asset(dir + "/" + name); if (asset.exists()) { return asset.absoluteFilePath(); } } return {}; } QString FindNekoBoxCoreRealPath() { auto fn = QApplication::applicationDirPath() + "/nekobox_core"; auto fi = QFileInfo(fn); if (fi.isSymLink()) return fi.symLinkTarget(); return fn; } short isAdminCache = -1; // IsAdmin 主要判断:有无权限启动 Tun bool IsAdmin() { if (isAdminCache >= 0) return isAdminCache; bool admin = false; #ifdef Q_OS_WIN admin = Windows_IsInAdmin(); #else #ifdef Q_OS_LINUX admin |= Linux_GetCapString(FindNekoBoxCoreRealPath()).contains("cap_net_admin"); #endif admin |= geteuid() == 0; #endif isAdminCache = admin; return admin; }; } // namespace NekoGui ================================================ FILE: main/NekoGui.hpp ================================================ #pragma once #include "Const.hpp" #include "NekoGui_Utils.hpp" #include "NekoGui_ConfigItem.hpp" #include "NekoGui_DataStore.hpp" // Switch core support namespace NekoGui { inline int coreType = CoreType::SING_BOX; QString FindCoreAsset(const QString &name); QString FindNekoBoxCoreRealPath(); bool IsAdmin(); } // namespace NekoGui #define ROUTES_PREFIX_NAME QStringLiteral("routes_box") #define ROUTES_PREFIX QString(ROUTES_PREFIX_NAME + "/") ================================================ FILE: main/NekoGui_ConfigItem.hpp ================================================ // DO NOT INCLUDE THIS namespace NekoGui_ConfigItem { // config 工具 enum itemType { string, integer, integer64, boolean, stringList, integerList, jsonStore, }; class configItem { public: QString name; void *ptr; itemType type; configItem(QString n, void *p, itemType t) { name = std::move(n); ptr = p; type = t; } }; // 可格式化对象 class JsonStore { public: QMap> _map; std::function callback_after_load = nullptr; std::function callback_before_save = nullptr; QString fn; bool load_control_must = false; // must load from file bool save_control_compact = false; bool save_control_no_save = false; QByteArray last_save_content; JsonStore() = default; explicit JsonStore(QString fileName) { fn = std::move(fileName); } void _add(configItem *item); QString _name(void *p); std::shared_ptr _get(const QString &name); void _setValue(const QString &name, void *p); QJsonObject ToJson(const QStringList &without = {}); QByteArray ToJsonBytes(); void FromJson(QJsonObject object); void FromJsonBytes(const QByteArray &data); bool Save(); bool Load(); }; } // namespace NekoGui_ConfigItem using namespace NekoGui_ConfigItem; ================================================ FILE: main/NekoGui_DataStore.hpp ================================================ // DO NOT INCLUDE THIS namespace NekoGui { class Routing : public JsonStore { public: QString direct_ip; QString direct_domain; QString proxy_ip; QString proxy_domain; QString block_ip; QString block_domain; QString def_outbound = "proxy"; QString custom = "{\"rules\": []}"; // DNS QString remote_dns = "https://dns.google/dns-query"; QString remote_dns_strategy = ""; QString direct_dns = "https://doh.pub/dns-query"; QString direct_dns_strategy = ""; bool dns_routing = true; bool use_dns_object = false; QString dns_object = ""; QString dns_final_out = "proxy"; // Misc QString domain_strategy = "AsIs"; QString outbound_domain_strategy = "AsIs"; int sniffing_mode = SniffingMode::FOR_ROUTING; explicit Routing(int preset = 0); [[nodiscard]] QString DisplayRouting() const; static QStringList List(); static bool SetToActive(const QString &name); }; class ExtraCore : public JsonStore { public: QString core_map; explicit ExtraCore(); [[nodiscard]] QString Get(const QString &id) const; void Set(const QString &id, const QString &path); void Delete(const QString &id); }; class InboundAuthorization : public JsonStore { public: QString username; QString password; InboundAuthorization(); [[nodiscard]] bool NeedAuth() const; }; class DataStore : public JsonStore { public: // Running QString core_token; int core_port = 19810; int started_id = -1919; bool core_running = false; bool prepare_exit = false; bool spmode_vpn = false; bool spmode_system_proxy = false; bool need_keep_vpn_off = false; QString appdataDir = ""; QStringList ignoreConnTag = {}; std::unique_ptr routing; int imported_count = 0; bool refreshing_group_list = false; bool refreshing_group = false; int resolve_count = 0; // Flags QStringList argv = {}; bool flag_use_appdata = false; bool flag_many = false; bool flag_tray = false; bool flag_debug = false; bool flag_restart_tun_on = false; bool flag_reorder = false; // Saved // Misc QString log_level = "info"; QString test_latency_url = "http://cp.cloudflare.com/"; QString test_download_url = "http://cachefly.cachefly.net/10mb.test"; int test_download_timeout = 30; int test_concurrent = 5; bool old_share_link_format = true; int traffic_loop_interval = 1000; bool connection_statistics = false; int current_group = 0; // group id QString mux_protocol = "h2mux"; bool mux_padding = false; int mux_concurrency = 8; bool mux_default_on = false; QString theme = "0"; int language = 0; QString mw_size = ""; bool check_include_pre = false; QString system_proxy_format = ""; QStringList log_ignore = {}; bool start_minimal = false; int max_log_line = 200; QString splitter_state = ""; // Subscription QString user_agent = ""; // set at main.cpp bool sub_use_proxy = false; bool sub_clear = false; bool sub_insecure = false; int sub_auto_update = -30; // Security bool skip_cert = false; QString utlsFingerprint = ""; // Remember QStringList remember_spmode = {}; int remember_id = -1919; bool remember_enable = false; // Socks & HTTP Inbound QString inbound_address = "127.0.0.1"; int inbound_socks_port = 2080; // or Mixed InboundAuthorization *inbound_auth = new InboundAuthorization; QString custom_inbound = "{\"inbounds\": []}"; // Routing QString custom_route_global = "{\"rules\": []}"; QString active_routing = "Default"; // VPN bool fake_dns = false; bool vpn_internal_tun = true; int vpn_implementation = 0; int vpn_mtu = 9000; bool vpn_ipv6 = false; bool vpn_hide_console = true; bool vpn_strict_route = false; bool vpn_rule_white = false; QString vpn_rule_process = ""; QString vpn_rule_cidr = ""; // Hotkey QString hotkey_mainwindow = ""; QString hotkey_group = ""; QString hotkey_route = ""; QString hotkey_system_proxy_menu = ""; // Core int core_box_clash_api = -9090; QString core_box_clash_api_secret = ""; QString core_box_underlying_dns = ""; // Other Core ExtraCore *extraCore = new ExtraCore; // Methods DataStore(); void UpdateStartedId(int id); QString GetUserAgent(bool isDefault = false) const; }; extern DataStore *dataStore; } // namespace NekoGui ================================================ FILE: main/NekoGui_Utils.cpp ================================================ #include "NekoGui_Utils.hpp" #include "3rdparty/base64.h" #include "3rdparty/QThreadCreateThread.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN #include "sys/windows/guihelper.h" #endif QStringList SplitLines(const QString &_string) { #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) return _string.split(QRegularExpression("[\r\n]"), Qt::SplitBehaviorFlags::SkipEmptyParts); #else return _string.split(QRegularExpression("[\r\n]"), QString::SkipEmptyParts); #endif } QStringList SplitLinesSkipSharp(const QString &_string, int maxLine) { auto lines = SplitLines(_string); QStringList newLines; int i = 0; for (const auto &line: lines) { if (line.trimmed().startsWith("#")) continue; newLines << line; if (maxLine > 0 && ++i >= maxLine) break; } return newLines; } QByteArray DecodeB64IfValid(const QString &input, QByteArray::Base64Options options) { Qt515Base64::Base64Options newOptions = Qt515Base64::Base64Option::AbortOnBase64DecodingErrors; if (options.testFlag(QByteArray::Base64UrlEncoding)) newOptions |= Qt515Base64::Base64Option::Base64UrlEncoding; if (options.testFlag(QByteArray::OmitTrailingEquals)) newOptions |= Qt515Base64::Base64Option::OmitTrailingEquals; auto result = Qt515Base64::QByteArray_fromBase64Encoding(input.toUtf8(), newOptions); if (result) { return result.decoded; } return {}; } QString QStringList2Command(const QStringList &list) { QStringList new_list; for (auto str: list) { auto q = "\"" + str.replace("\"", "\\\"") + "\""; new_list << q; } return new_list.join(" "); } QString GetQueryValue(const QUrlQuery &q, const QString &key, const QString &def) { auto a = q.queryItemValue(key); if (a.isEmpty()) { return def; } return a; } QString GetRandomString(int randomStringLength) { std::random_device rd; std::mt19937 mt(rd()); const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); std::uniform_int_distribution dist(0, possibleCharacters.length() - 1); QString randomString; for (int i = 0; i < randomStringLength; ++i) { QChar nextChar = possibleCharacters.at(dist(mt)); randomString.append(nextChar); } return randomString; } quint64 GetRandomUint64() { std::random_device rd; std::mt19937 mt(rd()); std::uniform_int_distribution dist; return dist(mt); } // QString >> QJson QJsonObject QString2QJsonObject(const QString &jsonString) { QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonString.toUtf8()); QJsonObject jsonObject = jsonDocument.object(); return jsonObject; } // QJson >> QString QString QJsonObject2QString(const QJsonObject &jsonObject, bool compact) { return QJsonDocument(jsonObject).toJson(compact ? QJsonDocument::Compact : QJsonDocument::Indented); } template QJsonArray QList2QJsonArray(const QList &list) { QVariantList list2; for (auto &item: list) list2.append(item); return QJsonArray::fromVariantList(list2); } template QJsonArray QList2QJsonArray(const QList &list); template QJsonArray QList2QJsonArray(const QList &list); QList QJsonArray2QListInt(const QJsonArray &arr) { QList list2; for (auto item: arr) list2.append(item.toInt()); return list2; } QList QJsonArray2QListString(const QJsonArray &arr) { QList list2; for (auto item: arr) list2.append(item.toString()); return list2; } QByteArray ReadFile(const QString &path) { QFile file(path); file.open(QFile::ReadOnly); return file.readAll(); } QString ReadFileText(const QString &path) { QFile file(path); file.open(QFile::ReadOnly | QFile::Text); QTextStream stream(&file); return stream.readAll(); } int MkPort() { QTcpServer s; s.listen(); auto port = s.serverPort(); s.close(); return port; } QString ReadableSize(const qint64 &size) { double sizeAsDouble = size; static QStringList measures; if (measures.isEmpty()) measures << "B" << "KiB" << "MiB" << "GiB" << "TiB" << "PiB" << "EiB" << "ZiB" << "YiB"; QStringListIterator it(measures); QString measure(it.next()); while (sizeAsDouble >= 1024.0 && it.hasNext()) { measure = it.next(); sizeAsDouble /= 1024.0; } return QString::fromLatin1("%1 %2").arg(sizeAsDouble, 0, 'f', 2).arg(measure); } bool IsIpAddress(const QString &str) { auto address = QHostAddress(str); if (address.protocol() == QAbstractSocket::IPv4Protocol || address.protocol() == QAbstractSocket::IPv6Protocol) return true; return false; } bool IsIpAddressV4(const QString &str) { auto address = QHostAddress(str); if (address.protocol() == QAbstractSocket::IPv4Protocol) return true; return false; } bool IsIpAddressV6(const QString &str) { auto address = QHostAddress(str); if (address.protocol() == QAbstractSocket::IPv6Protocol) return true; return false; } QString DisplayTime(long long time, int formatType) { QDateTime t; t.setMSecsSinceEpoch(time * 1000); return QLocale().toString(t, QLocale::FormatType(formatType)); } QWidget *GetMessageBoxParent() { auto activeWindow = QApplication::activeWindow(); if (activeWindow == nullptr && mainwindow != nullptr) { if (mainwindow->isVisible()) return mainwindow; return nullptr; } return activeWindow; } int MessageBoxWarning(const QString &title, const QString &text) { return QMessageBox::warning(GetMessageBoxParent(), title, text); } int MessageBoxInfo(const QString &title, const QString &text) { return QMessageBox::information(GetMessageBoxParent(), title, text); } void ActivateWindow(QWidget *w) { w->setWindowState(w->windowState() & ~Qt::WindowMinimized); w->setVisible(true); #ifdef Q_OS_WIN Windows_QWidget_SetForegroundWindow(w); #endif w->raise(); w->activateWindow(); } void runOnUiThread(const std::function &callback, QObject *parent) { // any thread auto *timer = new QTimer(); auto thread = dynamic_cast(parent); if (thread == nullptr) { timer->moveToThread(parent == nullptr ? mainwindow->thread() : parent->thread()); } else { timer->moveToThread(thread); } timer->setSingleShot(true); QObject::connect(timer, &QTimer::timeout, [=]() { // main thread callback(); timer->deleteLater(); }); QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection, Q_ARG(int, 0)); } void runOnNewThread(const std::function &callback) { createQThread(callback)->start(); } void setTimeout(const std::function &callback, QObject *obj, int timeout) { auto t = new QTimer; QObject::connect(t, &QTimer::timeout, obj, [=] { callback(); t->deleteLater(); }); t->setSingleShot(true); t->setInterval(timeout); t->start(); } ================================================ FILE: main/NekoGui_Utils.hpp ================================================ // DO NOT INCLUDE THIS #include #include #include #include #include // inline QString software_name = "NekoBox"; inline QString software_core_name = "sing-box"; // Main Functions inline std::function MF_release_runguard; // MainWindow functions class QWidget; inline QWidget *mainwindow; inline std::function MW_show_log; inline std::function MW_show_log_ext; inline std::function MW_show_log_ext_vt100; inline std::function MW_dialog_message; // Dispatchers class QThread; inline QThread *DS_cores; // Timers class QTimer; inline QTimer *TM_auto_update_subsctiption; inline std::function TM_auto_update_subsctiption_Reset_Minute; // String #define FIRST_OR_SECOND(a, b) a.isEmpty() ? b : a inline const QString UNICODE_LRO = QString::fromUtf8(QByteArray::fromHex("E280AD")); #define Int2String(num) QString::number(num) inline QString SubStrBefore(QString str, const QString &sub) { if (!str.contains(sub)) return str; return str.left(str.indexOf(sub)); } inline QString SubStrAfter(QString str, const QString &sub) { if (!str.contains(sub)) return str; return str.right(str.length() - str.indexOf(sub) - sub.length()); } QString QStringList2Command(const QStringList &list); QStringList SplitLines(const QString &_string); QStringList SplitLinesSkipSharp(const QString &_string, int maxLine = 0); // Base64 QByteArray DecodeB64IfValid(const QString &input, QByteArray::Base64Options options = QByteArray::Base64Option::Base64Encoding); // URL class QUrlQuery; #define GetQuery(url) QUrlQuery((url).query(QUrl::ComponentFormattingOption::FullyDecoded)); QString GetQueryValue(const QUrlQuery &q, const QString &key, const QString &def = ""); QString GetRandomString(int randomStringLength); quint64 GetRandomUint64(); // JSON class QJsonObject; class QJsonArray; QJsonObject QString2QJsonObject(const QString &jsonString); QString QJsonObject2QString(const QJsonObject &jsonObject, bool compact); template QJsonArray QList2QJsonArray(const QList &list); QList QJsonArray2QListInt(const QJsonArray &arr); #define QJSONARRAY_ADD(arr, add) \ for (const auto &a: (add)) { \ (arr) += a; \ } #define QJSONOBJECT_COPY(src, dst, key) \ if (src.contains(key)) dst[key] = src[key]; #define QJSONOBJECT_COPY2(src, dst, src_key, dst_key) \ if (src.contains(src_key)) dst[dst_key] = src[src_key]; QList QJsonArray2QListString(const QJsonArray &arr); // Files QByteArray ReadFile(const QString &path); QString ReadFileText(const QString &path); // Validators bool IsIpAddress(const QString &str); bool IsIpAddressV4(const QString &str); bool IsIpAddressV6(const QString &str); // [2001:4860:4860::8888] -> 2001:4860:4860::8888 inline QString UnwrapIPV6Host(QString &str) { return str.replace("[", "").replace("]", ""); } // [2001:4860:4860::8888] or 2001:4860:4860::8888 -> [2001:4860:4860::8888] inline QString WrapIPV6Host(QString &str) { if (!IsIpAddressV6(str)) return str; return "[" + UnwrapIPV6Host(str) + "]"; } inline QString DisplayAddress(QString serverAddress, int serverPort) { if (serverAddress.isEmpty() && serverPort == 0) return {}; return WrapIPV6Host(serverAddress) + ":" + Int2String(serverPort); }; // Format & Misc int MkPort(); QString DisplayTime(long long time, int formatType = 0); QString ReadableSize(const qint64 &size); inline bool InRange(unsigned x, unsigned low, unsigned high) { return (low <= x && x <= high); } inline bool IsValidPort(int port) { return InRange(port, 1, 65535); } // UI QWidget *GetMessageBoxParent(); int MessageBoxWarning(const QString &title, const QString &text); int MessageBoxInfo(const QString &title, const QString &text); void ActivateWindow(QWidget *w); // void runOnUiThread(const std::function &callback, QObject *parent = nullptr); void runOnNewThread(const std::function &callback); template inline void connectOnce(EMITTER *emitter, SIGNAL signal, RECEIVER *receiver, ReceiverFunc f, Qt::ConnectionType connectionType = Qt::AutoConnection) { auto connection = std::make_shared(); auto onTriggered = [connection, f](auto... arguments) { std::invoke(f, arguments...); QObject::disconnect(*connection); }; *connection = QObject::connect(emitter, signal, receiver, onTriggered, connectionType); } void setTimeout(const std::function &callback, QObject *obj, int timeout = 0); ================================================ FILE: main/main.cpp ================================================ #include #include #include #include #include #include #include #include #include #include "3rdparty/RunGuard.hpp" #include "main/NekoGui.hpp" #include "ui/mainwindow_interface.h" #ifdef Q_OS_WIN #include "sys/windows/MiniDump.h" #endif void signal_handler(int signum) { if (qApp) { GetMainWindow()->on_commitDataRequest(); qApp->exit(); } } QTranslator* trans = nullptr; QTranslator* trans_qt = nullptr; void loadTranslate(const QString& locale) { if (trans != nullptr) { trans->deleteLater(); } if (trans_qt != nullptr) { trans_qt->deleteLater(); } // trans = new QTranslator; trans_qt = new QTranslator; QLocale::setDefault(QLocale(locale)); // if (trans->load(":/translations/" + locale + ".qm")) { QCoreApplication::installTranslator(trans); } if (trans_qt->load(QApplication::applicationDirPath() + "/qtbase_" + locale + ".qm")) { QCoreApplication::installTranslator(trans_qt); } } #define LOCAL_SERVER_PREFIX "nekoraylocalserver-" int main(int argc, char* argv[]) { // Core dump #ifdef Q_OS_WIN Windows_SetCrashHandler(); #endif // pre-init QApplication #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton); #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) QApplication::setAttribute(Qt::AA_DontUseNativeDialogs); #endif QApplication::setQuitOnLastWindowClosed(false); auto preQApp = new QApplication(argc, argv); // Clean QDir::setCurrent(QApplication::applicationDirPath()); if (QFile::exists("updater.old")) { QFile::remove("updater.old"); } #ifndef Q_OS_WIN if (!QFile::exists("updater")) { QFile::link("launcher", "updater"); } #endif // Flags NekoGui::dataStore->argv = QApplication::arguments(); if (NekoGui::dataStore->argv.contains("-many")) NekoGui::dataStore->flag_many = true; if (NekoGui::dataStore->argv.contains("-appdata")) { NekoGui::dataStore->flag_use_appdata = true; int appdataIndex = NekoGui::dataStore->argv.indexOf("-appdata"); if (NekoGui::dataStore->argv.size() > appdataIndex + 1 && !NekoGui::dataStore->argv.at(appdataIndex + 1).startsWith("-")) { NekoGui::dataStore->appdataDir = NekoGui::dataStore->argv.at(appdataIndex + 1); } } if (NekoGui::dataStore->argv.contains("-tray")) NekoGui::dataStore->flag_tray = true; if (NekoGui::dataStore->argv.contains("-debug")) NekoGui::dataStore->flag_debug = true; if (NekoGui::dataStore->argv.contains("-flag_restart_tun_on")) NekoGui::dataStore->flag_restart_tun_on = true; if (NekoGui::dataStore->argv.contains("-flag_reorder")) NekoGui::dataStore->flag_reorder = true; #ifdef NKR_CPP_USE_APPDATA NekoGui::dataStore->flag_use_appdata = true; // Example: Package & MacOS #endif #ifdef NKR_CPP_DEBUG NekoGui::dataStore->flag_debug = true; #endif // dirs & clean auto wd = QDir(QApplication::applicationDirPath()); if (NekoGui::dataStore->flag_use_appdata) { QApplication::setApplicationName("nekoray"); if (!NekoGui::dataStore->appdataDir.isEmpty()) { wd.setPath(NekoGui::dataStore->appdataDir); } else { wd.setPath(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation)); } } if (!wd.exists()) wd.mkpath(wd.absolutePath()); if (!wd.exists("config")) wd.mkdir("config"); QDir::setCurrent(wd.absoluteFilePath("config")); QDir("temp").removeRecursively(); // init QApplication delete preQApp; QApplication a(argc, argv); // dispatchers DS_cores = new QThread; DS_cores->start(); // RunGuard RunGuard guard("nekoray" + wd.absolutePath()); quint64 guard_data_in = GetRandomUint64(); quint64 guard_data_out = 0; if (!NekoGui::dataStore->flag_many && !guard.tryToRun(&guard_data_in)) { // Some Good System if (guard.isAnotherRunning(&guard_data_out)) { // Wake up a running instance QLocalSocket socket; socket.connectToServer(LOCAL_SERVER_PREFIX + Int2String(guard_data_out)); qDebug() << socket.fullServerName(); if (!socket.waitForConnected(500)) { qDebug() << "Failed to wake a running instance."; return 0; } qDebug() << "connected to local server, try to raise another program"; return 0; } // Some Bad System QMessageBox::warning(nullptr, "NekoGui", "RunGuard disallow to run, use -many to force start."); return 0; } MF_release_runguard = [&] { guard.release(); }; // icons #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) QIcon::setFallbackSearchPaths(QStringList{ ":/neko", ":/icon", }); #endif // icon for no theme if (QIcon::themeName().isEmpty()) { QIcon::setThemeName("breeze"); } // Dir QDir dir; bool dir_success = true; if (!dir.exists("profiles")) { dir_success &= dir.mkdir("profiles"); } if (!dir.exists("groups")) { dir_success &= dir.mkdir("groups"); } if (!dir.exists(ROUTES_PREFIX_NAME)) { dir_success &= dir.mkdir(ROUTES_PREFIX_NAME); } if (!dir_success) { QMessageBox::warning(nullptr, "Error", "No permission to write " + dir.absolutePath()); return 1; } // Load dataStore switch (NekoGui::coreType) { case NekoGui::CoreType::SING_BOX: NekoGui::dataStore->fn = "groups/nekobox.json"; break; default: MessageBoxWarning("Error", "Unknown coreType."); return 0; } auto isLoaded = NekoGui::dataStore->Load(); if (!isLoaded) { NekoGui::dataStore->Save(); } // Datastore & Flags if (NekoGui::dataStore->start_minimal) NekoGui::dataStore->flag_tray = true; // load routing NekoGui::dataStore->routing = std::make_unique(); NekoGui::dataStore->routing->fn = ROUTES_PREFIX + NekoGui::dataStore->active_routing; isLoaded = NekoGui::dataStore->routing->Load(); if (!isLoaded) { NekoGui::dataStore->routing->Save(); } // Translate QString locale; switch (NekoGui::dataStore->language) { case 1: // English break; case 2: locale = "zh_CN"; break; case 3: locale = "fa_IR"; // farsi(iran) break; case 4: locale = "ru_RU"; // Russian break; default: locale = QLocale().name(); } QGuiApplication::tr("QT_LAYOUT_DIRECTION"); loadTranslate(locale); // Signals signal(SIGTERM, signal_handler); signal(SIGINT, signal_handler); // QLocalServer QLocalServer server; auto server_name = LOCAL_SERVER_PREFIX + Int2String(guard_data_in); QLocalServer::removeServer(server_name); server.listen(server_name); QObject::connect(&server, &QLocalServer::newConnection, &a, [&] { auto socket = server.nextPendingConnection(); qDebug() << "nextPendingConnection:" << server_name << socket; socket->deleteLater(); // raise main window MW_dialog_message("", "Raise"); }); UI_InitMainWindow(); return QApplication::exec(); } ================================================ FILE: nekoray_version.txt ================================================ 4.0.1-2024-12-12 ================================================ FILE: res/dashboard-notice.html ================================================

Please put your clash dashboard files to "./config/dashboard" dir.

For example, you can download from the following URL.

Download Yacd-meta or Use online

================================================ FILE: res/neko.css ================================================ QMessageBox { messagebox-text-interaction-flags: 5; } ================================================ FILE: res/neko.qrc ================================================ icon/internet-web-browser.svg icon/system-run.svg icon/preferences.svg icon/network-server.svg icon/dialog-question.svg icon/system-software-update.svg icon/material/lock-open-outline.svg icon/material/lock-outline.svg icon/material/cancel.svg icon/material/history.svg icon/material/swap-vertical.svg icon/material/delete.svg icon/material/swap-horizontal.svg public/nekobox.png neko.css vpn/vpn-run-root.sh vpn/sing-box-vpn.json dashboard-notice.html ================================================ FILE: res/theme/feiyangqingyun/qss/blacksoft.css ================================================ QPalette{background:#444444;}*{outline:0px;color:#DCDCDC;} QGraphicsView{ border:1px solid #242424; qproperty-backgroundBrush:#444444; } QWidget[form="true"],QLabel[frameShape="1"]{ border:1px solid #242424; border-radius:0px; } QWidget[form="bottom"]{ background:#484848; } QWidget[form="bottom"] .QFrame{ border:1px solid #DCDCDC; } QWidget[form="bottom"] QLabel,QWidget[form="title"] QLabel{ border-radius:0px; color:#DCDCDC; background:none; border-style:none; } QWidget[form="title"],QWidget[nav="left"],QWidget[nav="top"] QAbstractButton{ border-style:none; border-radius:0px; padding:5px; color:#DCDCDC; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838); } QWidget[nav="top"] QAbstractButton:hover,QWidget[nav="top"] QAbstractButton:pressed,QWidget[nav="top"] QAbstractButton:checked{ border-style:solid; border-width:0px 0px 2px 0px; padding:4px 4px 2px 4px; border-color:#AAAAAA; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252); } QWidget[nav="left"] QAbstractButton{ border-radius:0px; color:#DCDCDC; background:none; border-style:none; } QWidget[nav="left"] QAbstractButton:hover{ color:#FFFFFF; background-color:#AAAAAA; } QWidget[nav="left"] QAbstractButton:checked,QWidget[nav="left"] QAbstractButton:pressed{ color:#DCDCDC; border-style:solid; border-width:0px 0px 0px 2px; padding:4px 4px 4px 2px; border-color:#AAAAAA; background-color:#444444; } QWidget[video="true"] QLabel{ color:#DCDCDC; border:1px solid #242424; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838); } QWidget[video="true"] QLabel:focus{ border:1px solid #AAAAAA; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252); } QLineEdit:read-only{ background-color:#484848; } QLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{ border:1px solid #242424; border-radius:3px; padding:2px; background:none; selection-background-color:#AAAAAA; selection-color:#FFFFFF; } QLineEdit:focus,QTextEdit:focus,QPlainTextEdit:focus,QSpinBox:focus,QDoubleSpinBox:focus,QComboBox:focus,QDateEdit:focus,QTimeEdit:focus,QDateTimeEdit:focus,QLineEdit:hover,QTextEdit:hover,QPlainTextEdit:hover,QSpinBox:hover,QDoubleSpinBox:hover,QComboBox:hover,QDateEdit:hover,QTimeEdit:hover,QDateTimeEdit:hover{ border:1px solid #242424; } QLineEdit[echoMode="2"]{ lineedit-password-character:9679; } .QFrame{ border:1px solid #242424; border-radius:3px; } .QGroupBox{ border:1px solid #242424; border-radius:5px; margin-top:3ex; } .QGroupBox::title{ subcontrol-origin:margin; position:relative; left:10px; } .QPushButton,.QToolButton{ border-style:none; border:1px solid #242424; color:#DCDCDC; padding:5px; min-height:15px; border-radius:5px; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838); } .QPushButton:hover,.QToolButton:hover{ background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252); } .QPushButton:pressed,.QToolButton:pressed{ background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838); } .QToolButton::menu-indicator{ image:None; } QToolButton#btnMenu,QPushButton#btnMenu_Min,QPushButton#btnMenu_Max,QPushButton#btnMenu_Close{ border-radius:3px; color:#DCDCDC; padding:3px; margin:0px; background:none; border-style:none; } QToolButton#btnMenu:hover,QPushButton#btnMenu_Min:hover,QPushButton#btnMenu_Max:hover{ color:#FFFFFF; margin:1px 1px 2px 1px; background-color:rgba(51,127,209,230); } QPushButton#btnMenu_Close:hover{ color:#FFFFFF; margin:1px 1px 2px 1px; background-color:rgba(238,0,0,128); } QRadioButton::indicator{ width:15px; height:15px; } QRadioButton::indicator::unchecked{ image:url(:/qss/blacksoft/radiobutton_unchecked.png); } QRadioButton::indicator::unchecked:disabled{ image:url(:/qss/blacksoft/radiobutton_unchecked_disable.png); } QRadioButton::indicator::checked{ image:url(:/qss/blacksoft/radiobutton_checked.png); } QRadioButton::indicator::checked:disabled{ image:url(:/qss/blacksoft/radiobutton_checked_disable.png); } QGroupBox::indicator,QTreeView::indicator,QListView::indicator,QTableView::indicator{ padding:0px 0px 0px 0px; } QCheckBox::indicator,QGroupBox::indicator,QTreeView::indicator,QListView::indicator,QTableView::indicator{ width:13px; height:13px; } QCheckBox::indicator:unchecked,QGroupBox::indicator:unchecked,QTreeView::indicator:unchecked,QListView::indicator:unchecked,QTableView::indicator:unchecked{ image:url(:/qss/blacksoft/checkbox_unchecked.png); } QCheckBox::indicator:unchecked:disabled,QGroupBox::indicator:unchecked:disabled,QTreeView::indicator:unchecked:disabled,QListView::indicator:unchecked:disabled,QTableView::indicator:unchecked:disabled{ image:url(:/qss/blacksoft/checkbox_unchecked_disable.png); } QCheckBox::indicator:checked,QGroupBox::indicator:checked,QTreeView::indicator:checked,QListView::indicator:checked,QTableView::indicator:checked{ image:url(:/qss/blacksoft/checkbox_checked.png); } QCheckBox::indicator:checked:disabled,QGroupBox::indicator:checked:disabled,QTreeView::indicator:checked:disabled,QListView::indicator:checked:disabled,QTableView::indicator:checked:disabled{ image:url(:/qss/blacksoft/checkbox_checked_disable.png); } QCheckBox::indicator:indeterminate,QGroupBox::indicator:indeterminate,QTreeView::indicator:indeterminate,QListView::indicator:indeterminate,QTableView::indicator:indeterminate{ image:url(:/qss/blacksoft/checkbox_parcial.png); } QCheckBox::indicator:indeterminate:disabled,QGroupBox::indicator:indeterminate:disabled,QTreeView::indicator:indeterminate:disabled,QListView::indicator:indeterminate:disabled,QTableView::indicator:indeterminate:disabled{ image:url(:/qss/blacksoft/checkbox_parcial_disable.png); } QTimeEdit::up-button,QDateEdit::up-button,QDateTimeEdit::up-button,QDoubleSpinBox::up-button,QSpinBox::up-button{ image:url(:/qss/blacksoft/add_top.png); width:10px; height:10px; padding:2px 5px 0px 0px; } QTimeEdit::down-button,QDateEdit::down-button,QDateTimeEdit::down-button,QDoubleSpinBox::down-button,QSpinBox::down-button{ image:url(:/qss/blacksoft/add_bottom.png); width:10px; height:10px; padding:0px 5px 2px 0px; } QTimeEdit::up-button:pressed,QDateEdit::up-button:pressed,QDateTimeEdit::up-button:pressed,QDoubleSpinBox::up-button:pressed,QSpinBox::up-button:pressed{ top:-2px; } QTimeEdit::down-button:pressed,QDateEdit::down-button:pressed,QDateTimeEdit::down-button:pressed,QDoubleSpinBox::down-button:pressed,QSpinBox::down-button:pressed,QSpinBox::down-button:pressed{ bottom:-2px; } QComboBox::down-arrow,QDateEdit[calendarPopup="true"]::down-arrow,QTimeEdit[calendarPopup="true"]::down-arrow,QDateTimeEdit[calendarPopup="true"]::down-arrow{ image:url(:/qss/blacksoft/add_bottom.png); width:10px; height:10px; right:2px; } QComboBox::drop-down,QDateEdit::drop-down,QTimeEdit::drop-down,QDateTimeEdit::drop-down{ subcontrol-origin:padding; subcontrol-position:top right; width:15px; border-left-width:0px; border-left-style:solid; border-top-right-radius:3px; border-bottom-right-radius:3px; border-left-color:#242424; } QComboBox::drop-down:on{ top:1px; } QMenuBar::item{ color:#DCDCDC; background-color:#484848; margin:0px; padding:3px 10px; } QMenu,QMenuBar,QMenu:disabled,QMenuBar:disabled{ color:#DCDCDC; background-color:#484848; border:1px solid #242424; margin:0px; } QMenu::item{ padding:3px 20px; } QMenu::indicator{ width:20px; height:13px; } QMenu::indicator::checked{ image:url(:/qss/blacksoft/menu_checked.png); } QMenu::right-arrow{ image:url(:/qss/blacksoft/arrow_right.png); width:13px; height:13px; padding:0px 3px 0px 0px; } QMenu::item:selected,QMenuBar::item:selected{ color:#DCDCDC; border:0px solid #242424; background:#646464; } QMenu::separator{ height:1px; background:#242424; } QProgressBar{ min-height:10px; background:#484848; border-radius:5px; text-align:center; border:1px solid #484848; } QProgressBar:chunk{ border-radius:5px; background-color:#242424; } QSlider::groove:horizontal{ height:8px; border-radius:4px; background:#484848; } QSlider::add-page:horizontal{ height:8px; border-radius:4px; background:#484848; } QSlider::sub-page:horizontal{ height:8px; border-radius:4px; background:#242424; } QSlider::handle:horizontal{ width:13px; margin-top:-3px; margin-bottom:-3px; border-radius:6px; background:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #444444,stop:0.8 #242424); } QSlider::groove:vertical{ width:8px; border-radius:4px; background:#484848; } QSlider::add-page:vertical{ width:8px; border-radius:4px; background:#242424; } QSlider::sub-page:vertical{ width:8px; border-radius:4px; background:#484848; } QSlider::handle:vertical{ height:14px; margin-left:-3px; margin-right:-3px; border-radius:6px; background:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #444444,stop:0.8 #242424); } QScrollBar:horizontal{ background:#484848; padding:0px; border-radius:6px; max-height:12px; } QScrollBar::handle:horizontal{ background:#242424; min-width:50px; border-radius:6px; } QScrollBar::handle:horizontal:hover{ background:#AAAAAA; } QScrollBar::handle:horizontal:pressed{ background:#AAAAAA; } QScrollBar::add-page:horizontal{ background:none; } QScrollBar::sub-page:horizontal{ background:none; } QScrollBar::add-line:horizontal{ background:none; } QScrollBar::sub-line:horizontal{ background:none; } QScrollBar:vertical{ background:#484848; padding:0px; border-radius:6px; max-width:12px; } QScrollBar::handle:vertical{ background:#242424; min-height:50px; border-radius:6px; } QScrollBar::handle:vertical:hover{ background:#AAAAAA; } QScrollBar::handle:vertical:pressed{ background:#AAAAAA; } QScrollBar::add-page:vertical{ background:none; } QScrollBar::sub-page:vertical{ background:none; } QScrollBar::add-line:vertical{ background:none; } QScrollBar::sub-line:vertical{ background:none; } QScrollArea{ border:0px; } QTreeView,QListView,QTableView,QTabWidget::pane{ border:1px solid #242424; selection-background-color:#646464; selection-color:#DCDCDC; alternate-background-color:#525252; gridline-color:#242424; } QTreeView::branch:closed:has-children{ margin:4px; border-image:url(:/qss/blacksoft/branch_open.png); } QTreeView::branch:open:has-children{ margin:4px; border-image:url(:/qss/blacksoft/branch_close.png); } QTreeView,QListView,QTableView,QSplitter::handle,QTreeView::branch{ background:#444444; } QTableView::item:selected,QListView::item:selected,QTreeView::item:selected{ color:#DCDCDC; background:#383838; } QTableView::item:hover,QListView::item:hover,QTreeView::item:hover,QHeaderView,QHeaderView::section,QTableCornerButton:section{ color:#DCDCDC; background:#525252; } QTableView::item,QListView::item,QTreeView::item{ padding:1px; margin:0px; border:0px; } QHeaderView::section,QTableCornerButton:section{ padding:3px; margin:0px; border:1px solid #242424; border-left-width:0px; border-right-width:1px; border-top-width:0px; border-bottom-width:1px; } QTabBar::tab{ border:1px solid #242424; color:#DCDCDC; margin:0px; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252); } QTabBar::tab:selected{ border-style:solid; border-color:#AAAAAA; background:#444444; } QTabBar::tab:top,QTabBar::tab:bottom{ padding:3px 8px 3px 8px; } QTabBar::tab:left,QTabBar::tab:right{ padding:8px 3px 8px 3px; } QTabBar::tab:top:selected{ border-width:2px 0px 0px 0px; } QTabBar::tab:right:selected{ border-width:0px 0px 0px 2px; } QTabBar::tab:bottom:selected{ border-width:0px 0px 2px 0px; } QTabBar::tab:left:selected{ border-width:0px 2px 0px 0px; } QTabBar::tab:first:top:selected,QTabBar::tab:first:bottom:selected{ border-left-width:1px; border-left-color:#242424; } QTabBar::tab:first:left:selected,QTabBar::tab:first:right:selected{ border-top-width:1px; border-top-color:#242424; } QTabBar::tab:last:top:selected,QTabBar::tab:last:bottom:selected{ border-right-width:1px; border-right-color:#242424; } QTabBar::tab:last:left:selected,QTabBar::tab:last:right:selected{ border-bottom-width:1px; border-bottom-color:#242424; } QStatusBar::item{ border:0px solid #484848; border-radius:3px; } QToolBox::tab,QGroupBox#gboxDevicePanel,QGroupBox#gboxDeviceTitle,QFrame#gboxDevicePanel,QFrame#gboxDeviceTitle{ padding:3px; border-radius:5px; color:#DCDCDC; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838); } QToolTip{ border:0px solid #DCDCDC; padding:1px; color:#DCDCDC; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838); } QToolBox::tab:selected{ background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #646464,stop:1 #525252); } QPrintPreviewDialog QToolButton{ border:0px solid #DCDCDC; border-radius:0px; margin:0px; padding:3px; background:none; } QColorDialog QPushButton,QFileDialog QPushButton{ min-width:80px; } QToolButton#qt_calendar_prevmonth{ icon-size:0px; min-width:20px; image:url(:/qss/blacksoft/calendar_prevmonth.png); } QToolButton#qt_calendar_nextmonth{ icon-size:0px; min-width:20px; image:url(:/qss/blacksoft/calendar_nextmonth.png); } QToolButton#qt_calendar_prevmonth,QToolButton#qt_calendar_nextmonth,QToolButton#qt_calendar_monthbutton,QToolButton#qt_calendar_yearbutton{ border:0px solid #DCDCDC; border-radius:3px; margin:3px 3px 3px 3px; padding:3px; background:none; } QToolButton#qt_calendar_prevmonth:hover,QToolButton#qt_calendar_nextmonth:hover,QToolButton#qt_calendar_monthbutton:hover,QToolButton#qt_calendar_yearbutton:hover,QToolButton#qt_calendar_prevmonth:pressed,QToolButton#qt_calendar_nextmonth:pressed,QToolButton#qt_calendar_monthbutton:pressed,QToolButton#qt_calendar_yearbutton:pressed{ border:1px solid #242424; } QCalendarWidget QSpinBox#qt_calendar_yearedit{ margin:2px; } QCalendarWidget QToolButton::menu-indicator{ image:None; } QCalendarWidget QTableView{ border-width:0px; } QCalendarWidget QWidget#qt_calendar_navigationbar{ border:1px solid #242424; border-width:1px 1px 0px 1px; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #484848,stop:1 #383838); } QTableView[model="true"]::item{ padding:0px; margin:0px; } QTableView QLineEdit,QTableView QComboBox,QTableView QSpinBox,QTableView QDoubleSpinBox,QTableView QDateEdit,QTableView QTimeEdit,QTableView QDateTimeEdit{ border-width:0px; border-radius:0px; } QTableView QLineEdit:focus,QTableView QComboBox:focus,QTableView QSpinBox:focus,QTableView QDoubleSpinBox:focus,QTableView QDateEdit:focus,QTableView QTimeEdit:focus,QTableView QDateTimeEdit:focus{ border-width:0px; border-radius:0px; } QLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{ background:#444444; } QTabWidget::pane:top{top:-1px;} QTabWidget::pane:bottom{bottom:-1px;} QTabWidget::pane:left{right:-1px;} QTabWidget::pane:right{left:-1px;} QDialog,QDial,#QUIWidgetMain{ background-color:#444444; color:#DCDCDC; } QDialogButtonBox>QPushButton{ min-width:50px; } QListView[noborder="true"],QTreeView[noborder="true"],QTabWidget[noborder="true"]::pane{ border-width:0px; } QToolBar>*,QStatusBar>*{ margin:2px; } *:disabled,QMenu::item:disabled,QTabBar:tab:disabled,QHeaderView::section:disabled{ background:#444444; border-color:#484848; color:#242424; } /*TextColor:#DCDCDC*/ /*PanelColor:#444444*/ /*BorderColor:#242424*/ /*NormalColorStart:#484848*/ /*NormalColorEnd:#383838*/ /*DarkColorStart:#646464*/ /*DarkColorEnd:#525252*/ /*HighColor:#AAAAAA*/ ================================================ FILE: res/theme/feiyangqingyun/qss/flatgray.css ================================================ QPalette{background:#FFFFFF;}*{outline:0px;color:#57595B;} QGraphicsView{ border:1px solid #B6B6B6; qproperty-backgroundBrush:#FFFFFF; } QWidget[form="true"],QLabel[frameShape="1"]{ border:1px solid #B6B6B6; border-radius:0px; } QWidget[form="bottom"]{ background:#E4E4E4; } QWidget[form="bottom"] .QFrame{ border:1px solid #57595B; } QWidget[form="bottom"] QLabel,QWidget[form="title"] QLabel{ border-radius:0px; color:#57595B; background:none; border-style:none; } QWidget[form="title"],QWidget[nav="left"],QWidget[nav="top"] QAbstractButton{ border-style:none; border-radius:0px; padding:5px; color:#57595B; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4); } QWidget[nav="top"] QAbstractButton:hover,QWidget[nav="top"] QAbstractButton:pressed,QWidget[nav="top"] QAbstractButton:checked{ border-style:solid; border-width:0px 0px 2px 0px; padding:4px 4px 2px 4px; border-color:#575959; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F6F6F6,stop:1 #F6F6F6); } QWidget[nav="left"] QAbstractButton{ border-radius:0px; color:#57595B; background:none; border-style:none; } QWidget[nav="left"] QAbstractButton:hover{ color:#FFFFFF; background-color:#575959; } QWidget[nav="left"] QAbstractButton:checked,QWidget[nav="left"] QAbstractButton:pressed{ color:#57595B; border-style:solid; border-width:0px 0px 0px 2px; padding:4px 4px 4px 2px; border-color:#575959; background-color:#FFFFFF; } QWidget[video="true"] QLabel{ color:#57595B; border:1px solid #B6B6B6; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4); } QWidget[video="true"] QLabel:focus{ border:1px solid #575959; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F6F6F6,stop:1 #F6F6F6); } QLineEdit:read-only{ background-color:#E4E4E4; } QLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{ border:1px solid #B6B6B6; border-radius:3px; padding:2px; background:none; selection-background-color:#575959; selection-color:#FFFFFF; } QLineEdit:focus,QTextEdit:focus,QPlainTextEdit:focus,QSpinBox:focus,QDoubleSpinBox:focus,QComboBox:focus,QDateEdit:focus,QTimeEdit:focus,QDateTimeEdit:focus,QLineEdit:hover,QTextEdit:hover,QPlainTextEdit:hover,QSpinBox:hover,QDoubleSpinBox:hover,QComboBox:hover,QDateEdit:hover,QTimeEdit:hover,QDateTimeEdit:hover{ border:1px solid #B6B6B6; } QLineEdit[echoMode="2"]{ lineedit-password-character:9679; } .QFrame{ border:1px solid #B6B6B6; border-radius:3px; } .QGroupBox{ border:1px solid #B6B6B6; border-radius:5px; margin-top:3ex; } .QGroupBox::title{ subcontrol-origin:margin; position:relative; left:10px; } .QPushButton,.QToolButton{ border-style:none; border:1px solid #B6B6B6; color:#57595B; padding:5px; min-height:15px; border-radius:5px; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4); } .QPushButton:hover,.QToolButton:hover{ background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F6F6F6,stop:1 #F6F6F6); } .QPushButton:pressed,.QToolButton:pressed{ background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4); } .QToolButton::menu-indicator{ image:None; } QToolButton#btnMenu,QPushButton#btnMenu_Min,QPushButton#btnMenu_Max,QPushButton#btnMenu_Close{ border-radius:3px; color:#57595B; padding:3px; margin:0px; background:none; border-style:none; } QToolButton#btnMenu:hover,QPushButton#btnMenu_Min:hover,QPushButton#btnMenu_Max:hover{ color:#FFFFFF; margin:1px 1px 2px 1px; background-color:rgba(51,127,209,230); } QPushButton#btnMenu_Close:hover{ color:#FFFFFF; margin:1px 1px 2px 1px; background-color:rgba(238,0,0,128); } QRadioButton::indicator{ width:15px; height:15px; } QRadioButton::indicator::unchecked{ image:url(:/qss/flatgray/radiobutton_unchecked.png); } QRadioButton::indicator::unchecked:disabled{ image:url(:/qss/flatgray/radiobutton_unchecked_disable.png); } QRadioButton::indicator::checked{ image:url(:/qss/flatgray/radiobutton_checked.png); } QRadioButton::indicator::checked:disabled{ image:url(:/qss/flatgray/radiobutton_checked_disable.png); } QGroupBox::indicator,QTreeView::indicator,QListView::indicator,QTableView::indicator{ padding:0px 0px 0px 0px; } QCheckBox::indicator,QGroupBox::indicator,QTreeView::indicator,QListView::indicator,QTableView::indicator{ width:13px; height:13px; } QCheckBox::indicator:unchecked,QGroupBox::indicator:unchecked,QTreeView::indicator:unchecked,QListView::indicator:unchecked,QTableView::indicator:unchecked{ image:url(:/qss/flatgray/checkbox_unchecked.png); } QCheckBox::indicator:unchecked:disabled,QGroupBox::indicator:unchecked:disabled,QTreeView::indicator:unchecked:disabled,QListView::indicator:unchecked:disabled,QTableView::indicator:unchecked:disabled{ image:url(:/qss/flatgray/checkbox_unchecked_disable.png); } QCheckBox::indicator:checked,QGroupBox::indicator:checked,QTreeView::indicator:checked,QListView::indicator:checked,QTableView::indicator:checked{ image:url(:/qss/flatgray/checkbox_checked.png); } QCheckBox::indicator:checked:disabled,QGroupBox::indicator:checked:disabled,QTreeView::indicator:checked:disabled,QListView::indicator:checked:disabled,QTableView::indicator:checked:disabled{ image:url(:/qss/flatgray/checkbox_checked_disable.png); } QCheckBox::indicator:indeterminate,QGroupBox::indicator:indeterminate,QTreeView::indicator:indeterminate,QListView::indicator:indeterminate,QTableView::indicator:indeterminate{ image:url(:/qss/flatgray/checkbox_parcial.png); } QCheckBox::indicator:indeterminate:disabled,QGroupBox::indicator:indeterminate:disabled,QTreeView::indicator:indeterminate:disabled,QListView::indicator:indeterminate:disabled,QTableView::indicator:indeterminate:disabled{ image:url(:/qss/flatgray/checkbox_parcial_disable.png); } QTimeEdit::up-button,QDateEdit::up-button,QDateTimeEdit::up-button,QDoubleSpinBox::up-button,QSpinBox::up-button{ image:url(:/qss/flatgray/add_top.png); width:10px; height:10px; padding:2px 5px 0px 0px; } QTimeEdit::down-button,QDateEdit::down-button,QDateTimeEdit::down-button,QDoubleSpinBox::down-button,QSpinBox::down-button{ image:url(:/qss/flatgray/add_bottom.png); width:10px; height:10px; padding:0px 5px 2px 0px; } QTimeEdit::up-button:pressed,QDateEdit::up-button:pressed,QDateTimeEdit::up-button:pressed,QDoubleSpinBox::up-button:pressed,QSpinBox::up-button:pressed{ top:-2px; } QTimeEdit::down-button:pressed,QDateEdit::down-button:pressed,QDateTimeEdit::down-button:pressed,QDoubleSpinBox::down-button:pressed,QSpinBox::down-button:pressed,QSpinBox::down-button:pressed{ bottom:-2px; } QComboBox::down-arrow,QDateEdit[calendarPopup="true"]::down-arrow,QTimeEdit[calendarPopup="true"]::down-arrow,QDateTimeEdit[calendarPopup="true"]::down-arrow{ image:url(:/qss/flatgray/add_bottom.png); width:10px; height:10px; right:2px; } QComboBox::drop-down,QDateEdit::drop-down,QTimeEdit::drop-down,QDateTimeEdit::drop-down{ subcontrol-origin:padding; subcontrol-position:top right; width:15px; border-left-width:0px; border-left-style:solid; border-top-right-radius:3px; border-bottom-right-radius:3px; border-left-color:#B6B6B6; } QComboBox::drop-down:on{ top:1px; } QMenuBar::item{ color:#57595B; background-color:#E4E4E4; margin:0px; padding:3px 10px; } QMenu,QMenuBar,QMenu:disabled,QMenuBar:disabled{ color:#57595B; background-color:#E4E4E4; border:1px solid #B6B6B6; margin:0px; } QMenu::item{ padding:3px 20px; } QMenu::indicator{ width:20px; height:13px; } QMenu::indicator::checked{ image:url(:/qss/flatgray/menu_checked.png); } QMenu::right-arrow{ image:url(:/qss/flatgray/arrow_right.png); width:13px; height:13px; padding:0px 3px 0px 0px; } QMenu::item:selected,QMenuBar::item:selected{ color:#57595B; border:0px solid #B6B6B6; background:#F6F6F6; } QMenu::separator{ height:1px; background:#B6B6B6; } QProgressBar{ min-height:10px; background:#E4E4E4; border-radius:5px; text-align:center; border:1px solid #E4E4E4; } QProgressBar:chunk{ border-radius:5px; background-color:#B6B6B6; } QSlider::groove:horizontal{ height:8px; border-radius:4px; background:#E4E4E4; } QSlider::add-page:horizontal{ height:8px; border-radius:4px; background:#E4E4E4; } QSlider::sub-page:horizontal{ height:8px; border-radius:4px; background:#B6B6B6; } QSlider::handle:horizontal{ width:13px; margin-top:-3px; margin-bottom:-3px; border-radius:6px; background:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #FFFFFF,stop:0.8 #B6B6B6); } QSlider::groove:vertical{ width:8px; border-radius:4px; background:#E4E4E4; } QSlider::add-page:vertical{ width:8px; border-radius:4px; background:#B6B6B6; } QSlider::sub-page:vertical{ width:8px; border-radius:4px; background:#E4E4E4; } QSlider::handle:vertical{ height:14px; margin-left:-3px; margin-right:-3px; border-radius:6px; background:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #FFFFFF,stop:0.8 #B6B6B6); } QScrollBar:horizontal{ background:#E4E4E4; padding:0px; border-radius:6px; max-height:12px; } QScrollBar::handle:horizontal{ background:#B6B6B6; min-width:50px; border-radius:6px; } QScrollBar::handle:horizontal:hover{ background:#575959; } QScrollBar::handle:horizontal:pressed{ background:#575959; } QScrollBar::add-page:horizontal{ background:none; } QScrollBar::sub-page:horizontal{ background:none; } QScrollBar::add-line:horizontal{ background:none; } QScrollBar::sub-line:horizontal{ background:none; } QScrollBar:vertical{ background:#E4E4E4; padding:0px; border-radius:6px; max-width:12px; } QScrollBar::handle:vertical{ background:#B6B6B6; min-height:50px; border-radius:6px; } QScrollBar::handle:vertical:hover{ background:#575959; } QScrollBar::handle:vertical:pressed{ background:#575959; } QScrollBar::add-page:vertical{ background:none; } QScrollBar::sub-page:vertical{ background:none; } QScrollBar::add-line:vertical{ background:none; } QScrollBar::sub-line:vertical{ background:none; } QScrollArea{ border:0px; } QTreeView,QListView,QTableView,QTabWidget::pane{ border:1px solid #B6B6B6; selection-background-color:#F6F6F6; selection-color:#57595B; alternate-background-color:#F6F6F6; gridline-color:#B6B6B6; } QTreeView::branch:closed:has-children{ margin:4px; border-image:url(:/qss/flatgray/branch_open.png); } QTreeView::branch:open:has-children{ margin:4px; border-image:url(:/qss/flatgray/branch_close.png); } QTreeView,QListView,QTableView,QSplitter::handle,QTreeView::branch{ background:#FFFFFF; } QTableView::item:selected,QListView::item:selected,QTreeView::item:selected{ color:#57595B; background:#E4E4E4; } QTableView::item:hover,QListView::item:hover,QTreeView::item:hover,QHeaderView,QHeaderView::section,QTableCornerButton:section{ color:#57595B; background:#F6F6F6; } QTableView::item,QListView::item,QTreeView::item{ padding:1px; margin:0px; border:0px; } QHeaderView::section,QTableCornerButton:section{ padding:3px; margin:0px; border:1px solid #B6B6B6; border-left-width:0px; border-right-width:1px; border-top-width:0px; border-bottom-width:1px; } QTabBar::tab{ border:1px solid #B6B6B6; color:#57595B; margin:0px; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F6F6F6,stop:1 #F6F6F6); } QTabBar::tab:selected{ border-style:solid; border-color:#575959; background:#FFFFFF; } QTabBar::tab:top,QTabBar::tab:bottom{ padding:3px 8px 3px 8px; } QTabBar::tab:left,QTabBar::tab:right{ padding:8px 3px 8px 3px; } QTabBar::tab:top:selected{ border-width:2px 0px 0px 0px; } QTabBar::tab:right:selected{ border-width:0px 0px 0px 2px; } QTabBar::tab:bottom:selected{ border-width:0px 0px 2px 0px; } QTabBar::tab:left:selected{ border-width:0px 2px 0px 0px; } QTabBar::tab:first:top:selected,QTabBar::tab:first:bottom:selected{ border-left-width:1px; border-left-color:#B6B6B6; } QTabBar::tab:first:left:selected,QTabBar::tab:first:right:selected{ border-top-width:1px; border-top-color:#B6B6B6; } QTabBar::tab:last:top:selected,QTabBar::tab:last:bottom:selected{ border-right-width:1px; border-right-color:#B6B6B6; } QTabBar::tab:last:left:selected,QTabBar::tab:last:right:selected{ border-bottom-width:1px; border-bottom-color:#B6B6B6; } QStatusBar::item{ border:0px solid #E4E4E4; border-radius:3px; } QToolBox::tab,QGroupBox#gboxDevicePanel,QGroupBox#gboxDeviceTitle,QFrame#gboxDevicePanel,QFrame#gboxDeviceTitle{ padding:3px; border-radius:5px; color:#57595B; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4); } QToolTip{ border:0px solid #57595B; padding:1px; color:#57595B; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4); } QToolBox::tab:selected{ background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F6F6F6,stop:1 #F6F6F6); } QPrintPreviewDialog QToolButton{ border:0px solid #57595B; border-radius:0px; margin:0px; padding:3px; background:none; } QColorDialog QPushButton,QFileDialog QPushButton{ min-width:80px; } QToolButton#qt_calendar_prevmonth{ icon-size:0px; min-width:20px; image:url(:/qss/flatgray/calendar_prevmonth.png); } QToolButton#qt_calendar_nextmonth{ icon-size:0px; min-width:20px; image:url(:/qss/flatgray/calendar_nextmonth.png); } QToolButton#qt_calendar_prevmonth,QToolButton#qt_calendar_nextmonth,QToolButton#qt_calendar_monthbutton,QToolButton#qt_calendar_yearbutton{ border:0px solid #57595B; border-radius:3px; margin:3px 3px 3px 3px; padding:3px; background:none; } QToolButton#qt_calendar_prevmonth:hover,QToolButton#qt_calendar_nextmonth:hover,QToolButton#qt_calendar_monthbutton:hover,QToolButton#qt_calendar_yearbutton:hover,QToolButton#qt_calendar_prevmonth:pressed,QToolButton#qt_calendar_nextmonth:pressed,QToolButton#qt_calendar_monthbutton:pressed,QToolButton#qt_calendar_yearbutton:pressed{ border:1px solid #B6B6B6; } QCalendarWidget QSpinBox#qt_calendar_yearedit{ margin:2px; } QCalendarWidget QToolButton::menu-indicator{ image:None; } QCalendarWidget QTableView{ border-width:0px; } QCalendarWidget QWidget#qt_calendar_navigationbar{ border:1px solid #B6B6B6; border-width:1px 1px 0px 1px; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4); } QTableView[model="true"]::item{ padding:0px; margin:0px; } QTableView QLineEdit,QTableView QComboBox,QTableView QSpinBox,QTableView QDoubleSpinBox,QTableView QDateEdit,QTableView QTimeEdit,QTableView QDateTimeEdit{ border-width:0px; border-radius:0px; } QTableView QLineEdit:focus,QTableView QComboBox:focus,QTableView QSpinBox:focus,QTableView QDoubleSpinBox:focus,QTableView QDateEdit:focus,QTableView QTimeEdit:focus,QTableView QDateTimeEdit:focus{ border-width:0px; border-radius:0px; } QLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{ background:#FFFFFF; } QTabWidget::pane:top{top:-1px;} QTabWidget::pane:bottom{bottom:-1px;} QTabWidget::pane:left{right:-1px;} QTabWidget::pane:right{left:-1px;} QDialog,QDial,#QUIWidgetMain{ background-color:#FFFFFF; color:#57595B; } QDialogButtonBox>QPushButton{ min-width:50px; } QListView[noborder="true"],QTreeView[noborder="true"],QTabWidget[noborder="true"]::pane{ border-width:0px; } QToolBar>*,QStatusBar>*{ margin:2px; } *:disabled,QMenu::item:disabled,QTabBar:tab:disabled,QHeaderView::section:disabled{ background:#FFFFFF; border-color:#E4E4E4; color:#B6B6B6; } /*TextColor:#57595B*/ /*PanelColor:#FFFFFF*/ /*BorderColor:#B6B6B6*/ /*NormalColorStart:#E4E4E4*/ /*NormalColorEnd:#E4E4E4*/ /*DarkColorStart:#F6F6F6*/ /*DarkColorEnd:#F6F6F6*/ /*HighColor:#575959*/ ================================================ FILE: res/theme/feiyangqingyun/qss/lightblue.css ================================================ QPalette{background:#EAF7FF;}*{outline:0px;color:#386487;} QGraphicsView{ border:1px solid #C0DCF2; qproperty-backgroundBrush:#EAF7FF; } QWidget[form="true"],QLabel[frameShape="1"],QGraphicsView{ border:1px solid #C0DCF2; border-radius:0px; } QWidget[form="bottom"]{ background:#DEF0FE; } QWidget[form="bottom"] .QFrame{ border:1px solid #386487; } QWidget[form="bottom"] QLabel,QWidget[form="title"] QLabel{ border-radius:0px; color:#386487; background:none; border-style:none; } QWidget[form="title"],QWidget[nav="left"],QWidget[nav="top"] QAbstractButton{ border-style:none; border-radius:0px; padding:5px; color:#386487; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #DEF0FE,stop:1 #C0DEF6); } QWidget[nav="top"] QAbstractButton:hover,QWidget[nav="top"] QAbstractButton:pressed,QWidget[nav="top"] QAbstractButton:checked{ border-style:solid; border-width:0px 0px 2px 0px; padding:4px 4px 2px 4px; border-color:#386488; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F2F9FF,stop:1 #DAEFFF); } QWidget[nav="left"] QAbstractButton{ border-radius:0px; color:#386487; background:none; border-style:none; } QWidget[nav="left"] QAbstractButton:hover{ color:#FFFFFF; background-color:#386488; } QWidget[nav="left"] QAbstractButton:checked,QWidget[nav="left"] QAbstractButton:pressed{ color:#386487; border-style:solid; border-width:0px 0px 0px 2px; padding:4px 4px 4px 2px; border-color:#386488; background-color:#EAF7FF; } QWidget[video="true"] QLabel{ color:#386487; border:1px solid #C0DCF2; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #DEF0FE,stop:1 #C0DEF6); } QWidget[video="true"] QLabel:focus{ border:1px solid #386488; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F2F9FF,stop:1 #DAEFFF); } QLineEdit:read-only{ background-color:#DEF0FE; } QLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{ border:1px solid #C0DCF2; border-radius:3px; padding:2px; background:none; selection-background-color:#386488; selection-color:#FFFFFF; } QLineEdit:focus,QTextEdit:focus,QPlainTextEdit:focus,QSpinBox:focus,QDoubleSpinBox:focus,QComboBox:focus,QDateEdit:focus,QTimeEdit:focus,QDateTimeEdit:focus,QLineEdit:hover,QTextEdit:hover,QPlainTextEdit:hover,QSpinBox:hover,QDoubleSpinBox:hover,QComboBox:hover,QDateEdit:hover,QTimeEdit:hover,QDateTimeEdit:hover{ border:1px solid #C0DCF2; } QLineEdit[echoMode="2"]{ lineedit-password-character:9679; } .QFrame{ border:1px solid #C0DCF2; border-radius:3px; } .QGroupBox{ border:1px solid #C0DCF2; border-radius:5px; margin-top:3ex; } .QGroupBox::title{ subcontrol-origin:margin; position:relative; left:10px; } .QPushButton,.QToolButton{ border-style:none; border:1px solid #C0DCF2; color:#386487; padding:5px; min-height:15px; border-radius:5px; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #DEF0FE,stop:1 #C0DEF6); } .QPushButton:hover,.QToolButton:hover{ background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F2F9FF,stop:1 #DAEFFF); } .QPushButton:pressed,.QToolButton:pressed{ background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #DEF0FE,stop:1 #C0DEF6); } .QToolButton::menu-indicator{ image:None; } QToolButton#btnMenu,QPushButton#btnMenu_Min,QPushButton#btnMenu_Max,QPushButton#btnMenu_Close{ border-radius:3px; color:#386487; padding:3px; margin:0px; background:none; border-style:none; } QToolButton#btnMenu:hover,QPushButton#btnMenu_Min:hover,QPushButton#btnMenu_Max:hover{ color:#FFFFFF; margin:1px 1px 2px 1px; background-color:rgba(51,127,209,230); } QPushButton#btnMenu_Close:hover{ color:#FFFFFF; margin:1px 1px 2px 1px; background-color:rgba(238,0,0,128); } QRadioButton::indicator{ width:15px; height:15px; } QRadioButton::indicator::unchecked{ image:url(:/qss/lightblue/radiobutton_unchecked.png); } QRadioButton::indicator::unchecked:disabled{ image:url(:/qss/lightblue/radiobutton_unchecked_disable.png); } QRadioButton::indicator::checked{ image:url(:/qss/lightblue/radiobutton_checked.png); } QRadioButton::indicator::checked:disabled{ image:url(:/qss/lightblue/radiobutton_checked_disable.png); } QGroupBox::indicator,QTreeView::indicator,QListView::indicator,QTableView::indicator{ padding:0px 0px 0px 0px; } QCheckBox::indicator,QGroupBox::indicator,QTreeView::indicator,QListView::indicator,QTableView::indicator{ width:13px; height:13px; } QCheckBox::indicator:unchecked,QGroupBox::indicator:unchecked,QTreeView::indicator:unchecked,QListView::indicator:unchecked,QTableView::indicator:unchecked{ image:url(:/qss/lightblue/checkbox_unchecked.png); } QCheckBox::indicator:unchecked:disabled,QGroupBox::indicator:unchecked:disabled,QTreeView::indicator:unchecked:disabled,QListView::indicator:unchecked:disabled,QTableView::indicator:unchecked:disabled{ image:url(:/qss/lightblue/checkbox_unchecked_disable.png); } QCheckBox::indicator:checked,QGroupBox::indicator:checked,QTreeView::indicator:checked,QListView::indicator:checked,QTableView::indicator:checked{ image:url(:/qss/lightblue/checkbox_checked.png); } QCheckBox::indicator:checked:disabled,QGroupBox::indicator:checked:disabled,QTreeView::indicator:checked:disabled,QListView::indicator:checked:disabled,QTableView::indicator:checked:disabled{ image:url(:/qss/lightblue/checkbox_checked_disable.png); } QCheckBox::indicator:indeterminate,QGroupBox::indicator:indeterminate,QTreeView::indicator:indeterminate,QListView::indicator:indeterminate,QTableView::indicator:indeterminate{ image:url(:/qss/lightblue/checkbox_parcial.png); } QCheckBox::indicator:indeterminate:disabled,QGroupBox::indicator:indeterminate:disabled,QTreeView::indicator:indeterminate:disabled,QListView::indicator:indeterminate:disabled,QTableView::indicator:indeterminate:disabled{ image:url(:/qss/lightblue/checkbox_parcial_disable.png); } QTimeEdit::up-button,QDateEdit::up-button,QDateTimeEdit::up-button,QDoubleSpinBox::up-button,QSpinBox::up-button{ image:url(:/qss/lightblue/add_top.png); width:10px; height:10px; padding:2px 5px 0px 0px; } QTimeEdit::down-button,QDateEdit::down-button,QDateTimeEdit::down-button,QDoubleSpinBox::down-button,QSpinBox::down-button{ image:url(:/qss/lightblue/add_bottom.png); width:10px; height:10px; padding:0px 5px 2px 0px; } QTimeEdit::up-button:pressed,QDateEdit::up-button:pressed,QDateTimeEdit::up-button:pressed,QDoubleSpinBox::up-button:pressed,QSpinBox::up-button:pressed{ top:-2px; } QTimeEdit::down-button:pressed,QDateEdit::down-button:pressed,QDateTimeEdit::down-button:pressed,QDoubleSpinBox::down-button:pressed,QSpinBox::down-button:pressed,QSpinBox::down-button:pressed{ bottom:-2px; } QComboBox::down-arrow,QDateEdit[calendarPopup="true"]::down-arrow,QTimeEdit[calendarPopup="true"]::down-arrow,QDateTimeEdit[calendarPopup="true"]::down-arrow{ image:url(:/qss/lightblue/add_bottom.png); width:10px; height:10px; right:2px; } QComboBox::drop-down,QDateEdit::drop-down,QTimeEdit::drop-down,QDateTimeEdit::drop-down{ subcontrol-origin:padding; subcontrol-position:top right; width:15px; border-left-width:0px; border-left-style:solid; border-top-right-radius:3px; border-bottom-right-radius:3px; border-left-color:#C0DCF2; } QComboBox::drop-down:on{ top:1px; } QMenuBar::item{ color:#386487; background-color:#DEF0FE; margin:0px; padding:3px 10px; } QMenu,QMenuBar,QMenu:disabled,QMenuBar:disabled{ color:#386487; background-color:#DEF0FE; border:1px solid #C0DCF2; margin:0px; } QMenu::item{ padding:3px 20px; } QMenu::indicator{ width:20px; height:13px; } QMenu::indicator::checked{ image:url(:/qss/lightblue/menu_checked.png); } QMenu::right-arrow{ image:url(:/qss/lightblue/arrow_right.png); width:13px; height:13px; padding:0px 3px 0px 0px; } QMenu::item:selected,QMenuBar::item:selected{ color:#386487; border:0px solid #C0DCF2; background:#F2F9FF; } QMenu::separator{ height:1px; background:#C0DCF2; } QProgressBar{ min-height:10px; background:#DEF0FE; border-radius:5px; text-align:center; border:1px solid #DEF0FE; } QProgressBar:chunk{ border-radius:5px; background-color:#C0DCF2; } QSlider::groove:horizontal{ height:8px; border-radius:4px; background:#DEF0FE; } QSlider::add-page:horizontal{ height:8px; border-radius:4px; background:#DEF0FE; } QSlider::sub-page:horizontal{ height:8px; border-radius:4px; background:#C0DCF2; } QSlider::handle:horizontal{ width:13px; margin-top:-3px; margin-bottom:-3px; border-radius:6px; background:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #EAF7FF,stop:0.8 #C0DCF2); } QSlider::groove:vertical{ width:8px; border-radius:4px; background:#DEF0FE; } QSlider::add-page:vertical{ width:8px; border-radius:4px; background:#C0DCF2; } QSlider::sub-page:vertical{ width:8px; border-radius:4px; background:#DEF0FE; } QSlider::handle:vertical{ height:14px; margin-left:-3px; margin-right:-3px; border-radius:6px; background:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #EAF7FF,stop:0.8 #C0DCF2); } QScrollBar:horizontal{ background:#DEF0FE; padding:0px; border-radius:6px; max-height:12px; } QScrollBar::handle:horizontal{ background:#C0DCF2; min-width:50px; border-radius:6px; } QScrollBar::handle:horizontal:hover{ background:#386488; } QScrollBar::handle:horizontal:pressed{ background:#386488; } QScrollBar::add-page:horizontal{ background:none; } QScrollBar::sub-page:horizontal{ background:none; } QScrollBar::add-line:horizontal{ background:none; } QScrollBar::sub-line:horizontal{ background:none; } QScrollBar:vertical{ background:#DEF0FE; padding:0px; border-radius:6px; max-width:12px; } QScrollBar::handle:vertical{ background:#C0DCF2; min-height:50px; border-radius:6px; } QScrollBar::handle:vertical:hover{ background:#386488; } QScrollBar::handle:vertical:pressed{ background:#386488; } QScrollBar::add-page:vertical{ background:none; } QScrollBar::sub-page:vertical{ background:none; } QScrollBar::add-line:vertical{ background:none; } QScrollBar::sub-line:vertical{ background:none; } QScrollArea{ border:0px; } QTreeView,QListView,QTableView,QTabWidget::pane{ border:1px solid #C0DCF2; selection-background-color:#F2F9FF; selection-color:#386487; alternate-background-color:#DAEFFF; gridline-color:#C0DCF2; } QTreeView::branch:closed:has-children{ margin:4px; border-image:url(:/qss/lightblue/branch_open.png); } QTreeView::branch:open:has-children{ margin:4px; border-image:url(:/qss/lightblue/branch_close.png); } QTreeView,QListView,QTableView,QSplitter::handle,QTreeView::branch{ background:#EAF7FF; } QTableView::item:selected,QListView::item:selected,QTreeView::item:selected{ color:#386487; background:#C0DEF6; } QTableView::item:hover,QListView::item:hover,QTreeView::item:hover,QHeaderView,QHeaderView::section,QTableCornerButton:section{ color:#386487; background:#DAEFFF; } QTableView::item,QListView::item,QTreeView::item{ padding:1px; margin:0px; border:0px; } QHeaderView::section,QTableCornerButton:section{ padding:3px; margin:0px; border:1px solid #C0DCF2; border-left-width:0px; border-right-width:1px; border-top-width:0px; border-bottom-width:1px; } QTabBar::tab{ border:1px solid #C0DCF2; color:#386487; margin:0px; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F2F9FF,stop:1 #DAEFFF); } QTabBar::tab:selected{ border-style:solid; border-color:#386488; background:#EAF7FF; } QTabBar::tab:top,QTabBar::tab:bottom{ padding:3px 8px 3px 8px; } QTabBar::tab:left,QTabBar::tab:right{ padding:8px 3px 8px 3px; } QTabBar::tab:top:selected{ border-width:2px 0px 0px 0px; } QTabBar::tab:right:selected{ border-width:0px 0px 0px 2px; } QTabBar::tab:bottom:selected{ border-width:0px 0px 2px 0px; } QTabBar::tab:left:selected{ border-width:0px 2px 0px 0px; } QTabBar::tab:first:top:selected,QTabBar::tab:first:bottom:selected{ border-left-width:1px; border-left-color:#C0DCF2; } QTabBar::tab:first:left:selected,QTabBar::tab:first:right:selected{ border-top-width:1px; border-top-color:#C0DCF2; } QTabBar::tab:last:top:selected,QTabBar::tab:last:bottom:selected{ border-right-width:1px; border-right-color:#C0DCF2; } QTabBar::tab:last:left:selected,QTabBar::tab:last:right:selected{ border-bottom-width:1px; border-bottom-color:#C0DCF2; } QStatusBar::item{ border:0px solid #DEF0FE; border-radius:3px; } QToolBox::tab,QGroupBox#gboxDevicePanel,QGroupBox#gboxDeviceTitle,QFrame#gboxDevicePanel,QFrame#gboxDeviceTitle{ padding:3px; border-radius:5px; color:#386487; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #DEF0FE,stop:1 #C0DEF6); } QToolTip{ border:0px solid #386487; padding:1px; color:#386487; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #DEF0FE,stop:1 #C0DEF6); } QToolBox::tab:selected{ background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F2F9FF,stop:1 #DAEFFF); } QPrintPreviewDialog QToolButton{ border:0px solid #386487; border-radius:0px; margin:0px; padding:3px; background:none; } QColorDialog QPushButton,QFileDialog QPushButton{ min-width:80px; } QToolButton#qt_calendar_prevmonth{ icon-size:0px; min-width:20px; image:url(:/qss/lightblue/calendar_prevmonth.png); } QToolButton#qt_calendar_nextmonth{ icon-size:0px; min-width:20px; image:url(:/qss/lightblue/calendar_nextmonth.png); } QToolButton#qt_calendar_prevmonth,QToolButton#qt_calendar_nextmonth,QToolButton#qt_calendar_monthbutton,QToolButton#qt_calendar_yearbutton{ border:0px solid #386487; border-radius:3px; margin:3px 3px 3px 3px; padding:3px; background:none; } QToolButton#qt_calendar_prevmonth:hover,QToolButton#qt_calendar_nextmonth:hover,QToolButton#qt_calendar_monthbutton:hover,QToolButton#qt_calendar_yearbutton:hover,QToolButton#qt_calendar_prevmonth:pressed,QToolButton#qt_calendar_nextmonth:pressed,QToolButton#qt_calendar_monthbutton:pressed,QToolButton#qt_calendar_yearbutton:pressed{ border:1px solid #C0DCF2; } QCalendarWidget QSpinBox#qt_calendar_yearedit{ margin:2px; } QCalendarWidget QToolButton::menu-indicator{ image:None; } QCalendarWidget QTableView{ border-width:0px; } QCalendarWidget QWidget#qt_calendar_navigationbar{ border:1px solid #C0DCF2; border-width:1px 1px 0px 1px; background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #DEF0FE,stop:1 #C0DEF6); } QTableView[model="true"]::item{ padding:0px; margin:0px; } QTableView QLineEdit,QTableView QComboBox,QTableView QSpinBox,QTableView QDoubleSpinBox,QTableView QDateEdit,QTableView QTimeEdit,QTableView QDateTimeEdit{ border-width:0px; border-radius:0px; } QTableView QLineEdit:focus,QTableView QComboBox:focus,QTableView QSpinBox:focus,QTableView QDoubleSpinBox:focus,QTableView QDateEdit:focus,QTableView QTimeEdit:focus,QTableView QDateTimeEdit:focus{ border-width:0px; border-radius:0px; } QLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{ background:#EAF7FF; } QTabWidget::pane:top{top:-1px;} QTabWidget::pane:bottom{bottom:-1px;} QTabWidget::pane:left{right:-1px;} QTabWidget::pane:right{left:-1px;} QDialog,QDial,#QUIWidgetMain{ background-color:#EAF7FF; color:#386487; } QDialogButtonBox>QPushButton{ min-width:50px; } QListView[noborder="true"],QTreeView[noborder="true"],QTabWidget[noborder="true"]::pane{ border-width:0px; } QToolBar>*,QStatusBar>*{ margin:2px; } *:disabled,QMenu::item:disabled,QTabBar:tab:disabled,QHeaderView::section:disabled{ background:#EAF7FF; border-color:#DEF0FE; color:#C0DCF2; } /*TextColor:#386487*/ /*PanelColor:#EAF7FF*/ /*BorderColor:#C0DCF2*/ /*NormalColorStart:#DEF0FE*/ /*NormalColorEnd:#C0DEF6*/ /*DarkColorStart:#F2F9FF*/ /*DarkColorEnd:#DAEFFF*/ /*HighColor:#386488*/ ================================================ FILE: res/theme/feiyangqingyun/qss.qrc ================================================ qss/flatgray.css qss/lightblue.css qss/flatgray/add_bottom.png qss/flatgray/add_left.png qss/flatgray/add_right.png qss/flatgray/add_top.png qss/flatgray/arrow_bottom.png qss/flatgray/arrow_left.png qss/flatgray/arrow_right.png qss/flatgray/arrow_top.png qss/flatgray/branch_close.png qss/flatgray/branch_open.png qss/flatgray/calendar_nextmonth.png qss/flatgray/calendar_prevmonth.png qss/flatgray/checkbox_checked.png qss/flatgray/checkbox_checked_disable.png qss/flatgray/checkbox_parcial.png qss/flatgray/checkbox_parcial_disable.png qss/flatgray/checkbox_unchecked.png qss/flatgray/checkbox_unchecked_disable.png qss/flatgray/menu_checked.png qss/flatgray/radiobutton_checked.png qss/flatgray/radiobutton_checked_disable.png qss/flatgray/radiobutton_unchecked.png qss/flatgray/radiobutton_unchecked_disable.png qss/lightblue/add_bottom.png qss/lightblue/add_left.png qss/lightblue/add_right.png qss/lightblue/add_top.png qss/lightblue/arrow_bottom.png qss/lightblue/arrow_left.png qss/lightblue/arrow_right.png qss/lightblue/arrow_top.png qss/lightblue/branch_close.png qss/lightblue/branch_open.png qss/lightblue/calendar_nextmonth.png qss/lightblue/calendar_prevmonth.png qss/lightblue/checkbox_checked.png qss/lightblue/checkbox_checked_disable.png qss/lightblue/checkbox_parcial.png qss/lightblue/checkbox_parcial_disable.png qss/lightblue/checkbox_unchecked.png qss/lightblue/checkbox_unchecked_disable.png qss/lightblue/menu_checked.png qss/lightblue/radiobutton_checked.png qss/lightblue/radiobutton_checked_disable.png qss/lightblue/radiobutton_unchecked.png qss/lightblue/radiobutton_unchecked_disable.png qss/blacksoft.css qss/blacksoft/add_bottom.png qss/blacksoft/add_left.png qss/blacksoft/add_right.png qss/blacksoft/add_top.png qss/blacksoft/arrow_bottom.png qss/blacksoft/arrow_left.png qss/blacksoft/arrow_right.png qss/blacksoft/arrow_top.png qss/blacksoft/branch_close.png qss/blacksoft/branch_open.png qss/blacksoft/calendar_nextmonth.png qss/blacksoft/calendar_prevmonth.png qss/blacksoft/checkbox_checked.png qss/blacksoft/checkbox_checked_disable.png qss/blacksoft/checkbox_parcial.png qss/blacksoft/checkbox_parcial_disable.png qss/blacksoft/checkbox_unchecked.png qss/blacksoft/checkbox_unchecked_disable.png qss/blacksoft/menu_checked.png qss/blacksoft/radiobutton_checked.png qss/blacksoft/radiobutton_checked_disable.png qss/blacksoft/radiobutton_unchecked.png qss/blacksoft/radiobutton_unchecked_disable.png ================================================ FILE: res/vpn/sing-box-vpn.json ================================================ { "log": { "level": "info" }, "dns": { "fakeip": { "enabled": true, "inet4_range": "198.18.0.0/15", "inet6_range": "fc00::/18" }, "servers": [ { "tag": "dns-remote", "address": "8.8.8.8", "detour": "neko-socks" }, { "tag": "dns-direct", "address": "%DNS_ADDRESS%", "detour": "direct" }, { "address": "fakeip", "tag": "dns-fake" }, { "address": "rcode://success", "tag": "dns-block" } ], "rules": [ { "query_type": [ 32, 33 ], "server": "dns-block" }, { "domain_suffix": [ ".lan" ], "server": "dns-block" }, { "process_name": [ "nekoray_core", "nekoray_core.exe", "nekobox_core", "nekobox_core.exe" ], "server": "dns-direct" }, { "inbound": "%FAKE_DNS_INBOUND%", "server": "dns-fake" } ] }, "inbounds": [ { "type": "tun", "tag": "tun-in", "interface_name": "%TUN_NAME%", "inet4_address": "172.19.0.1/28", //%IPV6_ADDRESS% "mtu": %MTU%, "auto_route": true, "strict_route": %STRICT_ROUTE%, "stack": "%STACK%", "endpoint_independent_nat": true, "sniff": false } ], "outbounds": [ { "type": "socks", "tag": "neko-socks", "udp_fragment": true, //%SOCKS_USER_PASS% "server": "127.0.0.1", "server_port": %PORT% }, { "type": "block", "tag": "block" }, { "type": "direct", "tag": "direct" }, { "type": "dns", "tag": "dns-out" } ], "route": { "final": "%FINAL_OUT%", "auto_detect_interface": true, "rules": [ { "network": "udp", "port": [ 135, 137, 138, 139, 5353 ], "outbound": "block" }, { "ip_cidr": [ "224.0.0.0/3", "ff00::/8" ], "outbound": "block" }, { "source_ip_cidr": [ "224.0.0.0/3", "ff00::/8" ], "outbound": "block" }, { "port": 53, "process_name": [ "nekoray_core", "nekoray_core.exe" ], "outbound": "dns-out" }, { "process_name": [ "nekoray_core", "nekoray_core.exe", "nekobox_core", "nekobox_core.exe" ], "outbound": "direct" } //%PROCESS_NAME_RULE% //%CIDR_RULE% , { "port": 53, "outbound": "dns-out" } ] } } ================================================ FILE: res/vpn/vpn-run-root.sh ================================================ #!/bin/sh set -e set -x if [ "$EUID" -ne 0 ]; then echo "[Warning] Tun script not running as root" fi command -v pkill >/dev/null 2>&1 || echo "[Warning] pkill not found" BASEDIR=$(dirname "$0") cd $BASEDIR pre_start_linux() { # for Tun2Socket iptables -I INPUT -s 172.19.0.2 -d 172.19.0.1 -p tcp -j ACCEPT ip6tables -I INPUT -s fdfe:dcba:9876::2 -d fdfe:dcba:9876::1 -p tcp -j ACCEPT } start() { pre_start_linux "./nekobox_core" run -c "$CONFIG_PATH" } stop() { iptables -D INPUT -s 172.19.0.2 -d 172.19.0.1 -p tcp -j ACCEPT ip6tables -D INPUT -s fdfe:dcba:9876::2 -d fdfe:dcba:9876::1 -p tcp -j ACCEPT } if [ "$1" != "stop" ]; then start || true fi stop || true ================================================ FILE: rpc/gRPC.cpp ================================================ #include "gRPC.h" #include #include #ifndef NKR_NO_GRPC #include "main/NekoGui.hpp" #include #include #include #include #include #include #include #include namespace QtGrpc { const char *GrpcAcceptEncodingHeader = "grpc-accept-encoding"; const char *AcceptEncodingHeader = "accept-encoding"; const char *TEHeader = "te"; const char *GrpcStatusHeader = "grpc-status"; const char *GrpcStatusMessage = "grpc-message"; const int GrpcMessageSizeHeaderSize = 5; class NoCache : public QAbstractNetworkCache { public: QNetworkCacheMetaData metaData(const QUrl &url) override { return {}; } void updateMetaData(const QNetworkCacheMetaData &metaData) override { } QIODevice *data(const QUrl &url) override { return nullptr; } bool remove(const QUrl &url) override { return false; } [[nodiscard]] qint64 cacheSize() const override { return 0; } QIODevice *prepare(const QNetworkCacheMetaData &metaData) override { return nullptr; } void insert(QIODevice *device) override { } void clear() override { } }; class Http2GrpcChannelPrivate { private: QThread *thread; QNetworkAccessManager *nm; QString url_base; QString serviceName; QByteArray nekoray_auth; // async QNetworkReply *post(const QString &method, const QString &service, const QByteArray &args) { QUrl callUrl = url_base + "/" + service + "/" + method; // qDebug() << "Service call url: " << callUrl; QNetworkRequest request(callUrl); // request.setAttribute(QNetworkRequest::CacheSaveControlAttribute, false); // request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) request.setAttribute(QNetworkRequest::Http2DirectAttribute, true); #endif request.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String{"application/grpc"}); request.setRawHeader("Cache-Control", "no-store"); request.setRawHeader(GrpcAcceptEncodingHeader, QByteArray{"identity,deflate,gzip"}); request.setRawHeader(AcceptEncodingHeader, QByteArray{"identity,gzip"}); request.setRawHeader(TEHeader, QByteArray{"trailers"}); request.setRawHeader("nekoray_auth", nekoray_auth); QByteArray msg(GrpcMessageSizeHeaderSize, '\0'); *reinterpret_cast(msg.data() + 1) = qToBigEndian((int) args.size()); msg += args; // qDebug() << "SEND: " << msg.size(); QNetworkReply *networkReply = nm->post(request, msg); return networkReply; } static QByteArray processReply(QNetworkReply *networkReply, QNetworkReply::NetworkError &statusCode) { // Check if no network error occured if (networkReply->error() != QNetworkReply::NoError) { statusCode = networkReply->error(); return {}; } // Check if server answer with error auto errCode = networkReply->rawHeader(GrpcStatusHeader).toInt(); if (errCode != 0) { QStringList errstr; errstr << "grpc-status error code:" << Int2String(errCode) << ", error msg:" << QLatin1String(networkReply->rawHeader(GrpcStatusMessage)); MW_show_log(errstr.join(" ")); statusCode = QNetworkReply::NetworkError::ProtocolUnknownError; return {}; } statusCode = QNetworkReply::NetworkError::NoError; return networkReply->readAll().mid(GrpcMessageSizeHeaderSize); } QNetworkReply::NetworkError call(const QString &method, const QString &service, const QByteArray &args, QByteArray &qByteArray, int timeout_ms) { QNetworkReply *networkReply = post(method, service, args); QTimer *abortTimer = nullptr; if (timeout_ms > 0) { abortTimer = new QTimer; abortTimer->setSingleShot(true); abortTimer->setInterval(timeout_ms); QObject::connect(abortTimer, &QTimer::timeout, networkReply, &QNetworkReply::abort); abortTimer->start(); } { QEventLoop loop; QObject::connect(networkReply, &QNetworkReply::finished, &loop, &QEventLoop::quit); loop.exec(); } if (abortTimer != nullptr) { abortTimer->stop(); abortTimer->deleteLater(); } auto grpcStatus = QNetworkReply::NetworkError::ProtocolUnknownError; qByteArray = processReply(networkReply, grpcStatus); // qDebug() << __func__ << "RECV: " << qByteArray.toHex() << "grpcStatus" << grpcStatus; // qDebug() << networkReply->rawHeaderPairs(); networkReply->deleteLater(); return grpcStatus; } public: Http2GrpcChannelPrivate(const QString &url_, const QString &nekoray_auth_, const QString &serviceName_) { url_base = "http://" + url_; nekoray_auth = nekoray_auth_.toLatin1(); serviceName = serviceName_; // thread = new QThread; nm = new QNetworkAccessManager(); nm->setCache(new NoCache); nm->moveToThread(thread); thread->start(); } ~Http2GrpcChannelPrivate() { nm->deleteLater(); thread->quit(); thread->wait(); thread->deleteLater(); } QNetworkReply::NetworkError Call(const QString &methodName, const google::protobuf::Message &req, google::protobuf::Message *rsp, int timeout_ms = 0) { if (!NekoGui::dataStore->core_running) return QNetworkReply::NetworkError(-1919); std::string reqStr; req.SerializeToString(&reqStr); auto requestArray = QByteArray::fromStdString(reqStr); QByteArray responseArray; QNetworkReply::NetworkError err; QMutex lock; lock.lock(); runOnUiThread( [&] { err = call(methodName, serviceName, requestArray, responseArray, timeout_ms); lock.unlock(); }, nm); lock.lock(); lock.unlock(); // qDebug() << "rsp err" << err; // qDebug() << "rsp array" << responseArray; if (err != QNetworkReply::NetworkError::NoError) { return err; } if (!rsp->ParseFromArray(responseArray.data(), responseArray.size())) { return QNetworkReply::NetworkError(-114514); } return QNetworkReply::NetworkError::NoError; } }; } // namespace QtGrpc namespace NekoGui_rpc { Client::Client(std::function onError, const QString &target, const QString &token) { this->make_grpc_channel = [=]() { return std::make_unique(target, token, "libcore.LibcoreService"); }; this->default_grpc_channel = make_grpc_channel(); this->onError = std::move(onError); } #define NOT_OK \ *rpcOK = false; \ onError(QStringLiteral("QNetworkReply::NetworkError code: %1\n").arg(status)); void Client::Exit() { libcore::EmptyReq request; libcore::EmptyResp reply; default_grpc_channel->Call("Exit", request, &reply, 500); } QString Client::Start(bool *rpcOK, const libcore::LoadConfigReq &request) { libcore::ErrorResp reply; auto status = default_grpc_channel->Call("Start", request, &reply); if (status == QNetworkReply::NoError) { *rpcOK = true; return {reply.error().c_str()}; } else { NOT_OK return ""; } } QString Client::Stop(bool *rpcOK) { libcore::EmptyReq request; libcore::ErrorResp reply; auto status = default_grpc_channel->Call("Stop", request, &reply); if (status == QNetworkReply::NoError) { *rpcOK = true; return {reply.error().c_str()}; } else { NOT_OK return ""; } } long long Client::QueryStats(const std::string &tag, const std::string &direct) { libcore::QueryStatsReq request; request.set_tag(tag); request.set_direct(direct); libcore::QueryStatsResp reply; auto status = default_grpc_channel->Call("QueryStats", request, &reply, 500); if (status == QNetworkReply::NoError) { return reply.traffic(); } else { return 0; } } std::string Client::ListConnections() { libcore::EmptyReq request; libcore::ListConnectionsResp reply; auto status = default_grpc_channel->Call("ListConnections", request, &reply, 500); if (status == QNetworkReply::NoError) { return reply.nekoray_connections_json(); } else { return ""; } } // libcore::TestResp Client::Test(bool *rpcOK, const libcore::TestReq &request) { libcore::TestResp reply; auto status = make_grpc_channel()->Call("Test", request, &reply); if (status == QNetworkReply::NoError) { *rpcOK = true; return reply; } else { NOT_OK return reply; } } libcore::UpdateResp Client::Update(bool *rpcOK, const libcore::UpdateReq &request) { libcore::UpdateResp reply; auto status = default_grpc_channel->Call("Update", request, &reply); if (status == QNetworkReply::NoError) { *rpcOK = true; return reply; } else { NOT_OK return reply; } } } // namespace NekoGui_rpc #endif ================================================ FILE: rpc/gRPC.h ================================================ #pragma once #ifndef NKR_NO_GRPC #include "go/grpc_server/gen/libcore.pb.h" #include namespace QtGrpc { class Http2GrpcChannelPrivate; } namespace NekoGui_rpc { class Client { public: explicit Client(std::function onError, const QString &target, const QString &token); void Exit(); bool KeepAlive(); // QString returns is error string QString Start(bool *rpcOK, const libcore::LoadConfigReq &request); QString Stop(bool *rpcOK); long long QueryStats(const std::string &tag, const std::string &direct); std::string ListConnections(); libcore::TestResp Test(bool *rpcOK, const libcore::TestReq &request); libcore::UpdateResp Update(bool *rpcOK, const libcore::UpdateReq &request); private: std::function()> make_grpc_channel; std::unique_ptr default_grpc_channel; std::function onError; }; inline Client *defaultClient; } // namespace NekoGui_rpc #endif ================================================ FILE: sub/GroupUpdater.cpp ================================================ #include "db/ProfileFilter.hpp" #include "fmt/includes.h" #include "fmt/Preset.hpp" #include "main/HTTPRequestHelper.hpp" #include "GroupUpdater.hpp" #include #include #ifndef NKR_NO_YAML #include #endif namespace NekoGui_sub { GroupUpdater *groupUpdater = new GroupUpdater; void RawUpdater_FixEnt(const std::shared_ptr &ent) { if (ent == nullptr) return; auto stream = NekoGui_fmt::GetStreamSettings(ent->bean.get()); if (stream == nullptr) return; // 1. "security" if (stream->security == "none" || stream->security == "0" || stream->security == "false") { stream->security = ""; } else if (stream->security == "1" || stream->security == "true") { stream->security = "tls"; } // 2. TLS SNI: v2rayN config builder generate sni like this, so set sni here for their format. if (stream->security == "tls" && IsIpAddress(ent->bean->serverAddress) && (!stream->host.isEmpty()) && stream->sni.isEmpty()) { stream->sni = stream->host; } } void RawUpdater::update(const QString &str) { // Base64 encoded subscription if (auto str2 = DecodeB64IfValid(str); !str2.isEmpty()) { update(str2); return; } // Clash if (str.contains("proxies:")) { updateClash(str); return; } // Multi line if (str.count("\n") > 0) { auto list = str.split("\n"); for (const auto &str2: list) { update(str2.trimmed()); } return; } std::shared_ptr ent; bool needFix = true; // Nekoray format if (str.startsWith("nekoray://")) { needFix = false; auto link = QUrl(str); if (!link.isValid()) return; ent = NekoGui::ProfileManager::NewProxyEntity(link.host()); if (ent->bean->version == -114514) return; auto j = DecodeB64IfValid(link.fragment().toUtf8(), QByteArray::Base64UrlEncoding); if (j.isEmpty()) return; ent->bean->FromJsonBytes(j); } // SOCKS if (str.startsWith("socks5://") || str.startsWith("socks4://") || str.startsWith("socks4a://") || str.startsWith("socks://")) { ent = NekoGui::ProfileManager::NewProxyEntity("socks"); auto ok = ent->SocksHTTPBean()->TryParseLink(str); if (!ok) return; } // HTTP if (str.startsWith("http://") || str.startsWith("https://")) { ent = NekoGui::ProfileManager::NewProxyEntity("http"); auto ok = ent->SocksHTTPBean()->TryParseLink(str); if (!ok) return; } // ShadowSocks if (str.startsWith("ss://")) { ent = NekoGui::ProfileManager::NewProxyEntity("shadowsocks"); auto ok = ent->ShadowSocksBean()->TryParseLink(str); if (!ok) return; } // VMess if (str.startsWith("vmess://")) { ent = NekoGui::ProfileManager::NewProxyEntity("vmess"); auto ok = ent->VMessBean()->TryParseLink(str); if (!ok) return; } // VLESS if (str.startsWith("vless://")) { ent = NekoGui::ProfileManager::NewProxyEntity("vless"); auto ok = ent->TrojanVLESSBean()->TryParseLink(str); if (!ok) return; } // Trojan if (str.startsWith("trojan://")) { ent = NekoGui::ProfileManager::NewProxyEntity("trojan"); auto ok = ent->TrojanVLESSBean()->TryParseLink(str); if (!ok) return; } // Naive if (str.startsWith("naive+")) { needFix = false; ent = NekoGui::ProfileManager::NewProxyEntity("naive"); auto ok = ent->NaiveBean()->TryParseLink(str); if (!ok) return; } // Hysteria2 if (str.startsWith("hysteria2://") || str.startsWith("hy2://")) { needFix = false; ent = NekoGui::ProfileManager::NewProxyEntity("hysteria2"); auto ok = ent->QUICBean()->TryParseLink(str); if (!ok) return; } // TUIC if (str.startsWith("tuic://")) { needFix = false; ent = NekoGui::ProfileManager::NewProxyEntity("tuic"); auto ok = ent->QUICBean()->TryParseLink(str); if (!ok) return; } if (ent == nullptr) return; // Fix if (needFix) RawUpdater_FixEnt(ent); // End NekoGui::profileManager->AddProfile(ent, gid_add_to); updated_order += ent; } #ifndef NKR_NO_YAML QString Node2QString(const YAML::Node &n, const QString &def = "") { try { return n.as().c_str(); } catch (const YAML::Exception &ex) { qDebug() << ex.what(); return def; } } QStringList Node2QStringList(const YAML::Node &n) { try { if (n.IsSequence()) { QStringList list; for (auto item: n) { list << item.as().c_str(); } return list; } else { return {}; } } catch (const YAML::Exception &ex) { qDebug() << ex.what(); return {}; } } int Node2Int(const YAML::Node &n, const int &def = 0) { try { return n.as(); } catch (const YAML::Exception &ex) { qDebug() << ex.what(); return def; } } bool Node2Bool(const YAML::Node &n, const bool &def = false) { try { return n.as(); } catch (const YAML::Exception &ex) { try { return n.as(); } catch (const YAML::Exception &ex2) { qDebug() << ex2.what(); } qDebug() << ex.what(); return def; } } // NodeChild returns the first defined children or Null Node YAML::Node NodeChild(const YAML::Node &n, const std::list &keys) { for (const auto &key: keys) { auto child = n[key]; if (child.IsDefined()) return child; } return {}; } #endif // https://github.com/Dreamacro/clash/wiki/configuration void RawUpdater::updateClash(const QString &str) { #ifndef NKR_NO_YAML try { auto proxies = YAML::Load(str.toStdString())["proxies"]; for (auto proxy: proxies) { auto type = Node2QString(proxy["type"]).toLower(); auto type_clash = type; if (type == "ss" || type == "ssr") type = "shadowsocks"; if (type == "socks5") type = "socks"; auto ent = NekoGui::ProfileManager::NewProxyEntity(type); if (ent->bean->version == -114514) continue; bool needFix = false; // common ent->bean->name = Node2QString(proxy["name"]); ent->bean->serverAddress = Node2QString(proxy["server"]); ent->bean->serverPort = Node2Int(proxy["port"]); if (type_clash == "ss") { auto bean = ent->ShadowSocksBean(); bean->method = Node2QString(proxy["cipher"]).replace("dummy", "none"); bean->password = Node2QString(proxy["password"]); auto plugin_n = proxy["plugin"]; auto pluginOpts_n = proxy["plugin-opts"]; // UDP over TCP if (Node2Bool(proxy["udp-over-tcp"])) { bean->uot = Node2Int(proxy["udp-over-tcp-version"]); if (bean->uot == 0) bean->uot = 2; } if (plugin_n.IsDefined() && pluginOpts_n.IsDefined()) { QStringList ssPlugin; auto plugin = Node2QString(plugin_n); if (plugin == "obfs") { ssPlugin << "obfs-local"; ssPlugin << "obfs=" + Node2QString(pluginOpts_n["mode"]); ssPlugin << "obfs-host=" + Node2QString(pluginOpts_n["host"]); } else if (plugin == "v2ray-plugin") { auto mode = Node2QString(pluginOpts_n["mode"]); auto host = Node2QString(pluginOpts_n["host"]); auto path = Node2QString(pluginOpts_n["path"]); ssPlugin << "v2ray-plugin"; if (!mode.isEmpty() && mode != "websocket") ssPlugin << "mode=" + mode; if (Node2Bool(pluginOpts_n["tls"])) ssPlugin << "tls"; if (!host.isEmpty()) ssPlugin << "host=" + host; if (!path.isEmpty()) ssPlugin << "path=" + path; // clash only: skip-cert-verify // clash only: headers // clash: mux=? } bean->plugin = ssPlugin.join(";"); } // sing-mux auto smux = NodeChild(proxy, {"smux"}); if (Node2Bool(smux["enabled"])) bean->stream->multiplex_status = 1; } else if (type == "socks" || type == "http") { auto bean = ent->SocksHTTPBean(); bean->username = Node2QString(proxy["username"]); bean->password = Node2QString(proxy["password"]); if (Node2Bool(proxy["tls"])) bean->stream->security = "tls"; if (Node2Bool(proxy["skip-cert-verify"])) bean->stream->allow_insecure = true; } else if (type == "trojan" || type == "vless") { needFix = true; auto bean = ent->TrojanVLESSBean(); if (type == "vless") { bean->flow = Node2QString(proxy["flow"]); bean->password = Node2QString(proxy["uuid"]); // meta packet encoding if (Node2Bool(proxy["packet-addr"])) { bean->stream->packet_encoding = "packetaddr"; } else { // For VLESS, default to use xudp bean->stream->packet_encoding = "xudp"; } } else { bean->password = Node2QString(proxy["password"]); } bean->stream->security = "tls"; bean->stream->network = Node2QString(proxy["network"], "tcp"); bean->stream->sni = FIRST_OR_SECOND(Node2QString(proxy["sni"]), Node2QString(proxy["servername"])); bean->stream->alpn = Node2QStringList(proxy["alpn"]).join(","); bean->stream->allow_insecure = Node2Bool(proxy["skip-cert-verify"]); bean->stream->utlsFingerprint = Node2QString(proxy["client-fingerprint"]); if (bean->stream->utlsFingerprint.isEmpty()) { bean->stream->utlsFingerprint = NekoGui::dataStore->utlsFingerprint; } // sing-mux auto smux = NodeChild(proxy, {"smux"}); if (Node2Bool(smux["enabled"])) bean->stream->multiplex_status = 1; // opts auto ws = NodeChild(proxy, {"ws-opts", "ws-opt"}); if (ws.IsMap()) { auto headers = ws["headers"]; for (auto header: headers) { if (Node2QString(header.first).toLower() == "host") { bean->stream->host = Node2QString(header.second); } } bean->stream->path = Node2QString(ws["path"]); bean->stream->ws_early_data_length = Node2Int(ws["max-early-data"]); bean->stream->ws_early_data_name = Node2QString(ws["early-data-header-name"]); } auto grpc = NodeChild(proxy, {"grpc-opts", "grpc-opt"}); if (grpc.IsMap()) { bean->stream->path = Node2QString(grpc["grpc-service-name"]); } auto reality = NodeChild(proxy, {"reality-opts"}); if (reality.IsMap()) { bean->stream->reality_pbk = Node2QString(reality["public-key"]); bean->stream->reality_sid = Node2QString(reality["short-id"]); } } else if (type == "vmess") { needFix = true; auto bean = ent->VMessBean(); bean->uuid = Node2QString(proxy["uuid"]); bean->aid = Node2Int(proxy["alterId"]); bean->security = Node2QString(proxy["cipher"], bean->security); bean->stream->network = Node2QString(proxy["network"], "tcp").replace("h2", "http"); bean->stream->sni = FIRST_OR_SECOND(Node2QString(proxy["sni"]), Node2QString(proxy["servername"])); bean->stream->alpn = Node2QStringList(proxy["alpn"]).join(","); if (Node2Bool(proxy["tls"])) bean->stream->security = "tls"; if (Node2Bool(proxy["skip-cert-verify"])) bean->stream->allow_insecure = true; bean->stream->utlsFingerprint = Node2QString(proxy["client-fingerprint"]); bean->stream->utlsFingerprint = Node2QString(proxy["client-fingerprint"]); if (bean->stream->utlsFingerprint.isEmpty()) { bean->stream->utlsFingerprint = NekoGui::dataStore->utlsFingerprint; } // sing-mux auto smux = NodeChild(proxy, {"smux"}); if (Node2Bool(smux["enabled"])) bean->stream->multiplex_status = 1; // meta packet encoding if (Node2Bool(proxy["xudp"])) bean->stream->packet_encoding = "xudp"; if (Node2Bool(proxy["packet-addr"])) bean->stream->packet_encoding = "packetaddr"; // opts auto ws = NodeChild(proxy, {"ws-opts", "ws-opt"}); if (ws.IsMap()) { auto headers = ws["headers"]; for (auto header: headers) { if (Node2QString(header.first).toLower() == "host") { bean->stream->host = Node2QString(header.second); } } bean->stream->path = Node2QString(ws["path"]); bean->stream->ws_early_data_length = Node2Int(ws["max-early-data"]); bean->stream->ws_early_data_name = Node2QString(ws["early-data-header-name"]); // for Xray if (Node2QString(ws["early-data-header-name"]) == "Sec-WebSocket-Protocol") { bean->stream->path += "?ed=" + Node2QString(ws["max-early-data"]); } } auto grpc = NodeChild(proxy, {"grpc-opts", "grpc-opt"}); if (grpc.IsMap()) { bean->stream->path = Node2QString(grpc["grpc-service-name"]); } auto h2 = NodeChild(proxy, {"h2-opts", "h2-opt"}); if (h2.IsMap()) { auto hosts = h2["host"]; for (auto host: hosts) { bean->stream->host = Node2QString(host); break; } bean->stream->path = Node2QString(h2["path"]); } auto tcp_http = NodeChild(proxy, {"http-opts", "http-opt"}); if (tcp_http.IsMap()) { bean->stream->network = "tcp"; bean->stream->header_type = "http"; auto headers = tcp_http["headers"]; for (auto header: headers) { if (Node2QString(header.first).toLower() == "host") { bean->stream->host = Node2QString(header.second[0]); } break; } auto paths = tcp_http["path"]; for (auto path: paths) { bean->stream->path = Node2QString(path); break; } } } else if (type == "hysteria2") { auto bean = ent->QUICBean(); bean->hopPort = Node2QString(proxy["ports"]); bean->allowInsecure = Node2Bool(proxy["skip-cert-verify"]); bean->caText = Node2QString(proxy["ca-str"]); bean->sni = Node2QString(proxy["sni"]); bean->obfsPassword = Node2QString(proxy["obfs-password"]); bean->password = Node2QString(proxy["password"]); bean->uploadMbps = Node2QString(proxy["up"]).split(" ")[0].toInt(); bean->downloadMbps = Node2QString(proxy["down"]).split(" ")[0].toInt(); } else if (type == "tuic") { auto bean = ent->QUICBean(); bean->uuid = Node2QString(proxy["uuid"]); bean->password = Node2QString(proxy["password"]); if (Node2Int(proxy["heartbeat-interval"]) != 0) { bean->heartbeat = Int2String(Node2Int(proxy["heartbeat-interval"])) + "ms"; } bean->udpRelayMode = Node2QString(proxy["udp-relay-mode"], bean->udpRelayMode); bean->congestionControl = Node2QString(proxy["congestion-controller"], bean->congestionControl); bean->disableSni = Node2Bool(proxy["disable-sni"]); bean->zeroRttHandshake = Node2Bool(proxy["reduce-rtt"]); bean->allowInsecure = Node2Bool(proxy["skip-cert-verify"]); bean->alpn = Node2QStringList(proxy["alpn"]).join(","); bean->caText = Node2QString(proxy["ca-str"]); bean->sni = Node2QString(proxy["sni"]); if (Node2Bool(proxy["udp-over-stream"])) bean->uos = true; if (!Node2QString(proxy["ip"]).isEmpty()) { if (bean->sni.isEmpty()) bean->sni = bean->serverAddress; bean->serverAddress = Node2QString(proxy["ip"]); } } else { continue; } if (needFix) RawUpdater_FixEnt(ent); NekoGui::profileManager->AddProfile(ent, gid_add_to); updated_order += ent; } } catch (const YAML::Exception &ex) { runOnUiThread([=] { MessageBoxWarning("YAML Exception", ex.what()); }); } #endif } // 在新的 thread 运行 void GroupUpdater::AsyncUpdate(const QString &str, int _sub_gid, const std::function &finish) { auto content = str.trimmed(); bool asURL = false; bool createNewGroup = false; if (_sub_gid < 0 && (content.startsWith("http://") || content.startsWith("https://"))) { auto items = QStringList{ QObject::tr("As Subscription (add to this group)"), QObject::tr("As Subscription (create new group)"), QObject::tr("As link"), }; bool ok; auto a = QInputDialog::getItem(nullptr, QObject::tr("url detected"), QObject::tr("%1\nHow to update?").arg(content), items, 0, false, &ok); if (!ok) return; if (items.indexOf(a) <= 1) asURL = true; if (items.indexOf(a) == 1) createNewGroup = true; } runOnNewThread([=] { auto gid = _sub_gid; if (createNewGroup) { auto group = NekoGui::ProfileManager::NewGroup(); group->name = QUrl(str).host(); group->url = str; NekoGui::profileManager->AddGroup(group); gid = group->id; MW_dialog_message("SubUpdater", "NewGroup"); } Update(str, gid, asURL); emit asyncUpdateCallback(gid); if (finish != nullptr) finish(); }); } void GroupUpdater::Update(const QString &_str, int _sub_gid, bool _not_sub_as_url) { // 创建 rawUpdater NekoGui::dataStore->imported_count = 0; auto rawUpdater = std::make_unique(); rawUpdater->gid_add_to = _sub_gid; // 准备 QString sub_user_info; bool asURL = _sub_gid >= 0 || _not_sub_as_url; // 把 _str 当作 url 处理(下载内容) auto content = _str.trimmed(); auto group = NekoGui::profileManager->GetGroup(_sub_gid); if (group != nullptr && group->archive) return; // 网络请求 if (asURL) { auto groupName = group == nullptr ? content : group->name; MW_show_log(">>>>>>>> " + QObject::tr("Requesting subscription: %1").arg(groupName)); auto resp = NetworkRequestHelper::HttpGet(content); if (!resp.error.isEmpty()) { MW_show_log("<<<<<<<< " + QObject::tr("Requesting subscription %1 error: %2").arg(groupName, resp.error + "\n" + resp.data)); return; } content = resp.data; sub_user_info = NetworkRequestHelper::GetHeader(resp.header, "Subscription-UserInfo"); MW_show_log("<<<<<<<< " + QObject::tr("Subscription request fininshed: %1").arg(groupName)); } QList> in; // 更新前 QList> out_all; // 更新前 + 更新后 QList> out; // 更新后 QList> only_in; // 只在更新前有的 QList> only_out; // 只在更新后有的 QList> update_del; // 更新前后都有的,需要删除的新配置 QList> update_keep; // 更新前后都有的,被保留的旧配置 // 订阅解析前 if (group != nullptr) { in = group->Profiles(); group->sub_last_update = QDateTime::currentMSecsSinceEpoch() / 1000; group->info = sub_user_info; group->order.clear(); group->Save(); // if (NekoGui::dataStore->sub_clear) { MW_show_log(QObject::tr("Clearing servers...")); for (const auto &profile: in) { NekoGui::profileManager->DeleteProfile(profile->id); } } } // 解析并添加 profile rawUpdater->update(content); if (group != nullptr) { out_all = group->Profiles(); QString change_text; if (NekoGui::dataStore->sub_clear) { // all is new profile for (const auto &ent: out_all) { change_text += "[+] " + ent->bean->DisplayTypeAndName() + "\n"; } } else { // find and delete not updated profile by ProfileFilter NekoGui::ProfileFilter::OnlyInSrc_ByPointer(out_all, in, out); NekoGui::ProfileFilter::OnlyInSrc(in, out, only_in); NekoGui::ProfileFilter::OnlyInSrc(out, in, only_out); NekoGui::ProfileFilter::Common(in, out, update_keep, update_del, false); QString notice_added; QString notice_deleted; for (const auto &ent: only_out) { notice_added += "[+] " + ent->bean->DisplayTypeAndName() + "\n"; } for (const auto &ent: only_in) { notice_deleted += "[-] " + ent->bean->DisplayTypeAndName() + "\n"; } // sort according to order in remote group->order = {}; for (const auto &ent: rawUpdater->updated_order) { auto deleted_index = update_del.indexOf(ent); if (deleted_index > 0) { if (deleted_index >= update_keep.count()) continue; // should not happen auto ent2 = update_keep[deleted_index]; group->order.append(ent2->id); } else { group->order.append(ent->id); } } group->Save(); // cleanup for (const auto &ent: out_all) { if (!group->order.contains(ent->id)) { NekoGui::profileManager->DeleteProfile(ent->id); } } change_text = "\n" + QObject::tr("Added %1 profiles:\n%2\nDeleted %3 Profiles:\n%4") .arg(only_out.length()) .arg(notice_added) .arg(only_in.length()) .arg(notice_deleted); if (only_out.length() + only_in.length() == 0) change_text = QObject::tr("Nothing"); } MW_show_log("<<<<<<<< " + QObject::tr("Change of %1:").arg(group->name) + "\n" + change_text); MW_dialog_message("SubUpdater", "finish-dingyue"); } else { NekoGui::dataStore->imported_count = rawUpdater->updated_order.count(); MW_dialog_message("SubUpdater", "finish"); } } } // namespace NekoGui_sub bool UI_update_all_groups_Updating = false; #define should_skip_group(g) (g == nullptr || g->url.isEmpty() || g->archive || (onlyAllowed && g->skip_auto_update)) void serialUpdateSubscription(const QList &groupsTabOrder, int _order, bool onlyAllowed) { if (_order >= groupsTabOrder.size()) { UI_update_all_groups_Updating = false; return; } // calculate this group auto group = NekoGui::profileManager->GetGroup(groupsTabOrder[_order]); if (group == nullptr || should_skip_group(group)) { serialUpdateSubscription(groupsTabOrder, _order + 1, onlyAllowed); return; } int nextOrder = _order + 1; while (nextOrder < groupsTabOrder.size()) { auto nextGid = groupsTabOrder[nextOrder]; auto nextGroup = NekoGui::profileManager->GetGroup(nextGid); if (!should_skip_group(nextGroup)) { break; } nextOrder += 1; } // Async update current group UI_update_all_groups_Updating = true; NekoGui_sub::groupUpdater->AsyncUpdate(group->url, group->id, [=] { serialUpdateSubscription(groupsTabOrder, nextOrder, onlyAllowed); }); } void UI_update_all_groups(bool onlyAllowed) { if (UI_update_all_groups_Updating) { MW_show_log("The last subscription update has not exited."); return; } auto groupsTabOrder = NekoGui::profileManager->groupsTabOrder; serialUpdateSubscription(groupsTabOrder, 0, onlyAllowed); } ================================================ FILE: sub/GroupUpdater.hpp ================================================ #pragma once #include "db/Database.hpp" namespace NekoGui_sub { class RawUpdater { public: void updateClash(const QString &str); void update(const QString &str); int gid_add_to = -1; // 导入到指定组 -1 为当前选中组 QList> updated_order; // 新增的配置,按照导入时处理的先后排序 }; class GroupUpdater : public QObject { Q_OBJECT public: void AsyncUpdate(const QString &str, int _sub_gid = -1, const std::function &finish = nullptr); void Update(const QString &_str, int _sub_gid = -1, bool _not_sub_as_url = false); signals: void asyncUpdateCallback(int gid); }; extern GroupUpdater *groupUpdater; } // namespace NekoGui_sub // 更新所有订阅 关闭分组窗口时 更新动作继续执行 void UI_update_all_groups(bool onlyAllowed = false); ================================================ FILE: sys/AutoRun.cpp ================================================ #include "AutoRun.hpp" #include #include #include "3rdparty/fix_old_qt.h" #include "main/NekoGui.hpp" // macOS headers (possibly OBJ-c) #if defined(Q_OS_MACOS) #include #include #endif #ifdef Q_OS_WIN #include QString Windows_GenAutoRunString() { auto appPath = QApplication::applicationFilePath(); appPath = "\"" + QDir::toNativeSeparators(appPath) + "\""; appPath += " -tray"; return appPath; } void AutoRun_SetEnabled(bool enable) { // 以程序名称作为注册表中的键 // 根据键获取对应的值(程序路径) auto appPath = QApplication::applicationFilePath(); QFileInfo fInfo(appPath); QString name = fInfo.baseName(); QSettings settings("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat); if (enable) { settings.setValue(name, Windows_GenAutoRunString()); } else { settings.remove(name); } } bool AutoRun_IsEnabled() { QSettings settings("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat); // 以程序名称作为注册表中的键 // 根据键获取对应的值(程序路径) auto appPath = QApplication::applicationFilePath(); QFileInfo fInfo(appPath); QString name = fInfo.baseName(); return settings.value(name).toString() == Windows_GenAutoRunString(); } #endif #ifdef Q_OS_MACOS void AutoRun_SetEnabled(bool enable) { // From // https://github.com/nextcloud/desktop/blob/master/src/common/utility_mac.cpp QString filePath = QDir(QCoreApplication::applicationDirPath() + QLatin1String("/../..")).absolutePath(); CFStringRef folderCFStr = CFStringCreateWithCString(0, filePath.toUtf8().data(), kCFStringEncodingUTF8); CFURLRef urlRef = CFURLCreateWithFileSystemPath(0, folderCFStr, kCFURLPOSIXPathStyle, true); LSSharedFileListRef loginItems = LSSharedFileListCreate(0, kLSSharedFileListSessionLoginItems, 0); if (loginItems && enable) { // Insert an item to the list. LSSharedFileListItemRef item = LSSharedFileListInsertItemURL(loginItems, kLSSharedFileListItemLast, 0, 0, urlRef, 0, 0); if (item) CFRelease(item); CFRelease(loginItems); } else if (loginItems && !enable) { // We need to iterate over the items and check which one is "ours". UInt32 seedValue; CFArrayRef itemsArray = LSSharedFileListCopySnapshot(loginItems, &seedValue); CFStringRef appUrlRefString = CFURLGetString(urlRef); for (int i = 0; i < CFArrayGetCount(itemsArray); i++) { LSSharedFileListItemRef item = (LSSharedFileListItemRef) CFArrayGetValueAtIndex(itemsArray, i); CFURLRef itemUrlRef = NULL; if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr && itemUrlRef) { CFStringRef itemUrlString = CFURLGetString(itemUrlRef); if (CFStringCompare(itemUrlString, appUrlRefString, 0) == kCFCompareEqualTo) { LSSharedFileListItemRemove(loginItems, item); // remove it! } CFRelease(itemUrlRef); } } CFRelease(itemsArray); CFRelease(loginItems); } CFRelease(folderCFStr); CFRelease(urlRef); } bool AutoRun_IsEnabled() { // From // https://github.com/nextcloud/desktop/blob/master/src/common/utility_mac.cpp // this is quite some duplicate code with setLaunchOnStartup, at some // point we should fix this FIXME. bool returnValue = false; QString filePath = QDir(QCoreApplication::applicationDirPath() + QLatin1String("/../..")).absolutePath(); CFStringRef folderCFStr = CFStringCreateWithCString(0, filePath.toUtf8().data(), kCFStringEncodingUTF8); CFURLRef urlRef = CFURLCreateWithFileSystemPath(0, folderCFStr, kCFURLPOSIXPathStyle, true); LSSharedFileListRef loginItems = LSSharedFileListCreate(0, kLSSharedFileListSessionLoginItems, 0); if (loginItems) { // We need to iterate over the items and check which one is "ours". UInt32 seedValue; CFArrayRef itemsArray = LSSharedFileListCopySnapshot(loginItems, &seedValue); CFStringRef appUrlRefString = CFURLGetString(urlRef); // no need for release for (int i = 0; i < CFArrayGetCount(itemsArray); i++) { LSSharedFileListItemRef item = (LSSharedFileListItemRef) CFArrayGetValueAtIndex(itemsArray, i); CFURLRef itemUrlRef = NULL; if (LSSharedFileListItemResolve(item, 0, &itemUrlRef, NULL) == noErr && itemUrlRef) { CFStringRef itemUrlString = CFURLGetString(itemUrlRef); if (CFStringCompare(itemUrlString, appUrlRefString, 0) == kCFCompareEqualTo) { returnValue = true; } CFRelease(itemUrlRef); } } CFRelease(itemsArray); } CFRelease(loginItems); CFRelease(folderCFStr); CFRelease(urlRef); return returnValue; } #endif #ifdef Q_OS_LINUX #include #include #include #define NEWLINE "\n" // launchatlogin.cpp // ShadowClash // // Created by TheWanderingCoel on 2018/6/12. // Copyright © 2019 Coel Wu. All rights reserved. // QString getUserAutostartDir_private() { QString config = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); config += QLatin1String("/autostart/"); return config; } void AutoRun_SetEnabled(bool enable) { // From https://github.com/nextcloud/desktop/blob/master/src/common/utility_unix.cpp QString appName = QCoreApplication::applicationName(); QString userAutoStartPath = getUserAutostartDir_private(); QString desktopFileLocation = userAutoStartPath + appName + QLatin1String(".desktop"); QStringList appCmdList; // nekoray: launcher if (qEnvironmentVariable("NKR_FROM_LAUNCHER") == "1") { appCmdList << QApplication::applicationDirPath() + "/launcher" << "--"; } else { if (QProcessEnvironment::systemEnvironment().contains("APPIMAGE")) { appCmdList << QProcessEnvironment::systemEnvironment().value("APPIMAGE"); } else { appCmdList << QApplication::applicationFilePath(); } } appCmdList << "-tray"; if (NekoGui::dataStore->flag_use_appdata) { appCmdList << "-appdata"; } if (enable) { if (!QDir().exists(userAutoStartPath) && !QDir().mkpath(userAutoStartPath)) { // qCWarning(lcUtility) << "Could not create autostart folder" // << userAutoStartPath; return; } QFile iniFile(desktopFileLocation); if (!iniFile.open(QIODevice::WriteOnly)) { // qCWarning(lcUtility) << "Could not write auto start entry" << // desktopFileLocation; return; } QTextStream ts(&iniFile); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) ts.setCodec("UTF-8"); #endif ts << QLatin1String("[Desktop Entry]") << NEWLINE << QLatin1String("Name=") << appName << NEWLINE << QLatin1String("Exec=") << appCmdList.join(" ") << NEWLINE << QLatin1String("Terminal=") << "false" << NEWLINE << QLatin1String("Categories=") << "Network" << NEWLINE << QLatin1String("Type=") << "Application" << NEWLINE << QLatin1String("StartupNotify=") << "false" << NEWLINE << QLatin1String("X-GNOME-Autostart-enabled=") << "true" << NEWLINE; ts.flush(); iniFile.close(); } else { QFile::remove(desktopFileLocation); } } bool AutoRun_IsEnabled() { QString appName = QCoreApplication::applicationName(); QString desktopFileLocation = getUserAutostartDir_private() + appName + QLatin1String(".desktop"); return QFile::exists(desktopFileLocation); } #endif ================================================ FILE: sys/AutoRun.hpp ================================================ #pragma once void AutoRun_SetEnabled(bool enable); bool AutoRun_IsEnabled(); ================================================ FILE: sys/ExternalProcess.cpp ================================================ #include "ExternalProcess.hpp" #include "main/NekoGui.hpp" #include #include #include #include namespace NekoGui_sys { ExternalProcess::ExternalProcess() : QProcess() { // qDebug() << "[Debug] ExternalProcess()" << this << running_ext; this->env = QProcessEnvironment::systemEnvironment().toStringList(); } ExternalProcess::~ExternalProcess() { // qDebug() << "[Debug] ~ExternalProcess()" << this << running_ext; } void ExternalProcess::Start() { if (started) return; started = true; if (managed) { connect(this, &QProcess::readyReadStandardOutput, this, [&]() { auto log = readAllStandardOutput(); if (logCounter.fetchAndAddRelaxed(log.count("\n")) > NekoGui::dataStore->max_log_line) return; MW_show_log_ext_vt100(log); }); connect(this, &QProcess::readyReadStandardError, this, [&]() { MW_show_log_ext_vt100(readAllStandardError().trimmed()); }); connect(this, &QProcess::errorOccurred, this, [&](QProcess::ProcessError error) { if (!killed) { crashed = true; MW_show_log_ext(tag, "errorOccurred:" + errorString()); MW_dialog_message("ExternalProcess", "Crashed"); } }); connect(this, &QProcess::stateChanged, this, [&](QProcess::ProcessState state) { if (state == QProcess::NotRunning) { if (killed) { // 用户命令退出 MW_show_log_ext(tag, "External core stopped"); } else if (!crashed) { // 异常退出 crashed = true; MW_show_log_ext(tag, "[Error] Program exited accidentally: " + errorString()); Kill(); MW_dialog_message("ExternalProcess", "Crashed"); } } }); MW_show_log_ext(tag, "External core starting: " + env.join(" ") + " " + program + " " + arguments.join(" ")); } QProcess::setEnvironment(env); QProcess::start(program, arguments); } void ExternalProcess::Kill() { if (killed) return; killed = true; if (!crashed) { QProcess::kill(); QProcess::waitForFinished(500); } } // QElapsedTimer coreRestartTimer; CoreProcess::CoreProcess(const QString &core_path, const QStringList &args) : ExternalProcess() { ExternalProcess::managed = false; ExternalProcess::program = core_path; ExternalProcess::arguments = args; connect(this, &QProcess::readyReadStandardOutput, this, [&]() { auto log = readAllStandardOutput(); if (!NekoGui::dataStore->core_running) { if (log.contains("grpc server listening")) { // The core really started NekoGui::dataStore->core_running = true; if (start_profile_when_core_is_up >= 0) { MW_dialog_message("ExternalProcess", "CoreStarted," + Int2String(start_profile_when_core_is_up)); start_profile_when_core_is_up = -1; } } else if (log.contains("failed to serve")) { // The core failed to start QProcess::kill(); } } if (logCounter.fetchAndAddRelaxed(log.count("\n")) > NekoGui::dataStore->max_log_line) return; MW_show_log(log); }); connect(this, &QProcess::readyReadStandardError, this, [&]() { auto log = readAllStandardError().trimmed(); if (show_stderr) { MW_show_log(log); return; } if (log.contains("token is set")) { show_stderr = true; } }); connect(this, &QProcess::errorOccurred, this, [&](QProcess::ProcessError error) { if (error == QProcess::ProcessError::FailedToStart) { failed_to_start = true; MW_show_log("start core error occurred: " + errorString() + "\n"); } }); connect(this, &QProcess::stateChanged, this, [&](QProcess::ProcessState state) { if (state == QProcess::NotRunning) { NekoGui::dataStore->core_running = false; } if (!NekoGui::dataStore->prepare_exit && state == QProcess::NotRunning) { if (failed_to_start) return; // no retry if (restarting) return; MW_dialog_message("ExternalProcess", "CoreCrashed"); // Retry rate limit if (coreRestartTimer.isValid()) { if (coreRestartTimer.restart() < 10 * 1000) { coreRestartTimer = QElapsedTimer(); MW_show_log("[Error] " + QObject::tr("Core exits too frequently, stop automatic restart this profile.")); return; } } else { coreRestartTimer.start(); } // Restart start_profile_when_core_is_up = NekoGui::dataStore->started_id; MW_show_log("[Error] " + QObject::tr("Core exited, restarting.")); setTimeout([=] { Restart(); }, this, 1000); } }); } void CoreProcess::Start() { show_stderr = false; // cwd: same as GUI, at ./config ExternalProcess::Start(); write((NekoGui::dataStore->core_token + "\n").toUtf8()); } void CoreProcess::Restart() { restarting = true; QProcess::kill(); QProcess::waitForFinished(500); ExternalProcess::started = false; Start(); restarting = false; } } // namespace NekoGui_sys ================================================ FILE: sys/ExternalProcess.hpp ================================================ #pragma once #include #include namespace NekoGui_sys { class ExternalProcess : public QProcess { public: QString tag; QString program; QStringList arguments; QStringList env; bool managed = true; // MW_dialog_message ExternalProcess(); ~ExternalProcess(); // start & kill is one time virtual void Start(); void Kill(); protected: bool started = false; bool killed = false; bool crashed = false; }; class CoreProcess : public ExternalProcess { public: CoreProcess(const QString &core_path, const QStringList &args); void Start() override; void Restart(); int start_profile_when_core_is_up = -1; private: bool show_stderr = false; bool failed_to_start = false; bool restarting = false; }; // 手动管理 inline std::list> running_ext; inline QAtomicInt logCounter; } // namespace NekoGui_sys ================================================ FILE: sys/linux/LinuxCap.cpp ================================================ #include "LinuxCap.h" #include #include #include #define EXIT_CODE(p) (p.exitStatus() == QProcess::NormalExit ? p.exitCode() : -1) QString Linux_GetCapString(const QString &path) { QProcess p; p.setProgram(Linux_FindCapProgsExec("getcap")); p.setArguments({path}); p.start(); p.waitForFinished(500); return p.readAllStandardOutput(); } int Linux_Pkexec_SetCapString(const QString &path, const QString &cap) { QProcess p; p.setProgram("pkexec"); p.setArguments({Linux_FindCapProgsExec("setcap"), cap, path}); p.start(); p.waitForFinished(-1); return EXIT_CODE(p); } bool Linux_HavePkexec() { QProcess p; p.setProgram("pkexec"); p.setArguments({"--help"}); p.setProcessChannelMode(QProcess::SeparateChannels); p.start(); p.waitForFinished(500); return EXIT_CODE(p) == 0; } QString Linux_FindCapProgsExec(const QString &name) { QString exec = QStandardPaths::findExecutable(name); if (exec.isEmpty()) exec = QStandardPaths::findExecutable(name, {"/usr/sbin", "/sbin"}); if (exec.isEmpty()) qDebug() << "Executable" << name << "could not be resolved"; else qDebug() << "Found exec" << name << "at" << exec; return exec.isEmpty() ? name : exec; } ================================================ FILE: sys/linux/LinuxCap.h ================================================ #pragma once #include QString Linux_GetCapString(const QString &path); int Linux_Pkexec_SetCapString(const QString &path, const QString &cap); bool Linux_HavePkexec(); QString Linux_FindCapProgsExec(const QString &name); ================================================ FILE: sys/windows/MiniDump.cpp ================================================ #include "MiniDump.h" #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include #include #include #include #include #include #include typedef BOOL(WINAPI *MINIDUMPWRITEDUMP)( HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType, CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam); LONG __stdcall CreateCrashHandler(EXCEPTION_POINTERS *pException) { QDir::setCurrent(QApplication::applicationDirPath()); HMODULE DllHandle = NULL; DllHandle = LoadLibrary(_T("DBGHELP.DLL")); if (DllHandle) { MINIDUMPWRITEDUMP Dump = (MINIDUMPWRITEDUMP) GetProcAddress(DllHandle, "MiniDumpWriteDump"); if (Dump) { // 创建 Dump 文件 QDateTime CurDTime = QDateTime::currentDateTime(); QString current_date = CurDTime.toString("yyyy_MM_dd_hh_mm_ss"); // dmp文件的命名 QString dumpText = "Dump_" + current_date + ".dmp"; EXCEPTION_RECORD *record = pException->ExceptionRecord; QString errCode(QString::number(record->ExceptionCode, 16)); QString errAddr(QString::number((uintptr_t) record->ExceptionAddress, 16)); QString errFlag(QString::number(record->ExceptionFlags, 16)); QString errPara(QString::number(record->NumberParameters, 16)); HANDLE DumpHandle = CreateFile((LPCWSTR) dumpText.utf16(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (DumpHandle != INVALID_HANDLE_VALUE) { MINIDUMP_EXCEPTION_INFORMATION dumpInfo; dumpInfo.ExceptionPointers = pException; dumpInfo.ThreadId = GetCurrentThreadId(); dumpInfo.ClientPointers = TRUE; // 将dump信息写入dmp文件 Dump(GetCurrentProcess(), GetCurrentProcessId(), DumpHandle, MiniDumpNormal, &dumpInfo, NULL, NULL); CloseHandle(DumpHandle); } else { dumpText = ""; } // 创建消息提示 QMessageBox::warning(NULL, "Application crashed", QStringLiteral("ErrorCode: %1 ErrorAddr:%2 ErrorFlag: %3 ErrorPara: %4\nVersion: %5\nDump file at %6") .arg(errCode) .arg(errAddr) .arg(errFlag) .arg(errPara) .arg(NKR_VERSION) .arg(dumpText), QMessageBox::Ok); } } return EXCEPTION_EXECUTE_HANDLER; } void Windows_SetCrashHandler() { SetErrorMode(SEM_FAILCRITICALERRORS); SetUnhandledExceptionFilter(CreateCrashHandler); } ================================================ FILE: sys/windows/MiniDump.h ================================================ #pragma once void Windows_SetCrashHandler(); ================================================ FILE: sys/windows/guihelper.cpp ================================================ #include "guihelper.h" #include #include #include void Windows_QWidget_SetForegroundWindow(QWidget *w) { HWND hForgroundWnd = GetForegroundWindow(); DWORD dwForeID = ::GetWindowThreadProcessId(hForgroundWnd, NULL); DWORD dwCurID = ::GetCurrentThreadId(); ::AttachThreadInput(dwCurID, dwForeID, TRUE); ::SetForegroundWindow((HWND) w->winId()); ::AttachThreadInput(dwCurID, dwForeID, FALSE); } int isThisAdmin = -1; // cached bool Windows_IsInAdmin() { if (isThisAdmin >= 0) return isThisAdmin; isThisAdmin = IsUserAnAdmin(); return isThisAdmin; } ================================================ FILE: sys/windows/guihelper.h ================================================ #pragma once class QWidget; void Windows_QWidget_SetForegroundWindow(QWidget* w); bool Windows_IsInAdmin(); ================================================ FILE: test/test-qt512-sdk-build.sh ================================================ QT=$PWD/qtsdk/5.12.12/gcc_64 BUILD=build-sdk-qt512 rm -rf $BUILD mkdir -p $BUILD cd $BUILD cmake -GNinja -DCMAKE_PREFIX_PATH=$QT .. ninja ================================================ FILE: test/test-qt6-build.sh ================================================ rm -rf build-qt6 mkdir -p build-qt6 cd build-qt6 cmake -GNinja -DQT_VERSION_MAJOR=6 .. ninja ================================================ FILE: translations/fa_IR.ts ================================================ DialogBasicSettings Basic Settings تنظیمات پایه Enable فعال کردن Listen Address آدرس درحال شنود concurrency همزمانی User Agent عامل کاربر Common متداول Style استایل Theme پوسته System سیستم Subscription اشتراک Core هسته Extra Core هسته اضافی Select برگزیدن Edit ویرایش Custom Inbound ورودی سفارشی Concurrent هم زمان Use proxy when updating subscription استفاده از پروکسی زمانی که اشتراک را بروزرسانی می کنید Security امنیت security امنیت Statistics refresh rate نرخ تازه سازی آمار ترافیک Off خاموش Add اضافه کردن Delete حذف حذف کردن Please input the core name. لطفا نام هسته را وارد کنید Please select the core name. لطفا نام هسته را انتخاب کنید Connection statistics آمار اتصال Include Pre-release when checking update هنگام بررسی به‌روزرسانی، نسخه پیش‌انتشار را نیز لحاظ شود System proxy format فرمت پروکسی سیستم Set custom icon تنظیم آیکون سفارشی Hide dashboard at startup مخفی کردن داشبورد هنگام راه‌اندازی Clear servers before updating subscription قبل از به‌روزرسانی اشتراک، سرورها را پاک شود Ignore TLS errors when updating subscription هنگام به‌روزرسانی اشتراک، خطاهای TLS را نادیده گرفته شود Advanced system proxy settings. Please select a format. تنظیمات پیشرفته پروکسی سیستم. لطفا یک قالب را انتخاب کنید. Please select a PNG file. لطفاً یک فایل PNG انتخاب کنید. Reset بازنشانی Cancel لغو کردن Please select a valid square image. لطفاً یک تصویر مربع معتبر انتخاب کنید. Max log lines حداکثر خطوط فایل لاگ Inbound Auth اعتبار ورودی Username نام کاربری Password رمز عبور Skip TLS certificate authentication by default (allowInsecure) رد شدن از احراز هویت گواهی TLS به طور پیش فرض (allowInsecure) Default uTLS Fingerprint اثرانگشت پیشفرض uTLS Core Options تنظیمات هسته Override underlying DNS لغو دی ان اس زیربنایی Default On به صورت پیشفرض فعال Multiplex (mux) Multiplex (mux) Latency Test URL آدرس تست تاخیر Download Test URL آدرس تست دانلود Timeout (s) تایم اوت (به ثانیه) Automatic update آپدیت اتوماتیک Interval (minute, invalid if less than 30) فاصله (به دقیقا ، اگر کمتر از ۳۰ باشد نادرست است) Share VMess Link with v2rayN Format Old Share Link Format Mixed (SOCKS+HTTP) Listen Port DialogEditGroup Edit Group ویرایش گروه Type نوع Name نام Basic پایه Subscription اشتراک Archive بایگانی URL URL Copy profile share links لینک های اشتراک گذاری نمایه را کپی کنید Copy profile share links (Neko Links) لینک های اشتراک گذاری نمایه را کپی کنید (لینک های Neko) Warning هشدار Please input URL لطفا URL را وارد کنید Copied کپی شده است Manually column width عرض ستون به صورت دستی Front Proxy پروکسی front None هیچ یک Clear پاک کردن Skip automatic update لغو آپدیت اتوماتیک Common متداول Share اشتراک گذاری DialogEditProfile Edit ویرایش کردن Common متداول Type نوع Port پورت Address آدرس Name اسم Settings تنظیمات The underlying transport method. It must be consistent with the server, otherwise, the connection cannot be established. روش انتقال باید با سرور سازگار باشد، در غیر این صورت، اتصال نمی تواند برقرار شود. Network شبکه Transport Layer Security. It must be consistent with the server, otherwise, the connection cannot be established. امنیت لایه انتقال باید با سرور سازگار باشد، در غیر این صورت، اتصال نمی تواند برقرار شود. Security امنیت UDP FullCone Packet encoding for implementing features such as UDP FullCone. Server support is required, if the wrong selection is made, the connection cannot be made. Please leave it blank. Packet Encoding رمزنگاری بسته ها Network Settings (%1) تنظیمات شبکه (1%) When enabled, V2Ray will not check the validity of the TLS certificate provided by the remote host (the security is equivalent to plaintext) Allow insecure اجازه ناامن بودن Certificate گواهی Server name indication, clear text. شناسه نام سرور ، متن صریح Application layer protocol negotiation, clear text. Please separate them with commas. Custom (Extra Core) سفارشی ( هسته اضافه) Not set تنظیم نشده Already set تنظیم شده TLS Security Settings تنظیمات امنیت TLS TLS Camouflage Settings تنظیمات استتار TLS Reality public key. If not empty, turn TLS into REALITY. Custom (%1 outbound) Custom (%1 config) Custom Outbound Settings Custom Config Settings Apply settings to this group تنظیمات به این گروه اعمال شود Multiplex Keep Default نگه داری مقدار پیشفرض On فعال Off خاموش Confirm تایید Server support is required نیازمند پشتیبانی در سمت سرور Reality short id. Accept only one value. DialogHotkey Hotkey کلید میانبر Show routes مسیرها را نمایش بده Show groups نمایش گروه ها Trigger main window نمایش پنجره اصلی System Proxy پروکسی سیستم DialogManageGroups Groups گروه هاگروه ها New group گروه جدید Update all subscriptions به روز رسانی تمام اشتراک ها Confirmation تاییدیه Update all subscriptions? آیا همه اشتراک ها بروزرسانی شوند؟ DialogManageRoutes Routes مسیرها Sniffing Mode Disable غیرفعال کردن Remote DNS دی ان اس سمت-سرور Direct DNS دی ان اس مستقیم Enable DNS Routing فعال کردن مسیریابی DNS Block مسدود کردن Direct مستقیم Domain دامنه Proxy پروکسی IP آی پی Preset پیشفرض Mange route set مدیریت مسیرها Bypass LAN and China عبور ندادن ترافیک LAN و کشور چین Global همگانی Load بارگیری Save ذخیره کردن Remove حذف کردن Cancel لغو کردن Load routing: %1 بارگیری مسیر : 1% Save routing: %1 ذخیره مسیر : 1% Remove routing: %1 حذف مسیر : 1% Default Outbound Domain Strategy استراتژی دامنه Server Address Strategy استراتژی آدرس سرور Sniff result for routing Sniff result for destination Common متداول DNS دی ان اس Simple DNS Settings تنظیمات دی ان اس ساده Use DNS Object DNS Object Settings Simple Route مسیر ساده Custom Route مسیر سفارشی Custom Route (global) Note: Other settings are independent for each route set. Route sets Query Strategy Document اسناد Format فرمت This is especially important and it is recommended to use the default value of "localhost". If the default value does not work, try changing it to "223.5.5.5". For more information, see the document "Configuration/DNS". Final DNS Out DialogVPNSettings Tun Settings تنظیمات vpn Hide Console مخفی کردن کنسول Tun Enable IPv6 Bypass CIDR Bypass Process Name Whether blacklisted or whitelisted, your traffic will be handled by nekobox_core (sing-tun). This is NOT equal to "process mode" of some software. Whitelist mode حالت لیست سفید Proxy CIDR Proxy Process Name نام پروسه پراکسی Troubleshooting عیب یابی If you have trouble starting VPN, you can force reset nekobox_core process here. If still not working, see documentation for more information. https://matsuridayo.github.io/n-configuration/#vpn-tun Reset بازنشانی Cancel لغو کردن Internal Tun حالت تونل داخلی Add a tun inbound to the profile startup, instead of using two processes. This needs to be run NekoBox with administrator privileges. EditChain Traffic order is from top to bottom عبور ترافیک از بالا به پایین است Select Profile انتخاب کردن پروفایل Name cannot be empty. نام نمیتواند خالی باشد EditCustom Core هسته Json Editor ویرایشگر Json Command فرمان Config Suffix پسوند کانفیگ Outbound JSON, please read the documentation. JSON خروجی، لطفاً مستندات را بخوانید. Please pick a core. لطفا یک هسته انتخاب کنید. Random if it's empty or zero. Preview پیش نمایش Preview replace Preview config Please fill the complete config. Name cannot be empty. نام نمیتواند خالی باشد EditNaive Protocol پروتکل Password رمزعبور Extra headers SNI Username نام کاربری Certificate گواهی Insecure concurrency همزمانی ناامن Disable logs Turn on this option if your connection is lost after a while EditQUIC Certificate گواهی Download (Mbps) دانلود (مگابیت بر ثانیه) Disable MTU Discovery Hop Interval (s) Allow Insecure اجازه ارتباطات ناامن داده شود Hop Port Upload (Mbps) آپلود (مگابیت بر ثانیه) Obfs Password SNI Disable SNI Password Generate UUID Heartbeat Zero Rtt Handshake Congestion Control UDP Relay Mode Force use external core EditShadowSocks Plugin Args Password کلمه عبور Encryption رمزگذاری Plugin پلاگین Version of UDP over TCP protocol, server support is required. Off خاموش EditSocksHttp Version نسخه Username نام کاربری Password کلمه عبور EditTrojanVLESS Password کلمه عبور EditVMess Alter Id Security امنیت UUID Generate UUID GroupItem Update Subscription به روز رسانی اشتراک Edit ویرایش Remove حذف کردن Basic پایه Subscription اشتراک Archive آرشیو Last update: %1 آخرین آپدیت : %1 Confirmation تایید Remove %1? حذف %1? JsonEditor JSON Editor ویرایشگر JSON Format JSON فرمت JSON Remove All Comments حذف همه کامنت ها Json Editor ویرایشگر Json Structure Preview پیش نمایش ساختار OK تایید Json Contains Syntax Errors Original Json may contain syntax errors. Json tree is disabled. You must correct these errors before continuing. شما باید این ایرادات را قبل از ادامه دادن اصلاح کنید Syntax Errors خطاهای نحوی Please fix the JSON errors or remove the comments before continue لطفا قبل از ادامه دادن ایرادات JSON را اصلاح کنید یا کامنت ها را حذف کنید MainWindow Program برنامه Preferences تنظیمات Server سرور Ads تبلیغات Document اسناد Update بروزرسانی Tun Mode حالتvpn System Proxy پروکسی سیستمی Type نوع Address آدرس Name اسم Test Result نتیجه تست Traffic ترافیک Log ثبت رویدادها Connection اتصال Status وضعیت Outbound خروجی Destination مقصد Active Server سرور فعال Active Routing مسیریابی فعال Share اشتراک گذاری Current Group گروه فعلی Exit خروج Basic Settings تنظیمات پایه New profile پروفایل جدید Groups گروه ها Start آغازکردن Stop متوقف کردن Routing Settings تنظیمات مسیریابی Add profile from clipboard افزودن پروفایل از کلیپ بورد Delete از بین بردن Debug Info اطلاعات اشکال زدایی QR Code and link کد QR و پیوند Copy Link لینک را کپی کنید Clear Test Result نتایج تست پاک شود Export %1 config Reset Traffic بازنشانی ترافیک Scan QR Code کد QR را اسکن کنید Enable System Proxy پروکسی سیستم را فعال کنید Disable غیرفعال کردن Remove Duplicates موارد تکراری را حذف کردن fake جعلی Move جابجایی Start with system با سیستم شروع شود Remember last profile آخرین پروفایل را به خاطر بسپار Allow other devices to connect به دستگاه های دیگر اجازه اتصال دهید Remove Unavailable غیرقابل دستیابی پاک شود Full Test تست کامل Hotkey Settings تنظیمات کلید میانبر Select All انتخاب همه Copy links of selected (Neko Links) کپی لینک های انتخاب شده (پیوندهای Neko) Copy links of selected لینک های انتخاب شده را کپی کنید Enable Tun فعال کردن tun Clone همزادسازی Update subscription اشتراک را به روز کنید Resolve domain دامنه را حل کنید Tun Settings تنظیمات vpn Restart Program اجرا دوباره برنامه Open Config Folder پوشه Config باز شود Load routing and apply: %1 Error خطا Tun Settings changed تنظیمات Tun تغییر کرد Restart Tun to take effect. Tun را مجدداً راه اندازی کنید تا اعمال شود. Confirmation تائیدیه Settings changed, restart proxy? تنظیمات تغییر کرد، پراکسی راه اندازی مجدد شود؟ Imported %1 profile(s) Current server is incompatible with Tun. Please stop the server first, enable Tun Mode, and then restart. Not Running در حال اجرا نیست Select انتخاب Clone %1 item(s) Move %1 item(s) Remove %1 item(s) ? Copied %1 item(s) Config copied کانفیگ کپی شد QR Code not found کد QR یافت نشد Resolving domain to IP, if support. Set ignore keyword کلیدواژه نادیده گرفتن را تنظیم کنید Set the following keywords to ignore? Split by line. Save as route ذخیره به عنوان مسیر Edit ویرایش کردن Save "%1" as a routing rule? Clear پاک کردن Start: %1 End: %2 Failed to stop Tun process فرآیند Tun متوقف نشد [%1] test error: %2 Testing آزمایش کردن Unavailable غیرقایل دسترسی Starting profile %1 اغاز پروفایل %1 Stopping profile %1 متوقف کردن پروفایل %1 Current Select انتخاب فعلی Show Window نمایش پنجره برنامه Settings changed تنظیمات تغییر کرد Please run NekoBox as admin لطفا Nekobox را با مجوز ادمین اجرا کنید Restart Proxy راه اندازی مجدد پروکسی If there is no response for a long time, it is recommended to restart the software. اگر برای مدت زمان طولانی هیچ پاسخی دریافت نشد ، پیشنهاد میشود که نرم افزار را دوباره اجرا کنید Failed to start profile %1 Failed to stop, please restart the program. توقف ناموفق بود ، لطفا برنامه را دوباره اجرا کنید Select mode, double-click or press Enter to select a profile, press ESC to exit. Latency تاخیر UDP latency Download speed سرعت دانلود In and Out IP Test Options گزینه های تست Restart the program to take effect. برای مشاهده نتیجه برنامه را دوباره راه اندازی کنید Stop Testing URL Test ProxyItem Confirmation تائیدیه Remove %1? حذف %1? QGuiApplication QT_LAYOUT_DIRECTION RTL QObject Used: %1 Remain: %2 Expire: %3 Select انتخاب کردن Update بروزرسانی No update بدون بروزرسانی جدید Update found: %1 Release note: %2 Open in browser در مرور گر باز شود Close بستن Update is ready, restart to install? به روز رسانی آماده است، برای نصب مجدد راه اندازی شود؟ As link به عنوان لینک url detected آدرس شناسایی شد %1 How to update? چگونه بروزرسانی کنم ؟ Requesting subscription: %1 Requesting subscription %1 error: %2 Clearing servers... پاک کردن سرورها Added %1 profiles: %2 Deleted %3 Profiles: %4 Nothing خالی Change of %1: Chain Proxy پروکسی زنجیره ای Core not found: %1 هسته برنامه یافت نشد : %1 Proxy: %1 Direct: %2 Unavailable غیرقابل دسترس Request with proxy but no profile started. Subscription request fininshed: %1 Core exited, restarting. هسته برنامه متوقف شد ، در حال راه اندازی مجدد Core exits too frequently, stop automatic restart this profile. As Subscription (create new group) As Subscription (add to this group) Default پیش فرض The last speed test did not exit completely, please wait. If it persists, please restart the program. Qv2ray::ui::widgets::AutoCompleteTextEdit You can not input space characters here. شما نمی توانید کاراکتر فضای خالی در اینجا استفاده کنید. ================================================ FILE: translations/ru_RU.ts ================================================ DialogBasicSettings Basic Settings Основные настройки Common Общие Listen Address Адрес входящих подключений Custom Inbound Кастомный inbound Edit Изменить Enable Вкл Latency Test URL URL теста задержки Concurrent Параллельно Download Test URL URL теста загрузки Include Pre-release when checking update Проверять пре-релизы при обновлениях System proxy format Формат строки системного прокси Style Стиль Theme Тема System Системная Set custom icon Задать иконку Statistics refresh rate Период обновления статистики Off Выкл Connection statistics Статистика подключений Hide dashboard at startup Спрятать окно при старте Max log lines Макс. строк в логе Subscription Подписка User Agent User Agent Use proxy when updating subscription Использовать прокси при обновлении подписок Ignore TLS errors when updating subscription Игнорировать ошибки TLS при обновлении подписок Clear servers before updating subscription Очищать список серверов при обновлении подписок Core Ядро Select Выбрать Multiplex (mux) Мультиплексирование (mux) concurrency многопоточность Default On Вкл. по умолчанию Core Options Параметры ядра Extra Core Дополнительные ядра Add Добавить Delete Удалить Security Безопасность Skip TLS certificate authentication by default (allowInsecure) Не проверять подлинность TLS сертификатов по умолчанию Default uTLS Fingerprint uTLS fingerprint по умолчанию Advanced system proxy settings. Please select a format. Дополнительные настройки системного прокси. Пожалуйста, выберите формат. Please input the core name. Введите имя ядра. Please select the core name. Выберите имя ядра. Please select a PNG file. Выберите PNG файл Reset Сброс Cancel Отмена Please select a valid square image. Пожалуйста, выберите корректное квадратное изображение. Inbound Auth Аутентификация inbound Username Имя пользователя Password Пароль Override underlying DNS Переопределить нижестоящий DNS Timeout (s) Таймаут (с) Automatic update Автоматическое обновление Interval (minute, invalid if less than 30) Интервал (в минутах, значение считается неправильным, если меньше 30) Share VMess Link with v2rayN Format Поделиться ссылкой VMess в формате v2rayN Old Share Link Format Поделиться ссылкой в старом формате Mixed (SOCKS+HTTP) Listen Port DialogEditGroup Edit Group Изменить группу Name Имя Manually column width Уст. ширину колонок Archive Архив Type Тип Basic Простая Subscription Подписка (subscription) Front Proxy Фронт-прокси Clear Сбросить URL URL Copy profile share links Скопировать ссылки на профиль Copy profile share links (Neko Links) Скопировать ссылки на профиль (Neko links) Copied Скопировано Warning Предупреждение Please input URL Пожалуйста, введите URL None Нет Skip automatic update Пропустить автоматическое обновление Common Общие Share Поделиться DialogEditProfile Edit Редактировать Common Общие Type Тип Port Порт Address Адрес Name Имя Custom Outbound Settings Доп. настройки outbound Custom Config Settings Доп. настройки конфига Apply settings to this group Применить настройки к этой группе Settings Настройки The underlying transport method. It must be consistent with the server, otherwise, the connection cannot be established. Нижележащий транспорт. Должен соответствовать конфигурации сервера, иначе подключение будет невозможно. Network Транспорт Transport Layer Security. It must be consistent with the server, otherwise, the connection cannot be established. TLS. Должно совпадать с параметрами сервера, иначе подключение будет невозможно. Security Безопасность UDP FullCone Packet encoding for implementing features such as UDP FullCone. Server support is required, if the wrong selection is made, the connection cannot be made. Please leave it blank. UDP FullCone кодирование пакетов для реализации функционала типа UDP FullCone. Необходима поддержка со стороны сервера, при неправильном выборе подключение не будет работать. Оставьте пустым, если вы не знаете что и зачем это. Packet Encoding Кодирование пакетов Server support is required Необходима поддержка со стороны сервера Multiplex Мультиплексирование Keep Default По умолчанию On Вкл Off Выкл Network Settings (%1) Настройки транспорта (%1) TLS Security Settings Настройки TLS When enabled, V2Ray will not check the validity of the TLS certificate provided by the remote host (the security is equivalent to plaintext) Если вкл., то клиент не будет проверять валидность TLS-сертификата, предоставленного сервером Allow insecure Разрешить небезопасн. Certificate Сертификат Server name indication, clear text. SNI (идентификатор сервера, передается в открытом виде). Application layer protocol negotiation, clear text. Please separate them with commas. ALPN, идентификатор протокола приложения, передается открытым текстом. Используйте запятую в качестве разделителя. TLS Camouflage Settings Настройки маскировки TLS Reality public key. If not empty, turn TLS into REALITY. Публичный ключ Reality. Если задано значение, то будет использован протокол Reality для TLS. Reality short id. Accept only one value. Короткий ID для Reality. Можно задать только одно значение. Custom (%1 outbound) Кастомный (%1 outbound) Custom (%1 config) Кастомный (%1 конфиг) Custom (Extra Core) Кастомный (доп. ядро) Not set Не задано Already set Уже задано Confirm Подтвердить DialogHotkey Hotkey Горячие клавиши Show routes Показать маршруты Show groups Показать группы Trigger main window Показать главное окно System Proxy Режим системного прокси DialogManageGroups Groups Группы New group Новая группа Update all subscriptions Обновить все подписки Confirmation Подтвердить Update all subscriptions? Обновить все подписки? DialogManageRoutes Routes Маршруты Common Общие Route sets Набор маршрутов Mange route set Изменить набор маршрутов Custom Route (global) Кастомные маршруты (global) Note: Other settings are independent for each route set. Остальные настройки будут индивидуальны для каждого набора маршрутов. Domain Strategy Стратегия доменов Disable Выкл Sniff result for routing Подслушивать для маршрутизации Sniff result for destination Подслушивать для точки назначения Sniffing Mode Режим подслушивания Server Address Strategy Стратегия выбора адреса сервера DNS DNS Simple DNS Settings Базовые настройки DNS Direct DNS DNS для "прямых" запросов Query Strategy Стратегия запросов Remote DNS Удаленный (remote) DNS Enable DNS Routing Вкл. DNS-маршрутизацию DNS Object Settings Специальные настройки DNS Use DNS Object Использовать DNS-объект Format Форматировать Document Помощь Simple Route Базовые маршруты Block Блок Direct Напрямую Domain Домен Proxy Прокси IP IP Preset Пресет Custom Route Кастомные маршруты Default Outbound Outbound по-умолчанию Bypass LAN and China Пропускать LAN и китайские ресурсы Global Глобально Load Загрузить Save Сохранить Remove Удалить Cancel Отменить Load routing: %1 Загрузить машруты: %1 Save routing: %1 Сохранить маршруты: %1 Remove routing: %1 Удалить маршруты: %1 This is especially important and it is recommended to use the default value of "localhost". If the default value does not work, try changing it to "223.5.5.5". For more information, see the document "Configuration/DNS". Final DNS Out DialogVPNSettings Tun Settings Настройки Tun Tun Enable IPv6 Вкл. IPv6 в Tun Add a tun inbound to the profile startup, instead of using two processes. This needs to be run NekoBox with administrator privileges. Добавить inbound c Tun в конфигурацию профиля вместо того, чтобы использовать два отдельных процесса. Для этого необходимо запускать NekoBox c правами администратора. Internal Tun Встроен. Tun Hide Console Скрывать окно Bypass CIDR Пропускать CIDR Bypass Process Name Пропускать процессы Whether blacklisted or whitelisted, your traffic will be handled by nekobox_core (sing-tun). This is NOT equal to "process mode" of some software. При использовании белого или черного списка, ваш трафик будет обработан ядром nekobox (sign-tun). Это НЕ эквивалент "process mode" как в некотором ПО. Whitelist mode Режим белого списка Troubleshooting Исправление проблем Proxy CIDR Проксировать CIDR Proxy Process Name Проксировать процессы If you have trouble starting VPN, you can force reset nekobox_core process here. If still not working, see documentation for more information. https://matsuridayo.github.io/n-configuration/#vpn-tun Если у вас проблемы с запуском VPN, можно принудительно перезапустить процесс nekobox-core. Если ничего по-прежнему не работает, ознакомьтесь с документацией: https://matsuridayo.github.io/n-configuration/#vpn-tun Reset Перезапустить Cancel Отмена EditChain Traffic order is from top to bottom Порядок трафика сверху вниз Select Profile Выбор профиля Name cannot be empty. Имя не может быть пустым. EditCustom Core Ядро Json Editor Редактор JSON Command Команда Config Suffix Суффикс конфига Random if it's empty or zero. Если пусто или 0, то будет выбран случайным образом. Preview Предпросмотр Outbound JSON, please read the documentation. Outbound JSON, читайте документацию. Please fill the complete config. Пожалуйста, введите полную конфигурацию. Preview replace Предпросмотр замен Preview config Предпросмотр конфигурации Name cannot be empty. Имя не может быть пустым. Please pick a core. Пожалуйста, выберите ядро. EditNaive Protocol Протокол Password Пароль Extra headers Дополнительные заголовки SNI SNI Username Имя пользователя Certificate Сертификат Insecure concurrency Disable logs Turn on this option if your connection is lost after a while EditQUIC Download (Mbps) Скорость приема (Mbps) Disable MTU Discovery Выкл. MTU discovery Hop Interval (s) Certificate Сертификат Allow Insecure Разрешить небезопасные Hop Port Upload (Mbps) Скорость отдачи (Mbps) Obfs Password Пароль для обфускации SNI SNI Disable SNI Отключить SNI Password Пароль Generate UUID Сгенерировать UUID Heartbeat Сердцебиение (Hearbeat) Zero Rtt Handshake Без рукопожатия Rtt Congestion Control Управление перегрузкой UDP Relay Mode Режим UDP Relay Force use external core EditShadowSocks Encryption Шифрование Plugin Плагин Password Пароль Plugin Args Аргументы Version of UDP over TCP protocol, server support is required. Версия протокола "UDP over TCP", требуется поддержка со стороны сервера. Off Выкл EditSocksHttp Version Версия Username Имя пользователя Password Пароль EditTrojanVLESS Password Пароль EditVMess Security Шифрование Alter Id Альт. ID UUID UUID Generate UUID Сгенерировать UUID GroupItem Update Subscription Обновить Edit Изменить Remove Удалить Basic Простая Subscription Подписка Archive Архив Last update: %1 Последнее обновление: %1 Confirmation Подтверждение Remove %1? Удалить %1? JsonEditor JSON Editor Редактор JSON Format JSON Форматировать JSON Remove All Comments Удалить все комментарии Json Editor Редактор JSON Structure Preview Предпросмотр структуры OK OK Json Contains Syntax Errors JSON содержит синтаксические ошибки Original Json may contain syntax errors. Json tree is disabled. Кажется, оригинальный JSON содержит синтатсические ошибки. Дерево JSON отключено. You must correct these errors before continuing. Вы должны исправить эти ошибки чтобы продолжить. Syntax Errors Синтаксические ошибки Please fix the JSON errors or remove the comments before continue Пожалуйста, чтобы продолжить, исправьте ошибки в JSON или удалите комментарии MainWindow Program Программа Preferences Настройки Server Сервер Ads Реклама Document Документация Update Обновление Tun Mode Режим TUN System Proxy Режим системного прокси Type Тип Address Адрес Name Имя Test Result Результат теста Traffic Трафик Log Журнал Connection Подключение Status Статус Outbound Outbound Destination Пункт назначения Active Server Активный сервер Active Routing Активное правило роутинга Share Поделиться Current Group Текущая группа Current Select Текущий выбор Exit Выход Show Window Показать окно Basic Settings Основные настройки New profile Новый профиль Groups Группы Start Запустить Stop Остановить Routing Settings Настройки маршрутов Add profile from clipboard Добавить профиль из буфера обмена Delete Удалить Debug Info Отладочная информация QR Code and link QR-код и ссылка Copy Link Скопировать ссылку Clear Test Result Очистить результат теста Export %1 config Экспортировать конфиг %1 Reset Traffic Сбросить трафик Scan QR Code Сканировать QR-код Enable System Proxy Активировать системный прокси Disable Отключить Remove Duplicates Удалить дубликаты fake фейк Move Переместить Start with system Запускаться вместе с системой Remember last profile Запомнить последний профиль Allow other devices to connect Разрешить подключаться другим устройствам Remove Unavailable Удалить недоступные Full Test Полный тест Hotkey Settings Настройки комбинаций клавиш Select All Выбрать всё Copy links of selected (Neko Links) Скопировать ссылки для выбранных (Neko links) Copy links of selected Скопировать ссылки для выбранных Enable Tun Включить TUN-режим Clone Клонировать Update subscription Обновить подписки Resolve domain Ну слово "разрешить" можно использоваться и в значении "разобраться" Разрешить доменное имя Tun Settings Настройки TUN-режима Restart Program Перезапустить программу Open Config Folder Открыть папку с конфигами Restart Proxy Перезапустить прокси Load routing and apply: %1 Загрузить маршруты и активировать: %1 Error Ошибка Tun Settings changed Настройки TUN изменились Restart Tun to take effect. Перезапустите TUN чтобы применить изменения. Confirmation Подтверждение Settings changed, restart proxy? Настройки изменены, перезапустить прокси? Settings changed Настройки изменены Restart the program to take effect. Перезапустите программу чтобы новые настройки вступили в силу. Imported %1 profile(s) Импортирован(ы) %1 профиль(ей) Please run NekoBox as admin Пожалуйста, запустите NekoBox с правами администратора Current server is incompatible with Tun. Please stop the server first, enable Tun Mode, and then restart. Текущий сервер не совместим с TUN-режимом. Пожалуйста, сначала остановите подключение к серверу, активируйте TUN-режим, и потом перезапустите. Not Running Не запущен Select Выбор Select mode, double-click or press Enter to select a profile, press ESC to exit. Режим выбора, дважды кликните или нажмите Enter для выбора профиля, либо ESC чтобы выйти. Clone %1 item(s) Клонировать %1 записей Move %1 item(s) Переместить %1 записей Remove %1 item(s) ? Удалить %1 записей ? Copied %1 item(s) Скопировано %1 записей Config copied Конфиг скопирован QR Code not found QR-код не найден Resolving domain to IP, if support. Отрезолвить домен в IP-адрес, если поддерживается. Set ignore keyword Ключевые слова для игнорирования Set the following keywords to ignore? Split by line. Задайте ключнвые слова для игнорирования, каждое на отдельной строке. Save as route Сохранить как маршрут Edit Редактировать Save "%1" as a routing rule? Сохранить "%1" как профиль маршрутизации? Clear Очистить Start: %1 End: %2 Начало %1 Конец %2 Failed to stop Tun process Не удалось остановить TUN-процесс Test Options Параметры тестирования Latency Задержка UDP latency Задержка UDP Download speed Скорость загрузки In and Out IP Входящий и исходящий IP [%1] test error: %2 [%1] ошибка теста: %2 Testing Тестируем Unavailable Недоступен If there is no response for a long time, it is recommended to restart the software. Если нет ответа в течении долгого времени, рекомендуем перезапустить приложение. Starting profile %1 Запускаем профиль %1 Failed to start profile %1 Не удалось запустить профиль %1 Stopping profile %1 Останавливаем профиль %1 Failed to stop, please restart the program. Не удалось остановить, пожалуйста, перезапустите приложение. Stop Testing URL Test ProxyItem Confirmation Подтверждение Remove %1? Удалить %1? QGuiApplication QT_LAYOUT_DIRECTION QObject Core not found: %1 Ядро не найдено: %1 Unavailable Недоступно Proxy: %1 Direct: %2 Через прокси: %1 Напрямую: %2 Chain Proxy Цепочка прокси Request with proxy but no profile started. Запрос через прокси, но профиль не запущен. As Subscription (add to this group) Как подписку (добавить в эту группу) As Subscription (create new group) Как подписку (создать новую группу) As link Как ссылку url detected Обнаружен URL %1 How to update? %1 Как обновить? Requesting subscription: %1 Запрашиваем подписку: %1 Requesting subscription %1 error: %2 Запрашиваем подписку %1 ошибка: %2 Subscription request fininshed: %1 Запрос подписки завершен: %1 Clearing servers... Очишаем серверы... Added %1 profiles: %2 Deleted %3 Profiles: %4 Добавлено %1 профилей: %2 Удалено %3 профилей: %4 Nothing Ничего Change of %1: Изменение %1: Core exits too frequently, stop automatic restart this profile. Ядро слишком часто прекращает свою работу, отмена автоматического перезапуска этого профиля. Core exited, restarting. Ядро прекратило свою работу, перезапуск. Select Выбор Update Обновление No update Нет обновлений Update found: %1 Release note: %2 Найдено обновление: %1 Примечания к выпуску: %2 Open in browser Открыть в браузере Close Закрыть Update is ready, restart to install? Обновление готово, перезапуститься для установки? Used: %1 Remain: %2 Expire: %3 Использовано: %1, осталось: %2, истекло: %3 Default По умолчанию The last speed test did not exit completely, please wait. If it persists, please restart the program. Qv2ray::ui::widgets::AutoCompleteTextEdit You can not input space characters here. Сюда нельзя вводить пробелы. ================================================ FILE: translations/translations.qrc ================================================ zh_CN.qm fa_IR.qm ru_RU.qm ================================================ FILE: translations/zh_CN.ts ================================================ DialogBasicSettings Basic Settings 基本设置 Enable 启用 Listen Address 监听地址 concurrency 并发 User Agent Common 通用 Style 样式 Theme 主题 System 系统 Subscription 订阅 Core 核心 Extra Core 其他核心 Select 选择 Edit 编辑 Custom Inbound 自定义入站 Concurrent 并发 Use proxy when updating subscription 更新订阅时使用代理 Security 安全 Statistics refresh rate 流量统计刷新率 Off 关闭 Add 添加 Delete 删除 Please input the core name. 请输入核心名. Please select the core name. 请选择核心名. Connection statistics 连接统计 Include Pre-release when checking update 检查更新时包括 Pre-release 版本 Set custom icon 自定义图标 Please select a PNG file. 请选择一个 PNG 文件。 Reset 重置 Please select a valid square image. 请选择有效的正方形图像。 Cancel 取消 System proxy format 系统代理格式 Advanced system proxy settings. Please select a format. 高级系统代理设置。请选择一种格式。 Old Share Link Format 旧分享链接格式 Share VMess Link with v2rayN Format 用 v2rayN 的格式分享 VMess 链接 Clear servers before updating subscription 更新订阅前清除服务器 Ignore TLS errors when updating subscription 更新订阅时忽略 TLS 错误 Hide dashboard at startup 启动时不显示仪表盘 Max log lines 日志最大行数限制 Inbound Auth 入站认证设置 Username 用户名 Password 密码 Skip TLS certificate authentication by default (allowInsecure) 默认跳过 TLS 证书验证 (allowInsecure) Default uTLS Fingerprint 默认 uTLS 指纹 Core Options 核心选项 Override underlying DNS 覆盖底层 DNS Default On 默认开启 Multiplex (mux) 多路复用 Mux Latency Test URL 延迟测试 URL Download Test URL 下载测试 URL Timeout (s) 超时(秒) Automatic update 自动更新订阅 Interval (minute, invalid if less than 30) 时间间隔(分钟,少于 30 分钟无效) Mixed (SOCKS+HTTP) Listen Port Mixed (SOCKS+HTTP) 监听端口 DialogEditGroup Edit Group 编辑分组 Type 类型 Name 名称 Basic 基本 Subscription 订阅 URL Archive 归档 Warning 警告 Please input URL 请输入 URL Copy profile share links 复制分组内配置的分享链接 Copied 复制成功 Copy profile share links (Neko Links) 复制分组内配置的分享链接 (Neko Links) Manually column width 手动调节列宽 Front Proxy 前置代理 None Clear 清除 Skip automatic update 跳过自动更新 Common 通用 Share 分享 DialogEditProfile Edit 编辑 Common 通用 Type 类型 Port 端口 Address 地址 Name 名称 Network 传输 Security 传输层安全 Network Settings (%1) 传输设置 (%1) The underlying transport method. It must be consistent with the server, otherwise, the connection cannot be established. 底层传输方式。必须与服务器一致,否则无法建立连接。 Transport Layer Security. It must be consistent with the server, otherwise, the connection cannot be established. 传输层安全。必须与服务器一致,否则无法建立连接。 UDP FullCone Packet encoding for implementing features such as UDP FullCone. Server support is required, if the wrong selection is made, the connection cannot be made. Please leave it blank. 包编码,用于实现 UDP FullCone 等特性。需要服务器支持,选错无法连接。不懂请留空。 When enabled, V2Ray will not check the validity of the TLS certificate provided by the remote host (the security is equivalent to plaintext) 开启后 V2Ray 不会检查远端主机所提供的 TLS 证书的有效性(安全性相当于明文) Server name indication, clear text. 服务器名称指示,明文。 Application layer protocol negotiation, clear text. Please separate them with commas. 应用层协议协商,明文。多个请以英文逗号分隔。 Allow insecure 不检查服务器证书 Certificate 证书 Not set 未设置 Already set 已设置 Packet Encoding 包编码 Settings 设置 Custom (Extra Core) 自定义 (其他核心) TLS Security Settings TLS 安全设置 TLS Camouflage Settings TLS 伪装设置 Reality public key. If not empty, turn TLS into REALITY. Reality public key. 如果不为空,则将 TLS 变为 REALITY。 Custom (%1 outbound) 自定义 (%1 出站) Custom (%1 config) 自定义 (%1 完整配置) Custom Outbound Settings 自定义出站 JSON 设置 Custom Config Settings 自定义配置 JSON 设置 Apply settings to this group 将设置应用于该组 Multiplex 多路复用 Keep Default 保持默认 On 开启 Off 关闭 Confirm 确认 Server support is required 需要服务器支持 Reality short id. Accept only one value. Reality short id. 只接受一个值。 DialogHotkey Hotkey 热键 Show groups 显示分组 Show routes 显示路由 Trigger main window 显示/隐藏主窗口 System Proxy 系统代理 DialogManageGroups Groups 分组 New group 新建分组 Update all subscriptions 更新所有订阅 Confirmation 确认 Update all subscriptions? 更新所有订阅? DialogManageRoutes Routes 路由 Disable 禁用 Sniffing Mode 流量探测 Sniff result for routing 探测结果用于路由判断 Sniff result for destination 探测结果用于目标地址 Direct DNS 直连 DNS Remote DNS 远程 DNS Enable DNS Routing 启用 DNS 路由 Block 阻止 Direct 直连 Domain 域名 Proxy 代理 Preset 预设 Bypass LAN and China 绕过局域网和大陆 Global 全局 IP Save 保存 Load 加载 Cancel 取消 Remove 删除 Save routing: %1 保存路由: %1 Load routing: %1 加载路由: %1 Remove routing: %1 删除路由: %1 Mange route set 管理路由规则 Default Outbound 默认出站 Domain Strategy 域名策略 Server Address Strategy 服务器地址策略 Common 通用 DNS Simple DNS Settings 简易 DNS 设置 Use DNS Object 使用 DNS Object DNS Object Settings DNS Object 设置 Simple Route 简易路由 Custom Route 自定义路由 Custom Route (global) 自定义路由(全局) Note: Other settings are independent for each route set. 注意:其他设置对于每个路由集都是独立的。 Route sets 路由集 Query Strategy 查询策略 Document 文档 Format 格式化 This is especially important and it is recommended to use the default value of "localhost". If the default value does not work, try changing it to "223.5.5.5". For more information, see the document "Configuration/DNS". 此项尤为重要,建议使用默认值 "localhost"。 如果默认值不工作,可以尝试更改为 "223.5.5.5"。 更多信息,请参阅文档 "配置/DNS"。 Final DNS Out 默认 DNS 出站 DialogVPNSettings Tun Settings Tun 设置 Hide Console 隐藏控制台 Tun Enable IPv6 启用 Tun IPv6 Bypass CIDR 绕过 CIDR Bypass Process Name 绕过进程名 Whitelist mode 白名单模式 Proxy CIDR 代理 CIDR Proxy Process Name 代理进程名 Whether blacklisted or whitelisted, your traffic will be handled by nekobox_core (sing-tun). This is NOT equal to "process mode" of some software. 无论是黑名单还是白名单,您的流量都将由 nekobox_core (sing-tun) 处理。这不等于某些软件的「进程模式」。 Troubleshooting 故障排除 If you have trouble starting VPN, you can force reset nekobox_core process here. If still not working, see documentation for more information. https://matsuridayo.github.io/n-configuration/#vpn-tun 如果您在启动 Tun 时遇到问题,您可以在此处强制重置 nekobox_core 进程。 如果仍然无法正常工作,请参阅文档以获取更多信息。 https://matsuridayo.github.io/n-configuration/#vpn-tun Reset 重置 Cancel 取消 Internal Tun 内部 Tun Add a tun inbound to the profile startup, instead of using two processes. This needs to be run NekoBox with administrator privileges. 在配置文件启动时添加一个tun inbound,而不是使用两个进程。 这需要以管理员权限运行NekoBox。 EditChain Select Profile 选择配置 Traffic order is from top to bottom 流量顺序是从上到下(最后一个配置为流量的出口) Name cannot be empty. 名称 不能为空 EditCustom Core 核心 Command 命令 Json Editor JSON 编辑器 Please pick a core. 请选择一个核心。 Outbound JSON, please read the documentation. 填写出站 JSON 对象,详细请看文档。 Config Suffix 配置文件后缀 Random if it's empty or zero. 如果为空或为零,则表示使用随机端口。 Preview 预览 Preview replace 预览替换串 Preview config 预览配置 Please fill the complete config. 请填写完整配置。 Name cannot be empty. 名称 不能为空 EditNaive Protocol 协议 Password 密码 Extra headers 附加标头 SNI Username 用户名 Certificate 证书 Insecure concurrency 不安全并发 Disable logs 关闭日志 Turn on this option if your connection is lost after a while 如果连接一段时间后出现中断,请打开此选项 EditQUIC Certificate 证书 Download (Mbps) 下载速度 (Mbps) Disable MTU Discovery 禁用 MTU 探测 Hop Interval (s) 端口跳跃间隔 (秒) Allow Insecure 不检查服务器证书 Hop Port 跳跃端口 Upload (Mbps) 上传速度 (Mbps) Obfs Password 混淆密码 SNI SNI Generate UUID 生成 UUID Password 密码 Zero Rtt Handshake 0-Rtt 握手 UDP Relay Mode UDP 中继模式 Congestion Control 拥塞控制 Heartbeat 心跳包发送间隔 Disable SNI 不发送服务器名称指示 Force use external core 强制使用外部核心 EditShadowSocks Plugin Args 插件参数 Password 密码 Encryption 加密 Plugin 插件 Version of UDP over TCP protocol, server support is required. UDP over TCP 协议版本,需要服务器支持。 Off 关闭 EditSocksHttp Version 版本 Username 用户名 Password 密码 EditTrojanVLESS Password 密码 EditVMess Security 加密 Alter Id UUID Generate UUID 生成 UUID GroupItem Update Subscription 更新订阅 Edit 编辑 Basic 基本 Subscription 订阅 Confirmation 确认 Remove 删除 Remove %1? 删除 %1 ? Archive 归档 Last update: %1 最后更新 %1 JsonEditor JSON Editor Format JSON Remove All Comments Json Editor Structure Preview OK Json Contains Syntax Errors Original Json may contain syntax errors. Json tree is disabled. You must correct these errors before continuing. Syntax Errors Please fix the JSON errors or remove the comments before continue MainWindow Program 程序 Preferences 首选项 Server 服务器 Ads 推广 Type 类型 Address 地址 Name 名称 Test Result 测试结果 Traffic 流量 System Proxy 系统代理 Share 分享 Exit 退出 Basic Settings 基本设置 Groups 分组 Stop 停止 Add profile from clipboard 从剪贴板添加 Debug Info 调试信息 Copy Link 复制链接 Clear Test Result 清理测试结果 Scan QR Code 扫描 QR Code Disable 禁用 Error 错误 Confirmation 确认 Settings changed, restart proxy? 设置已改变,是否重启代理? Imported %1 profile(s) 导入了 %1 个配置 Unavailable 不可用 Remove %1 item(s) ? 删除 %1 个项目? Config copied 配置已复制 [%1] test error: %2 [%1] 测试错误: %2 Clear 清除 fake Testing 正在测试 Update 更新 Document 文档 Select 选择 QR Code not found 未扫描到 QR Code Move 移动 Log 日志 Connection 连接 Status 状态 Outbound 出站 Destination 目标地址 Start: %1 End: %2 开始: %1 结束: %2 Starting profile %1 正在启动配置 %1 Stopping profile %1 正在停止配置 %1 Start with system 跟随系统启动 Remember last profile 记住最后的配置 Move %1 item(s) 移动 %1 个项目 Remove Unavailable 删除不可用的配置 New profile 手动输入配置 Hotkey Settings 热键设置 QR Code and link 显示 QR Code 和分享链接 Active Routing 当前路由规则 Active Server 当前服务器 Load routing and apply: %1 加载路由规则并应用: %1 Copied %1 item(s) 复制了 %1 个项目 Full Test 完整测试 Current Group 当前分组 Reset Traffic 重置流量 Remove Duplicates 删除重复的配置 Select All 全选 Tun Mode Tun 模式 Failed to stop Tun process 停止 Tun 失败 Enable System Proxy 启用系统代理 Enable Tun 启用 Tun Tun Settings changed Tun 设置改变 Restart Tun to take effect. 重启 Tun 生效。 Start 启动 Delete 删除 Copy links of selected 批量复制选中项目的分享链接 Clone 克隆 Update subscription 更新订阅 Clone %1 item(s) 克隆 %1 个项目 Copy links of selected (Neko Links) 批量复制选中项目的分享链接 (Neko Links) Allow other devices to connect 允许其他设备连接 Resolve domain 将服务器域名解析为 IP Resolving domain to IP, if support. 将服务器域名解析为 IP(如果支持)。 Export %1 config 导出 %1 配置 Routing Settings 路由设置 Tun Settings Tun 设置 Restart Program 重启程序 Not Running 未启动 Current server is incompatible with Tun. Please stop the server first, enable Tun Mode, and then restart. 当前服务器与 Tun 不兼容。请先停止服务器,打开 Tun 模式后再启动。 Open Config Folder 打开配置目录 Set ignore keyword 设置忽略关键字 Set the following keywords to ignore? Split by line. 将以下关键字设置为忽略? 一行一个。 Save as route 保存为路由规则 Save "%1" as a routing rule? 将"%1"保存为一条路由规则? Edit 编辑 Current Select 当前选中 Show Window 显示主窗口 Settings changed 设置改变 Restart the program to take effect. 重启程序生效。 Please run NekoBox as admin 请以管理员权限运行 NekoBox Restart Proxy 重启代理 Failed to start profile %1 启动配置失败: %1 Failed to stop, please restart the program. 停止失败,请重启程序。 If there is no response for a long time, it is recommended to restart the software. 如果长时间没有反应,建议重启软件。 Select mode, double-click or press Enter to select a profile, press ESC to exit. 选择模式,双击或按回车键选择一个配置文件,按ESC键退出。 Latency 延迟 UDP latency UDP 延迟 Download speed 下载速度 In and Out IP 入口出口 IP Test Options 测试选项 Stop Testing 停止测试 URL Test URL 测试 ProxyItem Confirmation 确认 Remove %1? 删除 %1 ? QGuiApplication QT_LAYOUT_DIRECTION LTR QObject As link 作为链接 url detected 检测到 URL %1 How to update? %1 如何处理? Added %1 profiles: %2 Deleted %3 Profiles: %4 增加了 %1 个配置: %2 删除了 %3 个配置: %4 Proxy: %1 Direct: %2 代理: %1 直连: %2 Used: %1 Remain: %2 Expire: %3 已用 %1 剩余 %2 过期 %3 Core not found: %1 找不到 "%1" 核心。请前往设置 Update 更新 No update 无更新 Open in browser 浏览器打开 Close 关闭 Update is ready, restart to install? 更新已下载好,重启应用? Update found: %1 Release note: %2 检测到更新: %1 更新日志: %2 Unavailable 不可用 Request with proxy but no profile started. 即将使用代理请求,但是代理未启动。 Chain Proxy 链式代理 Requesting subscription: %1 正在请求订阅: %1 Requesting subscription %1 error: %2 请求订阅 %1 错误: %2 Nothing Change of %1: %1 变化: Select 选择 Clearing servers... 正在清理服务器... Subscription request fininshed: %1 订阅请求完成: %1 Core exited, restarting. Core 退出,正在重新启动。 Core exits too frequently, stop automatic restart this profile. Core 退出太频繁,停止自动重启。 As Subscription (create new group) 作为订阅(创建新组) As Subscription (add to this group) 作为订阅(添加到该组) Default 默认 The last speed test did not exit completely, please wait. If it persists, please restart the program. 上次速度测试未完全退出,请等待。如果问题仍然存在,请重新启动程序。 Qv2ray::ui::widgets::AutoCompleteTextEdit You can not input space characters here. ================================================ FILE: ui/GroupSort.hpp ================================================ #pragma once // implement in mainwindow namespace GroupSortMethod { enum GroupSortMethod { Raw, ByType, ByAddress, ByName, ByLatency, ById, }; } struct GroupSortAction { GroupSortMethod::GroupSortMethod method = GroupSortMethod::Raw; bool save_sort = false; // 保存到文件 bool descending = false; // 默认升序,开这个就是降序 bool scroll_to_started = false; }; ================================================ FILE: ui/Icon.cpp ================================================ #include "Icon.hpp" #include "main/NekoGui.hpp" #include QPixmap Icon::GetTrayIcon(Icon::TrayIconStatus status) { QPixmap pixmap; // software embedded icon auto pixmap_read = QPixmap(":/neko/" + software_name.toLower() + ".png"); if (!pixmap_read.isNull()) pixmap = pixmap_read; // software pack icon pixmap_read = QPixmap("../" + software_name.toLower() + ".png"); if (!pixmap_read.isNull()) pixmap = pixmap_read; // user icon pixmap_read = QPixmap("./" + software_name.toLower() + ".png"); if (!pixmap_read.isNull()) pixmap = pixmap_read; if (status == TrayIconStatus::NONE) return pixmap; auto p = QPainter(&pixmap); auto side = pixmap.width(); auto radius = side * 0.4; auto d = side * 0.3; auto margin = side * 0.05; if (status == TrayIconStatus::RUNNING) { p.setBrush(QBrush(Qt::darkGreen)); } else if (status == TrayIconStatus::SYSTEM_PROXY) { p.setBrush(QBrush(Qt::blue)); } else if (status == TrayIconStatus::VPN) { p.setBrush(QBrush(Qt::red)); } p.drawRoundedRect( QRect(side - d - margin, side - d - margin, d, d), radius, radius); p.end(); return pixmap; } QPixmap Icon::GetMaterialIcon(const QString &name) { QPixmap pixmap(":/icon/material/" + name + ".svg"); return pixmap; } ================================================ FILE: ui/Icon.hpp ================================================ #pragma once #include namespace Icon { enum TrayIconStatus { NONE, RUNNING, SYSTEM_PROXY, VPN, }; QPixmap GetTrayIcon(TrayIconStatus status); QPixmap GetMaterialIcon(const QString &name); } // namespace Icon ================================================ FILE: ui/ThemeManager.cpp ================================================ #include #include #include #include "ThemeManager.hpp" ThemeManager *themeManager = new ThemeManager; extern QString ReadFileText(const QString &path); void ThemeManager::ApplyTheme(const QString &theme) { auto internal = [=] { if (this->system_style_name.isEmpty()) { this->system_style_name = qApp->style()->objectName(); } if (this->current_theme == theme) { return; } bool ok; auto themeId = theme.toInt(&ok); if (ok) { // System & Built-in QString qss; if (themeId != 0) { QString path; std::map replace; switch (themeId) { case 1: path = ":/themes/feiyangqingyun/qss/flatgray.css"; replace[":/qss/"] = ":/themes/feiyangqingyun/qss/"; break; case 2: path = ":/themes/feiyangqingyun/qss/lightblue.css"; replace[":/qss/"] = ":/themes/feiyangqingyun/qss/"; break; case 3: path = ":/themes/feiyangqingyun/qss/blacksoft.css"; replace[":/qss/"] = ":/themes/feiyangqingyun/qss/"; break; default: return; } qss = ReadFileText(path); for (auto const &[a, b]: replace) { qss = qss.replace(a, b); } } auto system_style = QStyleFactory::create(this->system_style_name); if (themeId == 0) { // system theme qApp->setPalette(system_style->standardPalette()); qApp->setStyle(system_style); qApp->setStyleSheet(""); } else { if (themeId == 1 || themeId == 2 || themeId == 3) { // feiyangqingyun theme QString paletteColor = qss.mid(20, 7); qApp->setPalette(QPalette(paletteColor)); } else { // other theme qApp->setPalette(system_style->standardPalette()); } qApp->setStyleSheet(qss); } } else { // QStyleFactory const auto &_style = QStyleFactory::create(theme); if (_style != nullptr) { qApp->setPalette(_style->standardPalette()); qApp->setStyle(_style); qApp->setStyleSheet(""); } } current_theme = theme; }; internal(); auto nekoray_css = ReadFileText(":/neko/neko.css"); qApp->setStyleSheet(qApp->styleSheet().append("\n").append(nekoray_css)); } ================================================ FILE: ui/ThemeManager.hpp ================================================ #pragma once class ThemeManager { public: QString system_style_name = ""; QString current_theme = "0"; // int: 0:system 1+:builtin string: QStyleFactory void ApplyTheme(const QString &theme); }; extern ThemeManager *themeManager; ================================================ FILE: ui/dialog_basic_settings.cpp ================================================ #include "dialog_basic_settings.h" #include "ui_dialog_basic_settings.h" #include "3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp" #include "fmt/Preset.hpp" #include "ui/ThemeManager.hpp" #include "ui/Icon.hpp" #include "main/GuiUtils.hpp" #include "main/NekoGui.hpp" #include #include #include #include #include class ExtraCoreWidget : public QWidget { public: QString coreName; QLabel *label_name; MyLineEdit *lineEdit_path; QPushButton *pushButton_pick; explicit ExtraCoreWidget(QJsonObject *extraCore, const QString &coreName_, QWidget *parent = nullptr) : QWidget(parent) { coreName = coreName_; label_name = new QLabel; label_name->setText(coreName); lineEdit_path = new MyLineEdit; lineEdit_path->setText(extraCore->value(coreName).toString()); pushButton_pick = new QPushButton; pushButton_pick->setText(QObject::tr("Select")); auto layout = new QHBoxLayout; layout->addWidget(label_name); layout->addWidget(lineEdit_path); layout->addWidget(pushButton_pick); setLayout(layout); setContentsMargins(0, 0, 0, 0); // connect(pushButton_pick, &QPushButton::clicked, this, [=] { auto fn = QFileDialog::getOpenFileName(this, QObject::tr("Select"), QDir::currentPath(), "", nullptr, QFileDialog::Option::ReadOnly); if (!fn.isEmpty()) { lineEdit_path->setText(fn); } }); connect(lineEdit_path, &QLineEdit::textChanged, this, [=](const QString &newTxt) { extraCore->insert(coreName, newTxt); }); } }; DialogBasicSettings::DialogBasicSettings(QWidget *parent) : QDialog(parent), ui(new Ui::DialogBasicSettings) { ui->setupUi(this); ADD_ASTERISK(this); // Common ui->log_level->addItems(QStringLiteral("trace debug info warn error fatal panic").split(" ")); ui->mux_protocol->addItems({"h2mux", "smux", "yamux"}); refresh_auth(); D_LOAD_STRING(inbound_address) D_LOAD_COMBO_STRING(log_level) CACHE.custom_inbound = NekoGui::dataStore->custom_inbound; D_LOAD_INT(inbound_socks_port) D_LOAD_INT(test_concurrent) D_LOAD_INT(test_download_timeout) D_LOAD_STRING(test_latency_url) D_LOAD_STRING(test_download_url) D_LOAD_BOOL(old_share_link_format) connect(ui->custom_inbound_edit, &QPushButton::clicked, this, [=] { C_EDIT_JSON_ALLOW_EMPTY(custom_inbound) }); #ifdef Q_OS_WIN connect(ui->sys_proxy_format, &QPushButton::clicked, this, [=] { bool ok; auto str = QInputDialog::getItem(this, ui->sys_proxy_format->text() + " (Windows)", tr("Advanced system proxy settings. Please select a format."), Preset::Windows::system_proxy_format, Preset::Windows::system_proxy_format.indexOf(NekoGui::dataStore->system_proxy_format), false, &ok); if (ok) NekoGui::dataStore->system_proxy_format = str; }); #else ui->sys_proxy_format->hide(); #endif // Style ui->connection_statistics_box->setDisabled(true); // D_LOAD_BOOL(check_include_pre) D_LOAD_BOOL(connection_statistics) D_LOAD_BOOL(start_minimal) D_LOAD_INT(max_log_line) // if (NekoGui::dataStore->traffic_loop_interval == 500) { ui->rfsh_r->setCurrentIndex(0); } else if (NekoGui::dataStore->traffic_loop_interval == 1000) { ui->rfsh_r->setCurrentIndex(1); } else if (NekoGui::dataStore->traffic_loop_interval == 2000) { ui->rfsh_r->setCurrentIndex(2); } else if (NekoGui::dataStore->traffic_loop_interval == 3000) { ui->rfsh_r->setCurrentIndex(3); } else if (NekoGui::dataStore->traffic_loop_interval == 5000) { ui->rfsh_r->setCurrentIndex(4); } else { ui->rfsh_r->setCurrentIndex(5); } // ui->language->setCurrentIndex(NekoGui::dataStore->language); connect(ui->language, static_cast(&QComboBox::currentIndexChanged), this, [=](int index) { CACHE.needRestart = true; }); // int built_in_len = ui->theme->count(); ui->theme->addItems(QStyleFactory::keys()); // bool ok; auto themeId = NekoGui::dataStore->theme.toInt(&ok); if (ok) { ui->theme->setCurrentIndex(themeId); } else { ui->theme->setCurrentText(NekoGui::dataStore->theme); } // connect(ui->theme, static_cast(&QComboBox::currentIndexChanged), this, [=](int index) { if (index + 1 <= built_in_len) { themeManager->ApplyTheme(Int2String(index)); NekoGui::dataStore->theme = Int2String(index); } else { themeManager->ApplyTheme(ui->theme->currentText()); NekoGui::dataStore->theme = ui->theme->currentText(); } repaint(); mainwindow->repaint(); NekoGui::dataStore->Save(); }); // Subscription ui->user_agent->setText(NekoGui::dataStore->user_agent); ui->user_agent->setPlaceholderText(NekoGui::dataStore->GetUserAgent(true)); D_LOAD_BOOL(sub_use_proxy) D_LOAD_BOOL(sub_clear) D_LOAD_BOOL(sub_insecure) D_LOAD_INT_ENABLE(sub_auto_update, sub_auto_update_enable) // Core ui->groupBox_core->setTitle(software_core_name); // CACHE.extraCore = QString2QJsonObject(NekoGui::dataStore->extraCore->core_map); if (!CACHE.extraCore.contains("naive")) CACHE.extraCore.insert("naive", ""); if (!CACHE.extraCore.contains("hysteria2")) CACHE.extraCore.insert("hysteria2", ""); if (!CACHE.extraCore.contains("tuic")) CACHE.extraCore.insert("tuic", ""); // auto extra_core_layout = ui->extra_core_box_scrollAreaWidgetContents->layout(); for (const auto &s: CACHE.extraCore.keys()) { extra_core_layout->addWidget(new ExtraCoreWidget(&CACHE.extraCore, s)); } // connect(ui->extra_core_add, &QPushButton::clicked, this, [=] { bool ok; auto s = QInputDialog::getText(nullptr, tr("Add"), tr("Please input the core name."), QLineEdit::Normal, "", &ok) .trimmed(); if (s.isEmpty() || !ok) return; if (CACHE.extraCore.contains(s)) return; extra_core_layout->addWidget(new ExtraCoreWidget(&CACHE.extraCore, s)); CACHE.extraCore.insert(s, ""); }); connect(ui->extra_core_del, &QPushButton::clicked, this, [=] { bool ok; auto s = QInputDialog::getItem(nullptr, tr("Delete"), tr("Please select the core name."), CACHE.extraCore.keys(), 0, false, &ok); if (s.isEmpty() || !ok) return; for (int i = 0; i < extra_core_layout->count(); i++) { auto item = extra_core_layout->itemAt(i); auto ecw = dynamic_cast(item->widget()); if (ecw != nullptr && ecw->coreName == s) { ecw->deleteLater(); CACHE.extraCore.remove(s); return; } } }); // Mux D_LOAD_INT(mux_concurrency) D_LOAD_COMBO_STRING(mux_protocol) D_LOAD_BOOL(mux_padding) D_LOAD_BOOL(mux_default_on) // Security ui->utlsFingerprint->addItems(Preset::SingBox::UtlsFingerPrint); D_LOAD_BOOL(skip_cert) ui->utlsFingerprint->setCurrentText(NekoGui::dataStore->utlsFingerprint); } DialogBasicSettings::~DialogBasicSettings() { delete ui; } void DialogBasicSettings::accept() { // Common D_SAVE_STRING(inbound_address) D_SAVE_COMBO_STRING(log_level) NekoGui::dataStore->custom_inbound = CACHE.custom_inbound; D_SAVE_INT(inbound_socks_port) D_SAVE_INT(test_concurrent) D_SAVE_INT(test_download_timeout) D_SAVE_STRING(test_latency_url) D_SAVE_STRING(test_download_url) D_SAVE_BOOL(old_share_link_format) // Style NekoGui::dataStore->language = ui->language->currentIndex(); D_SAVE_BOOL(connection_statistics) D_SAVE_BOOL(check_include_pre) D_SAVE_BOOL(start_minimal) D_SAVE_INT(max_log_line) if (NekoGui::dataStore->max_log_line <= 0) { NekoGui::dataStore->max_log_line = 200; } if (ui->rfsh_r->currentIndex() == 0) { NekoGui::dataStore->traffic_loop_interval = 500; } else if (ui->rfsh_r->currentIndex() == 1) { NekoGui::dataStore->traffic_loop_interval = 1000; } else if (ui->rfsh_r->currentIndex() == 2) { NekoGui::dataStore->traffic_loop_interval = 2000; } else if (ui->rfsh_r->currentIndex() == 3) { NekoGui::dataStore->traffic_loop_interval = 3000; } else if (ui->rfsh_r->currentIndex() == 4) { NekoGui::dataStore->traffic_loop_interval = 5000; } else { NekoGui::dataStore->traffic_loop_interval = 0; } // Subscription if (ui->sub_auto_update_enable->isChecked()) { TM_auto_update_subsctiption_Reset_Minute(ui->sub_auto_update->text().toInt()); } else { TM_auto_update_subsctiption_Reset_Minute(0); } NekoGui::dataStore->user_agent = ui->user_agent->text(); D_SAVE_BOOL(sub_use_proxy) D_SAVE_BOOL(sub_clear) D_SAVE_BOOL(sub_insecure) D_SAVE_INT_ENABLE(sub_auto_update, sub_auto_update_enable) // Core NekoGui::dataStore->extraCore->core_map = QJsonObject2QString(CACHE.extraCore, true); // Mux D_SAVE_INT(mux_concurrency) D_SAVE_COMBO_STRING(mux_protocol) D_SAVE_BOOL(mux_padding) D_SAVE_BOOL(mux_default_on) // Security D_SAVE_BOOL(skip_cert) NekoGui::dataStore->utlsFingerprint = ui->utlsFingerprint->currentText(); // 关闭连接统计,停止刷新前清空记录。 if (NekoGui::dataStore->traffic_loop_interval == 0 || !NekoGui::dataStore->connection_statistics) { MW_dialog_message("", "ClearConnectionList"); } QStringList str{"UpdateDataStore"}; if (CACHE.needRestart) str << "NeedRestart"; MW_dialog_message(Dialog_DialogBasicSettings, str.join(",")); QDialog::accept(); } // slots void DialogBasicSettings::refresh_auth() { ui->inbound_auth->setText({}); if (NekoGui::dataStore->inbound_auth->NeedAuth()) { ui->inbound_auth->setIcon(Icon::GetMaterialIcon("lock-outline")); } else { ui->inbound_auth->setIcon(Icon::GetMaterialIcon("lock-open-outline")); } } void DialogBasicSettings::on_set_custom_icon_clicked() { auto title = ui->set_custom_icon->text(); QString user_icon_path = "./" + software_name.toLower() + ".png"; auto c = QMessageBox::question(this, title, tr("Please select a PNG file."), tr("Select"), tr("Reset"), tr("Cancel"), 2, 2); if (c == 0) { auto fn = QFileDialog::getOpenFileName(this, QObject::tr("Select"), QDir::currentPath(), "*.png", nullptr, QFileDialog::Option::ReadOnly); QImage img(fn); if (img.isNull() || img.height() != img.width()) { MessageBoxWarning(title, tr("Please select a valid square image.")); return; } QFile::remove(user_icon_path); QFile::copy(fn, user_icon_path); } else if (c == 1) { QFile::remove(user_icon_path); } else { return; } MW_dialog_message(Dialog_DialogBasicSettings, "UpdateIcon"); } void DialogBasicSettings::on_inbound_auth_clicked() { auto w = new QDialog(this); w->setWindowTitle(tr("Inbound Auth")); auto layout = new QGridLayout; w->setLayout(layout); // auto user_l = new QLabel(tr("Username")); auto pass_l = new QLabel(tr("Password")); auto user = new MyLineEdit; auto pass = new MyLineEdit; user->setText(NekoGui::dataStore->inbound_auth->username); pass->setText(NekoGui::dataStore->inbound_auth->password); // layout->addWidget(user_l, 0, 0); layout->addWidget(user, 0, 1); layout->addWidget(pass_l, 1, 0); layout->addWidget(pass, 1, 1); auto box = new QDialogButtonBox; box->setOrientation(Qt::Horizontal); box->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); connect(box, &QDialogButtonBox::accepted, w, [=] { NekoGui::dataStore->inbound_auth->username = user->text(); NekoGui::dataStore->inbound_auth->password = pass->text(); MW_dialog_message(Dialog_DialogBasicSettings, "UpdateDataStore"); w->accept(); }); connect(box, &QDialogButtonBox::rejected, w, &QDialog::reject); layout->addWidget(box, 2, 1); // w->exec(); w->deleteLater(); refresh_auth(); } void DialogBasicSettings::on_core_settings_clicked() { auto w = new QDialog(this); w->setWindowTitle(software_core_name + " Core Options"); auto layout = new QGridLayout; w->setLayout(layout); // auto line = -1; QCheckBox *core_box_enable_clash_api; MyLineEdit *core_box_clash_api; MyLineEdit *core_box_clash_api_secret; MyLineEdit *core_box_underlying_dns; // auto core_box_underlying_dns_l = new QLabel(tr("Override underlying DNS")); core_box_underlying_dns = new MyLineEdit; core_box_underlying_dns->setText(NekoGui::dataStore->core_box_underlying_dns); core_box_underlying_dns->setMinimumWidth(300); layout->addWidget(core_box_underlying_dns_l, ++line, 0); layout->addWidget(core_box_underlying_dns, line, 1); // auto core_box_enable_clash_api_l = new QLabel("Enable Clash API"); core_box_enable_clash_api = new QCheckBox; core_box_enable_clash_api->setChecked(NekoGui::dataStore->core_box_clash_api > 0); layout->addWidget(core_box_enable_clash_api_l, ++line, 0); layout->addWidget(core_box_enable_clash_api, line, 1); // auto core_box_clash_api_l = new QLabel("Clash API Listen Port"); core_box_clash_api = new MyLineEdit; core_box_clash_api->setText(Int2String(std::abs(NekoGui::dataStore->core_box_clash_api))); layout->addWidget(core_box_clash_api_l, ++line, 0); layout->addWidget(core_box_clash_api, line, 1); // auto core_box_clash_api_secret_l = new QLabel("Clash API Secret"); core_box_clash_api_secret = new MyLineEdit; core_box_clash_api_secret->setText(NekoGui::dataStore->core_box_clash_api_secret); layout->addWidget(core_box_clash_api_secret_l, ++line, 0); layout->addWidget(core_box_clash_api_secret, line, 1); // auto box = new QDialogButtonBox; box->setOrientation(Qt::Horizontal); box->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); connect(box, &QDialogButtonBox::accepted, w, [=] { NekoGui::dataStore->core_box_underlying_dns = core_box_underlying_dns->text(); NekoGui::dataStore->core_box_clash_api = core_box_clash_api->text().toInt() * (core_box_enable_clash_api->isChecked() ? 1 : -1); NekoGui::dataStore->core_box_clash_api_secret = core_box_clash_api_secret->text(); MW_dialog_message(Dialog_DialogBasicSettings, "UpdateDataStore"); w->accept(); }); connect(box, &QDialogButtonBox::rejected, w, &QDialog::reject); layout->addWidget(box, ++line, 1); // ADD_ASTERISK(w) w->exec(); w->deleteLater(); refresh_auth(); } ================================================ FILE: ui/dialog_basic_settings.h ================================================ #ifndef DIALOG_BASIC_SETTINGS_H #define DIALOG_BASIC_SETTINGS_H #include #include namespace Ui { class DialogBasicSettings; } class DialogBasicSettings : public QDialog { Q_OBJECT public: explicit DialogBasicSettings(QWidget *parent = nullptr); ~DialogBasicSettings(); public slots: void accept(); private: Ui::DialogBasicSettings *ui; struct { QJsonObject extraCore; QString custom_inbound; bool needRestart = false; } CACHE; private slots: void refresh_auth(); void on_set_custom_icon_clicked(); void on_inbound_auth_clicked(); void on_core_settings_clicked(); }; #endif // DIALOG_BASIC_SETTINGS_H ================================================ FILE: ui/dialog_basic_settings.ui ================================================ DialogBasicSettings 0 0 600 400 0 0 Basic Settings Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok 0 Common Listen Address Custom Inbound Edit Mixed (SOCKS+HTTP) Listen Port 0 0 Latency Test URL Concurrent Download Test URL Timeout (s) Include Pre-release when checking update Qt::Vertical Share VMess Link with v2rayN Format Old Share Link Format System proxy format 0 0 0 0 0 0 Style 0 0 Theme System flatgray lightblue blacksoft 0 0 Set custom icon 0 0 Language System English 简体中文 فارسی Русский Statistics refresh rate 500ms 1s 2s 3s 5s Off Connection statistics Enable 0 0 Hide dashboard at startup Max log lines 0 0 Subscription 0 0 Enable Interval (minute, invalid if less than 30) 0 0 Use proxy when updating subscription Ignore TLS errors when updating subscription Clear servers before updating subscription 0 0 Automatic update User Agent Core 0 0 core_name 0 0 Multiplex (mux) Loglevel concurrency Padding Default On Core Options Extra Core QFrame::NoFrame true 0 0 632 299 0 0 Add Delete Security Skip TLS certificate authentication by default (allowInsecure) 0 0 Default uTLS Fingerprint true MyLineEdit QLineEdit
ui/widget/MyLineEdit.h
buttonBox accepted() DialogBasicSettings accept() 248 254 157 274 buttonBox rejected() DialogBasicSettings reject() 316 260 286 274
================================================ FILE: ui/dialog_hotkey.cpp ================================================ #include "dialog_hotkey.h" #include "ui_dialog_hotkey.h" #include "ui/mainwindow_interface.h" DialogHotkey::DialogHotkey(QWidget *parent) : QDialog(parent), ui(new Ui::DialogHotkey) { ui->setupUi(this); ui->show_mainwindow->setKeySequence(NekoGui::dataStore->hotkey_mainwindow); ui->show_groups->setKeySequence(NekoGui::dataStore->hotkey_group); ui->show_routes->setKeySequence(NekoGui::dataStore->hotkey_route); ui->system_proxy->setKeySequence(NekoGui::dataStore->hotkey_system_proxy_menu); GetMainWindow()->RegisterHotkey(true); } DialogHotkey::~DialogHotkey() { if (result() == QDialog::Accepted) { NekoGui::dataStore->hotkey_mainwindow = ui->show_mainwindow->keySequence().toString(); NekoGui::dataStore->hotkey_group = ui->show_groups->keySequence().toString(); NekoGui::dataStore->hotkey_route = ui->show_routes->keySequence().toString(); NekoGui::dataStore->hotkey_system_proxy_menu = ui->system_proxy->keySequence().toString(); NekoGui::dataStore->Save(); } GetMainWindow()->RegisterHotkey(false); delete ui; } ================================================ FILE: ui/dialog_hotkey.h ================================================ #pragma once #include #include "main/NekoGui.hpp" QT_BEGIN_NAMESPACE namespace Ui { class DialogHotkey; } QT_END_NAMESPACE class DialogHotkey : public QDialog { Q_OBJECT public: explicit DialogHotkey(QWidget *parent = nullptr); ~DialogHotkey() override; private: Ui::DialogHotkey *ui; }; ================================================ FILE: ui/dialog_hotkey.ui ================================================ DialogHotkey 0 0 400 300 Hotkey Show routes Qt::StrongFocus QDialogButtonBox::Cancel|QDialogButtonBox::Ok Show groups Trigger main window System Proxy QtExtKeySequenceEdit QKeySequenceEdit
3rdparty/QtExtKeySequenceEdit.h
show_mainwindow show_groups show_routes system_proxy buttonBox buttonBox accepted() DialogHotkey accept() 258 255 199 149 buttonBox rejected() DialogHotkey reject() 258 255 199 149
================================================ FILE: ui/dialog_manage_groups.cpp ================================================ #include "dialog_manage_groups.h" #include "ui_dialog_manage_groups.h" #include "db/Database.hpp" #include "sub/GroupUpdater.hpp" #include "main/GuiUtils.hpp" #include "ui/widget/GroupItem.h" #include "ui/edit/dialog_edit_group.h" #include #include #include #define AddGroupToListIfExist(_id) \ auto __ent = NekoGui::profileManager->GetGroup(_id); \ if (__ent != nullptr) { \ auto wI = new QListWidgetItem(); \ auto w = new GroupItem(this, __ent, wI); \ wI->setData(114514, _id); \ ui->listWidget->addItem(wI); \ ui->listWidget->setItemWidget(wI, w); \ } DialogManageGroups::DialogManageGroups(QWidget *parent) : QDialog(parent), ui(new Ui::DialogManageGroups) { ui->setupUi(this); for (auto id: NekoGui::profileManager->groupsTabOrder) { AddGroupToListIfExist(id) } connect(ui->listWidget, &QListWidget::itemDoubleClicked, this, [=](QListWidgetItem *wI) { auto w = dynamic_cast(ui->listWidget->itemWidget(wI)); emit w->edit_clicked(); }); } DialogManageGroups::~DialogManageGroups() { delete ui; } void DialogManageGroups::on_add_clicked() { auto ent = NekoGui::ProfileManager::NewGroup(); auto dialog = new DialogEditGroup(ent, this); int ret = dialog->exec(); dialog->deleteLater(); if (ret == QDialog::Accepted) { NekoGui::profileManager->AddGroup(ent); AddGroupToListIfExist(ent->id); MW_dialog_message(Dialog_DialogManageGroups, "refresh-1"); } } void DialogManageGroups::on_update_all_clicked() { if (QMessageBox::question(this, tr("Confirmation"), tr("Update all subscriptions?")) == QMessageBox::StandardButton::Yes) { UI_update_all_groups(); } } ================================================ FILE: ui/dialog_manage_groups.h ================================================ #pragma once #include #include #include #include #include "db/Group.hpp" QT_BEGIN_NAMESPACE namespace Ui { class DialogManageGroups; } QT_END_NAMESPACE class DialogManageGroups : public QDialog { Q_OBJECT public: explicit DialogManageGroups(QWidget *parent = nullptr); ~DialogManageGroups() override; private: Ui::DialogManageGroups *ui; private slots: void on_add_clicked(); void on_update_all_clicked(); }; ================================================ FILE: ui/dialog_manage_groups.ui ================================================ DialogManageGroups 0 0 640 480 Qt::TabFocus Groups Qt::NoFocus Qt::NoFocus New group Qt::NoFocus Update all subscriptions ================================================ FILE: ui/dialog_manage_routes.cpp ================================================ #include "dialog_manage_routes.h" #include "ui_dialog_manage_routes.h" #include "3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp" #include "3rdparty/qv2ray/v3/components/GeositeReader/GeositeReader.hpp" #include "main/GuiUtils.hpp" #include "fmt/Preset.hpp" #include #include #include #include #define REFRESH_ACTIVE_ROUTING(name, obj) \ this->active_routing = name; \ setWindowTitle(title_base + " [" + name + "]"); \ UpdateDisplayRouting(obj, false); DialogManageRoutes::DialogManageRoutes(QWidget *parent) : QDialog(parent), ui(new Ui::DialogManageRoutes) { ui->setupUi(this); title_base = windowTitle(); QStringList qsValue = {""}; QString dnsHelpDocumentUrl; // ui->outbound_domain_strategy->addItems(Preset::SingBox::DomainStrategy); ui->domainStrategyCombo->addItems(Preset::SingBox::DomainStrategy); qsValue += QStringLiteral("prefer_ipv4 prefer_ipv6 ipv4_only ipv6_only").split(" "); ui->dns_object->setPlaceholderText(DecodeB64IfValid("ewogICJzZXJ2ZXJzIjogW10sCiAgInJ1bGVzIjogW10sCiAgImZpbmFsIjogIiIsCiAgInN0cmF0ZWd5IjogIiIsCiAgImRpc2FibGVfY2FjaGUiOiBmYWxzZSwKICAiZGlzYWJsZV9leHBpcmUiOiBmYWxzZSwKICAiaW5kZXBlbmRlbnRfY2FjaGUiOiBmYWxzZSwKICAicmV2ZXJzZV9tYXBwaW5nIjogZmFsc2UsCiAgImZha2VpcCI6IHt9Cn0=")); dnsHelpDocumentUrl = "https://sing-box.sagernet.org/configuration/dns/"; // ui->direct_dns_strategy->addItems(qsValue); ui->remote_dns_strategy->addItems(qsValue); // D_C_LOAD_STRING(custom_route_global) // connect(ui->use_dns_object, &QCheckBox::stateChanged, this, [=](int state) { auto useDNSObject = state == Qt::Checked; ui->simple_dns_box->setDisabled(useDNSObject); ui->dns_object->setDisabled(!useDNSObject); }); ui->use_dns_object->stateChanged(Qt::Unchecked); // uncheck to uncheck connect(ui->dns_document, &QPushButton::clicked, this, [=] { MessageBoxInfo("DNS", dnsHelpDocumentUrl); }); connect(ui->format_dns_object, &QPushButton::clicked, this, [=] { auto obj = QString2QJsonObject(ui->dns_object->toPlainText()); if (obj.isEmpty()) { MessageBoxInfo("DNS", "invaild json"); } else { ui->dns_object->setPlainText(QJsonObject2QString(obj, false)); } }); // connect(ui->custom_route_edit, &QPushButton::clicked, this, [=] { C_EDIT_JSON_ALLOW_EMPTY(custom_route) }); connect(ui->custom_route_global_edit, &QPushButton::clicked, this, [=] { C_EDIT_JSON_ALLOW_EMPTY(custom_route_global) }); // builtInSchemesMenu = new QMenu(this); builtInSchemesMenu->addActions(this->getBuiltInSchemes()); ui->preset->setMenu(builtInSchemesMenu); QString geoipFn = NekoGui::FindCoreAsset("geoip.dat"); QString geositeFn = NekoGui::FindCoreAsset("geosite.dat"); // const auto sourceStringsDomain = Qv2ray::components::GeositeReader::ReadGeoSiteFromFile(geositeFn); directDomainTxt = new AutoCompleteTextEdit("geosite", sourceStringsDomain, this); proxyDomainTxt = new AutoCompleteTextEdit("geosite", sourceStringsDomain, this); blockDomainTxt = new AutoCompleteTextEdit("geosite", sourceStringsDomain, this); // const auto sourceStringsIP = Qv2ray::components::GeositeReader::ReadGeoSiteFromFile(geoipFn); directIPTxt = new AutoCompleteTextEdit("geoip", sourceStringsIP, this); proxyIPTxt = new AutoCompleteTextEdit("geoip", sourceStringsIP, this); blockIPTxt = new AutoCompleteTextEdit("geoip", sourceStringsIP, this); // ui->directTxtLayout->addWidget(directDomainTxt, 0, 0); ui->proxyTxtLayout->addWidget(proxyDomainTxt, 0, 0); ui->blockTxtLayout->addWidget(blockDomainTxt, 0, 0); // ui->directIPLayout->addWidget(directIPTxt, 0, 0); ui->proxyIPLayout->addWidget(proxyIPTxt, 0, 0); ui->blockIPLayout->addWidget(blockIPTxt, 0, 0); // REFRESH_ACTIVE_ROUTING(NekoGui::dataStore->active_routing, NekoGui::dataStore->routing.get()) ADD_ASTERISK(this) } DialogManageRoutes::~DialogManageRoutes() { delete ui; } void DialogManageRoutes::accept() { D_C_SAVE_STRING(custom_route_global) bool routeChanged = false; if (NekoGui::dataStore->active_routing != active_routing) routeChanged = true; SaveDisplayRouting(NekoGui::dataStore->routing.get()); NekoGui::dataStore->active_routing = active_routing; NekoGui::dataStore->routing->fn = ROUTES_PREFIX + NekoGui::dataStore->active_routing; if (NekoGui::dataStore->routing->Save()) routeChanged = true; // QString info = "UpdateDataStore"; if (routeChanged) info += "RouteChanged"; MW_dialog_message(Dialog_DialogManageRoutes, info); QDialog::accept(); } // built in settings QList DialogManageRoutes::getBuiltInSchemes() { QList list; list.append(this->schemeToAction(tr("Bypass LAN and China"), routing_cn_lan)); list.append(this->schemeToAction(tr("Global"), routing_global)); return list; } QAction *DialogManageRoutes::schemeToAction(const QString &name, const NekoGui::Routing &scheme) { auto *action = new QAction(name, this); connect(action, &QAction::triggered, [this, &scheme] { this->UpdateDisplayRouting((NekoGui::Routing *) &scheme, true); }); return action; } void DialogManageRoutes::UpdateDisplayRouting(NekoGui::Routing *conf, bool qv) { // directDomainTxt->setPlainText(conf->direct_domain); proxyDomainTxt->setPlainText(conf->proxy_domain); blockDomainTxt->setPlainText(conf->block_domain); // blockIPTxt->setPlainText(conf->block_ip); directIPTxt->setPlainText(conf->direct_ip); proxyIPTxt->setPlainText(conf->proxy_ip); // CACHE.custom_route = conf->custom; ui->def_outbound->setCurrentText(conf->def_outbound); // if (qv) return; // ui->sniffing_mode->setCurrentIndex(conf->sniffing_mode); ui->outbound_domain_strategy->setCurrentText(conf->outbound_domain_strategy); ui->domainStrategyCombo->setCurrentText(conf->domain_strategy); ui->use_dns_object->setChecked(conf->use_dns_object); ui->dns_object->setPlainText(conf->dns_object); ui->dns_routing->setChecked(conf->dns_routing); ui->remote_dns->setCurrentText(conf->remote_dns); ui->remote_dns_strategy->setCurrentText(conf->remote_dns_strategy); ui->direct_dns->setCurrentText(conf->direct_dns); ui->direct_dns_strategy->setCurrentText(conf->direct_dns_strategy); ui->dns_final_out->setCurrentText(conf->dns_final_out); } void DialogManageRoutes::SaveDisplayRouting(NekoGui::Routing *conf) { conf->direct_ip = directIPTxt->toPlainText(); conf->direct_domain = directDomainTxt->toPlainText(); conf->proxy_ip = proxyIPTxt->toPlainText(); conf->proxy_domain = proxyDomainTxt->toPlainText(); conf->block_ip = blockIPTxt->toPlainText(); conf->block_domain = blockDomainTxt->toPlainText(); conf->def_outbound = ui->def_outbound->currentText(); conf->custom = CACHE.custom_route; // conf->sniffing_mode = ui->sniffing_mode->currentIndex(); conf->domain_strategy = ui->domainStrategyCombo->currentText(); conf->outbound_domain_strategy = ui->outbound_domain_strategy->currentText(); conf->use_dns_object = ui->use_dns_object->isChecked(); conf->dns_object = ui->dns_object->toPlainText(); conf->dns_routing = ui->dns_routing->isChecked(); conf->remote_dns = ui->remote_dns->currentText(); conf->remote_dns_strategy = ui->remote_dns_strategy->currentText(); conf->direct_dns = ui->direct_dns->currentText(); conf->direct_dns_strategy = ui->direct_dns_strategy->currentText(); conf->dns_final_out = ui->dns_final_out->currentText(); } void DialogManageRoutes::on_load_save_clicked() { auto w = new QDialog; auto layout = new QVBoxLayout; w->setLayout(layout); auto lineEdit = new QLineEdit; layout->addWidget(lineEdit); auto list = new QListWidget; layout->addWidget(list); for (const auto &name: NekoGui::Routing::List()) { list->addItem(name); } connect(list, &QListWidget::currentTextChanged, lineEdit, &QLineEdit::setText); auto bottom = new QHBoxLayout; layout->addLayout(bottom); auto load = new QPushButton; load->setText(tr("Load")); bottom->addWidget(load); auto save = new QPushButton; save->setText(tr("Save")); bottom->addWidget(save); auto remove = new QPushButton; remove->setText(tr("Remove")); bottom->addWidget(remove); auto cancel = new QPushButton; cancel->setText(tr("Cancel")); bottom->addWidget(cancel); connect(load, &QPushButton::clicked, w, [=] { auto fn = lineEdit->text(); if (!fn.isEmpty()) { auto r = std::make_unique(); r->load_control_must = true; r->fn = ROUTES_PREFIX + fn; if (r->Load()) { if (QMessageBox::question(nullptr, software_name, tr("Load routing: %1").arg(fn) + "\n" + r->DisplayRouting()) == QMessageBox::Yes) { REFRESH_ACTIVE_ROUTING(fn, r.get()) // temp save to the window w->accept(); } } } }); connect(save, &QPushButton::clicked, w, [=] { auto fn = lineEdit->text(); if (!fn.isEmpty()) { auto r = std::make_unique(); SaveDisplayRouting(r.get()); r->fn = ROUTES_PREFIX + fn; if (QMessageBox::question(nullptr, software_name, tr("Save routing: %1").arg(fn) + "\n" + r->DisplayRouting()) == QMessageBox::Yes) { r->Save(); REFRESH_ACTIVE_ROUTING(fn, r.get()) w->accept(); } } }); connect(remove, &QPushButton::clicked, w, [=] { auto fn = lineEdit->text(); if (!fn.isEmpty() && NekoGui::Routing::List().length() > 1) { if (QMessageBox::question(nullptr, software_name, tr("Remove routing: %1").arg(fn)) == QMessageBox::Yes) { QFile f(ROUTES_PREFIX + fn); f.remove(); if (NekoGui::dataStore->active_routing == fn) { NekoGui::Routing::SetToActive(NekoGui::Routing::List().first()); REFRESH_ACTIVE_ROUTING(NekoGui::dataStore->active_routing, NekoGui::dataStore->routing.get()) } w->accept(); } } }); connect(cancel, &QPushButton::clicked, w, &QDialog::accept); connect(list, &QListWidget::itemDoubleClicked, this, [=](QListWidgetItem *item) { lineEdit->setText(item->text()); emit load->clicked(); }); w->exec(); w->deleteLater(); } ================================================ FILE: ui/dialog_manage_routes.h ================================================ #pragma once #include #include #include "3rdparty/qv2ray/v2/ui/QvAutoCompleteTextEdit.hpp" #include "main/NekoGui.hpp" QT_BEGIN_NAMESPACE namespace Ui { class DialogManageRoutes; } QT_END_NAMESPACE class DialogManageRoutes : public QDialog { Q_OBJECT public: explicit DialogManageRoutes(QWidget *parent = nullptr); ~DialogManageRoutes() override; private: Ui::DialogManageRoutes *ui; struct { QString custom_route; QString custom_route_global; } CACHE; QMenu *builtInSchemesMenu; Qv2ray::ui::widgets::AutoCompleteTextEdit *directDomainTxt; Qv2ray::ui::widgets::AutoCompleteTextEdit *proxyDomainTxt; Qv2ray::ui::widgets::AutoCompleteTextEdit *blockDomainTxt; // Qv2ray::ui::widgets::AutoCompleteTextEdit *directIPTxt; Qv2ray::ui::widgets::AutoCompleteTextEdit *blockIPTxt; Qv2ray::ui::widgets::AutoCompleteTextEdit *proxyIPTxt; // NekoGui::Routing routing_cn_lan = NekoGui::Routing(1); NekoGui::Routing routing_global = NekoGui::Routing(0); // QString title_base; QString active_routing; public slots: void accept() override; QList getBuiltInSchemes(); QAction *schemeToAction(const QString &name, const NekoGui::Routing &scheme); void UpdateDisplayRouting(NekoGui::Routing *conf, bool qv); void SaveDisplayRouting(NekoGui::Routing *conf); void on_load_save_clicked(); }; ================================================ FILE: ui/dialog_manage_routes.ui ================================================ DialogManageRoutes 0 0 800 600 Routes 0 Common Qt::Vertical 20 40 Route sets Mange route set Custom Route (global) Note: Other settings are independent for each route set. For V2Ray, it sets routing.domainStrategy For sing-box, it sets inbound.domain_strategy Domain Strategy false Disable Sniff result for routing Sniff result for destination Sniffing Mode Server Address Strategy false Qt::Horizontal 40 20 DNS Simple DNS Settings This is especially important and it is recommended to use the default value of "localhost". If the default value does not work, try changing it to "223.5.5.5". For more information, see the document "Configuration/DNS". Direct DNS 0 0 true https://8.8.8.8/dns-query https://1.0.0.1/dns-query https://1.1.1.1/dns-query https://dns.google/dns-query https://one.one.one.one/dns-query https://[2001:4860:4860::8888]/dns-query https://[2606:4700:4700::1111]/dns-query Query Strategy Remote DNS widget Enable DNS Routing Qt::Horizontal 40 20 Final DNS Out proxy bypass 0 0 true local tls://120.53.53.53 https://223.5.5.5/dns-query https://1.12.12.12/dns-query https://dns.alidns.com/dns-query https://doh.pub/dns-query 223.5.5.5 119.29.29.29 2400:3200::1 2402:4e00:: Query Strategy DNS Object Settings Use DNS Object 0 0 Format 0 0 Document Simple Route Block Qt::AlignCenter Direct Qt::AlignCenter Domain Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter Proxy Qt::AlignCenter IP Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 0 0 0 0 Preset QToolButton::InstantPopup Qt::ToolButtonTextBesideIcon Custom Route Qt::Horizontal 40 20 0 0 0 0 Default Outbound proxy bypass block 0 0 QDialogButtonBox::Cancel|QDialogButtonBox::Ok tabWidget sniffing_mode domainStrategyCombo outbound_domain_strategy load_save custom_route_global_edit remote_dns remote_dns_strategy direct_dns direct_dns_strategy dns_routing dns_final_out use_dns_object format_dns_object dns_document dns_object preset custom_route_edit def_outbound buttonBox rejected() DialogManageRoutes reject() 399 574 399 299 buttonBox accepted() DialogManageRoutes accept() 399 574 399 299 ================================================ FILE: ui/dialog_vpn_settings.cpp ================================================ #include "dialog_vpn_settings.h" #include "ui_dialog_vpn_settings.h" #include "main/GuiUtils.hpp" #include "main/NekoGui.hpp" #include "ui/mainwindow_interface.h" #include DialogVPNSettings::DialogVPNSettings(QWidget *parent) : QDialog(parent), ui(new Ui::DialogVPNSettings) { ui->setupUi(this); ADD_ASTERISK(this); ui->fake_dns->setChecked(NekoGui::dataStore->fake_dns); ui->vpn_implementation->setCurrentIndex(NekoGui::dataStore->vpn_implementation); ui->vpn_mtu->setCurrentText(Int2String(NekoGui::dataStore->vpn_mtu)); ui->vpn_ipv6->setChecked(NekoGui::dataStore->vpn_ipv6); ui->hide_console->setChecked(NekoGui::dataStore->vpn_hide_console); #ifndef Q_OS_WIN ui->hide_console->setVisible(false); #endif ui->strict_route->setChecked(NekoGui::dataStore->vpn_strict_route); ui->single_core->setChecked(NekoGui::dataStore->vpn_internal_tun); // D_LOAD_STRING_PLAIN(vpn_rule_cidr) D_LOAD_STRING_PLAIN(vpn_rule_process) // connect(ui->whitelist_mode, &QCheckBox::stateChanged, this, [=](int state) { if (state == Qt::Checked) { ui->gb_cidr->setTitle(tr("Proxy CIDR")); ui->gb_process_name->setTitle(tr("Proxy Process Name")); } else { ui->gb_cidr->setTitle(tr("Bypass CIDR")); ui->gb_process_name->setTitle(tr("Bypass Process Name")); } }); ui->whitelist_mode->setChecked(NekoGui::dataStore->vpn_rule_white); } DialogVPNSettings::~DialogVPNSettings() { delete ui; } void DialogVPNSettings::accept() { // auto mtu = ui->vpn_mtu->currentText().toInt(); if (mtu > 10000 || mtu < 1000) mtu = 9000; NekoGui::dataStore->vpn_implementation = ui->vpn_implementation->currentIndex(); NekoGui::dataStore->fake_dns = ui->fake_dns->isChecked(); NekoGui::dataStore->vpn_mtu = mtu; NekoGui::dataStore->vpn_ipv6 = ui->vpn_ipv6->isChecked(); NekoGui::dataStore->vpn_hide_console = ui->hide_console->isChecked(); NekoGui::dataStore->vpn_strict_route = ui->strict_route->isChecked(); NekoGui::dataStore->vpn_rule_white = ui->whitelist_mode->isChecked(); bool isInternalChanged = NekoGui::dataStore->vpn_internal_tun != ui->single_core->isChecked(); NekoGui::dataStore->vpn_internal_tun = ui->single_core->isChecked(); // D_SAVE_STRING_PLAIN(vpn_rule_cidr) D_SAVE_STRING_PLAIN(vpn_rule_process) // QStringList msg{"UpdateDataStore"}; if (isInternalChanged) { msg << "NeedRestart"; } else { msg << "VPNChanged"; } MW_dialog_message("", msg.join(",")); QDialog::accept(); } void DialogVPNSettings::on_troubleshooting_clicked() { auto r = QMessageBox::information(this, tr("Troubleshooting"), tr("If you have trouble starting VPN, you can force reset nekobox_core process here.\n\n" "If still not working, see documentation for more information.\n" "https://matsuridayo.github.io/n-configuration/#vpn-tun"), tr("Reset"), tr("Cancel"), "", 1, 1); if (r == 0) { GetMainWindow()->StopVPNProcess(true); } } ================================================ FILE: ui/dialog_vpn_settings.h ================================================ #ifndef NEKORAY_DIALOG_VPN_SETTINGS_H #define NEKORAY_DIALOG_VPN_SETTINGS_H #include QT_BEGIN_NAMESPACE namespace Ui { class DialogVPNSettings; } QT_END_NAMESPACE class DialogVPNSettings : public QDialog { Q_OBJECT public: explicit DialogVPNSettings(QWidget *parent = nullptr); ~DialogVPNSettings() override; private: Ui::DialogVPNSettings *ui; public slots: void accept() override; void on_troubleshooting_clicked(); }; #endif // NEKORAY_DIALOG_VPN_SETTINGS_H ================================================ FILE: ui/dialog_vpn_settings.ui ================================================ DialogVPNSettings 0 0 600 600 Tun Settings 0 0 0 0 Stack Mixed gVisor System 0 0 MTU true 9000 1500 Tun Enable IPv6 Strict Route FakeDNS Qt::Vertical Add a tun inbound to the profile startup, instead of using two processes. This needs to be run NekoBox with administrator privileges. Internal Tun Hide Console Bypass CIDR Bypass Process Name Whether blacklisted or whitelisted, your traffic will be handled by nekobox_core (sing-tun). This is NOT equal to "process mode" of some software. Whitelist mode Qt::Horizontal 40 20 Troubleshooting QDialogButtonBox::Cancel|QDialogButtonBox::Ok vpn_implementation vpn_mtu vpn_ipv6 strict_route fake_dns single_core hide_console vpn_rule_cidr vpn_rule_process whitelist_mode troubleshooting buttonBox accepted() DialogVPNSettings accept() 399 575 399 299 buttonBox rejected() DialogVPNSettings reject() 399 575 399 299 ================================================ FILE: ui/edit/dialog_edit_group.cpp ================================================ #include "dialog_edit_group.h" #include "ui_dialog_edit_group.h" #include "db/Database.hpp" #include "ui/mainwindow_interface.h" #include #define ADJUST_SIZE runOnUiThread([=] { adjustSize(); adjustPosition(mainwindow); }, this); DialogEditGroup::DialogEditGroup(const std::shared_ptr &ent, QWidget *parent) : QDialog(parent), ui(new Ui::DialogEditGroup) { ui->setupUi(this); this->ent = ent; connect(ui->type, static_cast(&QComboBox::currentIndexChanged), this, [=](int index) { ui->cat_sub->setHidden(index == 0); ADJUST_SIZE }); ui->name->setText(ent->name); ui->archive->setChecked(ent->archive); ui->skip_auto_update->setChecked(ent->skip_auto_update); ui->url->setText(ent->url); ui->type->setCurrentIndex(ent->url.isEmpty() ? 0 : 1); ui->type->currentIndexChanged(ui->type->currentIndex()); ui->manually_column_width->setChecked(ent->manually_column_width); ui->cat_share->setVisible(false); if (ent->id >= 0) { // already a group ui->type->setDisabled(true); if (!ent->Profiles().isEmpty()) { ui->cat_share->setVisible(true); } } else { // new group ui->front_proxy->hide(); ui->front_proxy_l->hide(); ui->front_proxy_clear->hide(); } CACHE.front_proxy = ent->front_proxy_id; refresh_front_proxy(); connect(ui->front_proxy_clear, &QPushButton::clicked, this, [=] { CACHE.front_proxy = -1; refresh_front_proxy(); }); connect(ui->copy_links, &QPushButton::clicked, this, [=] { QStringList links; for (const auto &[_, profile]: NekoGui::profileManager->profiles) { if (profile->gid != ent->id) continue; links += profile->bean->ToShareLink(); } QApplication::clipboard()->setText(links.join("\n")); MessageBoxInfo(software_name, tr("Copied")); }); connect(ui->copy_links_nkr, &QPushButton::clicked, this, [=] { QStringList links; for (const auto &[_, profile]: NekoGui::profileManager->profiles) { if (profile->gid != ent->id) continue; links += profile->bean->ToNekorayShareLink(profile->type); } QApplication::clipboard()->setText(links.join("\n")); MessageBoxInfo(software_name, tr("Copied")); }); ADJUST_SIZE } DialogEditGroup::~DialogEditGroup() { delete ui; } void DialogEditGroup::accept() { if (ent->id >= 0) { // already a group if (!ent->url.isEmpty() && ui->url->text().isEmpty()) { MessageBoxWarning(tr("Warning"), tr("Please input URL")); return; } } ent->name = ui->name->text(); ent->url = ui->url->text(); ent->archive = ui->archive->isChecked(); ent->skip_auto_update = ui->skip_auto_update->isChecked(); ent->manually_column_width = ui->manually_column_width->isChecked(); ent->front_proxy_id = CACHE.front_proxy; QDialog::accept(); } void DialogEditGroup::refresh_front_proxy() { auto fEnt = NekoGui::profileManager->GetProfile(CACHE.front_proxy); ui->front_proxy->setText(fEnt == nullptr ? tr("None") : fEnt->bean->DisplayTypeAndName()); } void DialogEditGroup::on_front_proxy_clicked() { auto parent = dynamic_cast(this->parent()); parent->hide(); this->hide(); GetMainWindow()->start_select_mode(this, [=](int id) { CACHE.front_proxy = id; refresh_front_proxy(); parent->show(); show(); }); } ================================================ FILE: ui/edit/dialog_edit_group.h ================================================ #pragma once #include #include "db/Group.hpp" QT_BEGIN_NAMESPACE namespace Ui { class DialogEditGroup; } QT_END_NAMESPACE class DialogEditGroup : public QDialog { Q_OBJECT public: explicit DialogEditGroup(const std::shared_ptr &ent, QWidget *parent = nullptr); ~DialogEditGroup() override; private: Ui::DialogEditGroup *ui; std::shared_ptr ent; struct { int front_proxy; } CACHE; void refresh_front_proxy(); private slots: void accept() override; void on_front_proxy_clicked(); }; ================================================ FILE: ui/edit/dialog_edit_group.ui ================================================ DialogEditGroup 0 0 400 468 400 300 Edit Group 0 0 Common Type Basic Subscription Front Proxy Manually column width Archive Name 0 0 Clear Subscription URL Skip automatic update Share Copy profile share links Copy profile share links (Neko Links) QDialogButtonBox::Cancel|QDialogButtonBox::Ok MyLineEdit QLineEdit
ui/widget/MyLineEdit.h
name type front_proxy front_proxy_clear manually_column_width archive url skip_auto_update copy_links copy_links_nkr buttonBox rejected() DialogEditGroup reject() 199 275 199 149 buttonBox accepted() DialogEditGroup accept() 199 291 199 157
================================================ FILE: ui/edit/dialog_edit_profile.cpp ================================================ #include "dialog_edit_profile.h" #include "ui_dialog_edit_profile.h" #include "ui/edit/edit_socks_http.h" #include "ui/edit/edit_shadowsocks.h" #include "ui/edit/edit_chain.h" #include "ui/edit/edit_vmess.h" #include "ui/edit/edit_trojan_vless.h" #include "ui/edit/edit_naive.h" #include "ui/edit/edit_quic.h" #include "ui/edit/edit_custom.h" #include "fmt/includes.h" #include "fmt/Preset.hpp" #include "3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp" #include "main/GuiUtils.hpp" #include #define ADJUST_SIZE runOnUiThread([=] { adjustSize(); adjustPosition(mainwindow); }, this); #define LOAD_TYPE(a) ui->type->addItem(NekoGui::ProfileManager::NewProxyEntity(a)->bean->DisplayType(), a); DialogEditProfile::DialogEditProfile(const QString &_type, int profileOrGroupId, QWidget *parent) : QDialog(parent), ui(new Ui::DialogEditProfile) { // setup UI ui->setupUi(this); ui->dialog_layout->setAlignment(ui->left, Qt::AlignTop); // network changed network_title_base = ui->network_box->title(); connect(ui->network, &QComboBox::currentTextChanged, this, [=](const QString &txt) { ui->network_box->setTitle(network_title_base.arg(txt)); // 传输设置 if (txt == "tcp") { ui->header_type->setVisible(true); ui->header_type_l->setVisible(true); ui->path->setVisible(true); ui->path_l->setVisible(true); ui->host->setVisible(true); ui->host_l->setVisible(true); } else if (txt == "grpc") { ui->header_type->setVisible(false); ui->header_type_l->setVisible(false); ui->path->setVisible(true); ui->path_l->setVisible(true); ui->host->setVisible(false); ui->host_l->setVisible(false); } else if (txt == "ws" || txt == "http" || txt == "httpupgrade") { ui->header_type->setVisible(false); ui->header_type_l->setVisible(false); ui->path->setVisible(true); ui->path_l->setVisible(true); ui->host->setVisible(true); ui->host_l->setVisible(true); } else { ui->header_type->setVisible(false); ui->header_type_l->setVisible(false); ui->path->setVisible(false); ui->path_l->setVisible(false); ui->host->setVisible(false); ui->host_l->setVisible(false); } // 传输设置 ED if (txt == "ws") { ui->ws_early_data_length->setVisible(true); ui->ws_early_data_length_l->setVisible(true); ui->ws_early_data_name->setVisible(true); ui->ws_early_data_name_l->setVisible(true); } else { ui->ws_early_data_length->setVisible(false); ui->ws_early_data_length_l->setVisible(false); ui->ws_early_data_name->setVisible(false); ui->ws_early_data_name_l->setVisible(false); } // 传输设置 for NekoBox if (!ui->utlsFingerprint->count()) ui->utlsFingerprint->addItems(Preset::SingBox::UtlsFingerPrint); // 传输设置 是否可见 int networkBoxVisible = 0; for (auto label: ui->network_box->findChildren()) { if (!label->isHidden()) networkBoxVisible++; } ui->network_box->setVisible(networkBoxVisible); ADJUST_SIZE }); ui->network->removeItem(0); // security changed connect(ui->security, &QComboBox::currentTextChanged, this, [=](const QString &txt) { if (txt == "tls") { ui->security_box->setVisible(true); ui->tls_camouflage_box->setVisible(true); ui->reality_spx->hide(); ui->reality_spx_l->hide(); } else { ui->security_box->setVisible(false); ui->tls_camouflage_box->setVisible(false); } ADJUST_SIZE }); emit ui->security->currentTextChanged(ui->security->currentText()); // 确定模式和 ent newEnt = _type != ""; if (newEnt) { this->groupId = profileOrGroupId; this->type = _type; // load type to combo box LOAD_TYPE("socks") LOAD_TYPE("http") LOAD_TYPE("shadowsocks") LOAD_TYPE("trojan") LOAD_TYPE("vmess") LOAD_TYPE("vless") LOAD_TYPE("naive") LOAD_TYPE("hysteria2") LOAD_TYPE("tuic") ui->type->addItem(tr("Custom (%1 outbound)").arg(software_core_name), "internal"); ui->type->addItem(tr("Custom (%1 config)").arg(software_core_name), "internal-full"); ui->type->addItem(tr("Custom (Extra Core)"), "custom"); LOAD_TYPE("chain") // type changed connect(ui->type, static_cast(&QComboBox::currentIndexChanged), this, [=](int index) { typeSelected(ui->type->itemData(index).toString()); }); ui->apply_to_group->hide(); } else { this->ent = NekoGui::profileManager->GetProfile(profileOrGroupId); if (this->ent == nullptr) return; this->type = ent->type; ui->type->setVisible(false); ui->type_l->setVisible(false); } typeSelected(this->type); } DialogEditProfile::~DialogEditProfile() { delete ui; } void DialogEditProfile::typeSelected(const QString &newType) { QString customType; type = newType; bool validType = true; if (type == "socks" || type == "http") { auto _innerWidget = new EditSocksHttp(this); innerWidget = _innerWidget; innerEditor = _innerWidget; } else if (type == "shadowsocks") { auto _innerWidget = new EditShadowSocks(this); innerWidget = _innerWidget; innerEditor = _innerWidget; } else if (type == "chain") { auto _innerWidget = new EditChain(this); innerWidget = _innerWidget; innerEditor = _innerWidget; } else if (type == "vmess") { auto _innerWidget = new EditVMess(this); innerWidget = _innerWidget; innerEditor = _innerWidget; } else if (type == "trojan" || type == "vless") { auto _innerWidget = new EditTrojanVLESS(this); innerWidget = _innerWidget; innerEditor = _innerWidget; } else if (type == "naive") { auto _innerWidget = new EditNaive(this); innerWidget = _innerWidget; innerEditor = _innerWidget; } else if (type == "hysteria2" || type == "tuic") { auto _innerWidget = new EditQUIC(this); innerWidget = _innerWidget; innerEditor = _innerWidget; } else if (type == "custom" || type == "internal" || type == "internal-full") { auto _innerWidget = new EditCustom(this); innerWidget = _innerWidget; innerEditor = _innerWidget; customType = newEnt ? type : ent->CustomBean()->core; if (customType != "custom") _innerWidget->preset_core = customType; type = "custom"; } else { validType = false; } if (!validType) { MessageBoxWarning(newType, "Wrong type"); return; } if (newEnt) { this->ent = NekoGui::ProfileManager::NewProxyEntity(type); this->ent->gid = groupId; } // hide some widget auto showAddressPort = type != "chain" && customType != "internal" && customType != "internal-full"; ui->address->setVisible(showAddressPort); ui->address_l->setVisible(showAddressPort); ui->port->setVisible(showAddressPort); ui->port_l->setVisible(showAddressPort); // 右边 stream auto stream = GetStreamSettings(ent->bean.get()); if (stream != nullptr) { ui->right_all_w->setVisible(true); ui->network->setCurrentText(stream->network); ui->security->setCurrentText(stream->security); ui->packet_encoding->setCurrentText(stream->packet_encoding); ui->path->setText(stream->path); ui->host->setText(stream->host); ui->sni->setText(stream->sni); ui->alpn->setText(stream->alpn); if (newEnt) { ui->utlsFingerprint->setCurrentText(NekoGui::dataStore->utlsFingerprint); } else { ui->utlsFingerprint->setCurrentText(stream->utlsFingerprint); } ui->insecure->setChecked(stream->allow_insecure); ui->header_type->setCurrentText(stream->header_type); ui->ws_early_data_name->setText(stream->ws_early_data_name); ui->ws_early_data_length->setText(Int2String(stream->ws_early_data_length)); ui->reality_pbk->setText(stream->reality_pbk); ui->reality_sid->setText(stream->reality_sid); ui->multiplex->setCurrentIndex(stream->multiplex_status); CACHE.certificate = stream->certificate; } else { ui->right_all_w->setVisible(false); } // left: custom CACHE.custom_config = ent->bean->custom_config; CACHE.custom_outbound = ent->bean->custom_outbound; bool show_custom_config = true; bool show_custom_outbound = true; if (type == "chain") { show_custom_outbound = false; } else if (type == "custom") { if (customType == "internal") { show_custom_outbound = false; } else if (customType == "internal-full") { show_custom_outbound = false; show_custom_config = false; } } ui->custom_box->setVisible(show_custom_outbound); ui->custom_global_box->setVisible(show_custom_config); // 左边 bean auto old = ui->bean->layout()->itemAt(0)->widget(); ui->bean->layout()->removeWidget(old); innerWidget->layout()->setContentsMargins(0, 0, 0, 0); ui->bean->layout()->addWidget(innerWidget); ui->bean->setTitle(ent->bean->DisplayType()); delete old; // 左边 bean inner editor innerEditor->get_edit_dialog = [&]() { return (QWidget *) this; }; innerEditor->get_edit_text_name = [&]() { return ui->name->text(); }; innerEditor->get_edit_text_serverAddress = [&]() { return ui->address->text(); }; innerEditor->get_edit_text_serverPort = [&]() { return ui->port->text(); }; innerEditor->editor_cache_updated = [=] { editor_cache_updated_impl(); }; innerEditor->onStart(ent); // 左边 common ui->name->setText(ent->bean->name); ui->address->setText(ent->bean->serverAddress); ui->port->setText(Int2String(ent->bean->serverPort)); ui->port->setValidator(QRegExpValidator_Number); // 星号 ADD_ASTERISK(this) // 设置 for NekoBox if (type == "vmess" || type == "vless") { ui->packet_encoding->setVisible(true); ui->packet_encoding_l->setVisible(true); } else { ui->packet_encoding->setVisible(false); ui->packet_encoding_l->setVisible(false); } if (type == "vmess" || type == "vless" || type == "trojan") { ui->network_l->setVisible(true); ui->network->setVisible(true); ui->network_box->setVisible(true); } else { ui->network_l->setVisible(false); ui->network->setVisible(false); ui->network_box->setVisible(false); } if (type == "vmess" || type == "vless" || type == "trojan" || type == "http") { ui->security->setVisible(true); ui->security_l->setVisible(true); } else { ui->security->setVisible(false); ui->security_l->setVisible(false); } if (type == "vmess" || type == "vless" || type == "trojan" || type == "shadowsocks") { ui->multiplex->setVisible(true); ui->multiplex_l->setVisible(true); } else { ui->multiplex->setVisible(false); ui->multiplex_l->setVisible(false); } // 设置 是否可见 int streamBoxVisible = 0; for (auto label: ui->stream_box->findChildren()) { if (!label->isHidden()) streamBoxVisible++; } ui->stream_box->setVisible(streamBoxVisible); // 载入 type 之后,有些类型没有右边的设置 auto rightNoBox = (ui->stream_box->isHidden() && ui->network_box->isHidden() && ui->security_box->isHidden()); if (rightNoBox && !ui->right_all_w->isHidden()) { ui->right_all_w->setVisible(false); } editor_cache_updated_impl(); ADJUST_SIZE // 第一次显示 if (isHidden()) { runOnUiThread([=] { show(); }, this); } } bool DialogEditProfile::onEnd() { // bean if (!innerEditor->onEnd()) { return false; } // 左边 ent->bean->name = ui->name->text(); ent->bean->serverAddress = ui->address->text().remove(' '); ent->bean->serverPort = ui->port->text().toInt(); // 右边 stream auto stream = GetStreamSettings(ent->bean.get()); if (stream != nullptr) { stream->network = ui->network->currentText(); stream->security = ui->security->currentText(); stream->packet_encoding = ui->packet_encoding->currentText(); stream->path = ui->path->text(); stream->host = ui->host->text(); stream->sni = ui->sni->text(); stream->alpn = ui->alpn->text(); stream->utlsFingerprint = ui->utlsFingerprint->currentText(); stream->allow_insecure = ui->insecure->isChecked(); stream->header_type = ui->header_type->currentText(); stream->ws_early_data_name = ui->ws_early_data_name->text(); stream->ws_early_data_length = ui->ws_early_data_length->text().toInt(); stream->reality_pbk = ui->reality_pbk->text(); stream->reality_sid = ui->reality_sid->text(); stream->multiplex_status = ui->multiplex->currentIndex(); stream->certificate = CACHE.certificate; } // cached custom ent->bean->custom_outbound = CACHE.custom_outbound; ent->bean->custom_config = CACHE.custom_config; return true; } void DialogEditProfile::accept() { // save to ent if (!onEnd()) { return; } // finish QStringList msg = {"accept"}; if (newEnt) { auto ok = NekoGui::profileManager->AddProfile(ent); if (!ok) { MessageBoxWarning("???", "id exists"); } } else { auto changed = ent->Save(); if (changed && NekoGui::dataStore->started_id == ent->id) msg << "restart"; } MW_dialog_message(Dialog_DialogEditProfile, msg.join(",")); QDialog::accept(); } // cached editor (dialog) void DialogEditProfile::editor_cache_updated_impl() { if (CACHE.certificate.isEmpty()) { ui->certificate_edit->setText(tr("Not set")); } else { ui->certificate_edit->setText(tr("Already set")); } if (CACHE.custom_outbound.isEmpty()) { ui->custom_outbound_edit->setText(tr("Not set")); } else { ui->custom_outbound_edit->setText(tr("Already set")); } if (CACHE.custom_config.isEmpty()) { ui->custom_config_edit->setText(tr("Not set")); } else { ui->custom_config_edit->setText(tr("Already set")); } // CACHE macro for (auto a: innerEditor->get_editor_cached()) { if (a.second.isEmpty()) { a.first->setText(tr("Not set")); } else { a.first->setText(tr("Already set")); } } } void DialogEditProfile::on_custom_outbound_edit_clicked() { C_EDIT_JSON_ALLOW_EMPTY(custom_outbound) editor_cache_updated_impl(); } void DialogEditProfile::on_custom_config_edit_clicked() { C_EDIT_JSON_ALLOW_EMPTY(custom_config) editor_cache_updated_impl(); } void DialogEditProfile::on_certificate_edit_clicked() { bool ok; auto txt = QInputDialog::getMultiLineText(this, tr("Certificate"), "", CACHE.certificate, &ok); if (ok) { CACHE.certificate = txt; editor_cache_updated_impl(); } } void DialogEditProfile::on_apply_to_group_clicked() { if (apply_to_group_ui.empty()) { apply_to_group_ui[ui->multiplex] = new FloatCheckBox(ui->multiplex, this); apply_to_group_ui[ui->sni] = new FloatCheckBox(ui->sni, this); apply_to_group_ui[ui->alpn] = new FloatCheckBox(ui->alpn, this); apply_to_group_ui[ui->host] = new FloatCheckBox(ui->host, this); apply_to_group_ui[ui->path] = new FloatCheckBox(ui->path, this); apply_to_group_ui[ui->utlsFingerprint] = new FloatCheckBox(ui->utlsFingerprint, this); apply_to_group_ui[ui->insecure] = new FloatCheckBox(ui->insecure, this); apply_to_group_ui[ui->certificate_edit] = new FloatCheckBox(ui->certificate_edit, this); apply_to_group_ui[ui->custom_config_edit] = new FloatCheckBox(ui->custom_config_edit, this); apply_to_group_ui[ui->custom_outbound_edit] = new FloatCheckBox(ui->custom_outbound_edit, this); ui->apply_to_group->setText(tr("Confirm")); } else { auto group = NekoGui::profileManager->GetGroup(ent->gid); if (group == nullptr) { MessageBoxWarning("failed", "unknown group"); return; } // save this if (onEnd()) { ent->Save(); } else { MessageBoxWarning("failed", "failed to save"); return; } // copy keys for (const auto &pair: apply_to_group_ui) { if (pair.second->isChecked()) { do_apply_to_group(group, pair.first); } delete pair.second; } apply_to_group_ui.clear(); ui->apply_to_group->setText(tr("Apply settings to this group")); } } void DialogEditProfile::do_apply_to_group(const std::shared_ptr &group, QWidget *key) { auto stream = GetStreamSettings(ent->bean.get()); auto copyStream = [=](void *p) { for (const auto &profile: group->Profiles()) { auto newStream = GetStreamSettings(profile->bean.get()); if (newStream == nullptr) continue; if (stream == newStream) continue; newStream->_setValue(stream->_name(p), p); // qDebug() << newStream->ToJsonBytes(); profile->Save(); } }; auto copyBean = [=](void *p) { for (const auto &profile: group->Profiles()) { if (profile == ent) continue; profile->bean->_setValue(ent->bean->_name(p), p); // qDebug() << profile->bean->ToJsonBytes(); profile->Save(); } }; if (key == ui->multiplex) { copyStream(&stream->multiplex_status); } else if (key == ui->sni) { copyStream(&stream->sni); } else if (key == ui->alpn) { copyStream(&stream->alpn); } else if (key == ui->host) { copyStream(&stream->host); } else if (key == ui->path) { copyStream(&stream->path); } else if (key == ui->utlsFingerprint) { copyStream(&stream->utlsFingerprint); } else if (key == ui->insecure) { copyStream(&stream->allow_insecure); } else if (key == ui->certificate_edit) { copyStream(&stream->certificate); } else if (key == ui->custom_config_edit) { copyBean(&ent->bean->custom_config); } else if (key == ui->custom_outbound_edit) { copyBean(&ent->bean->custom_outbound); } } ================================================ FILE: ui/edit/dialog_edit_profile.h ================================================ #ifndef DIALOG_EDIT_PROFILE_H #define DIALOG_EDIT_PROFILE_H #include #include "db/Database.hpp" #include "profile_editor.h" #include "ui/widget/FloatCheckBox.h" namespace Ui { class DialogEditProfile; } class DialogEditProfile : public QDialog { Q_OBJECT public: explicit DialogEditProfile(const QString &_type, int profileOrGroupId, QWidget *parent = nullptr); ~DialogEditProfile() override; public slots: void accept() override; private slots: void on_custom_outbound_edit_clicked(); void on_custom_config_edit_clicked(); void on_certificate_edit_clicked(); void on_apply_to_group_clicked(); private: Ui::DialogEditProfile *ui; std::map apply_to_group_ui; QWidget *innerWidget{}; ProfileEditor *innerEditor{}; QString type; int groupId; bool newEnt = false; std::shared_ptr ent; QString network_title_base; struct { QString custom_outbound; QString custom_config; QString certificate; } CACHE; void typeSelected(const QString &newType); bool onEnd(); void editor_cache_updated_impl(); void do_apply_to_group(const std::shared_ptr &group, QWidget *key); }; #endif // DIALOG_EDIT_PROFILE_H ================================================ FILE: ui/edit/dialog_edit_profile.ui ================================================ DialogEditProfile 0 0 1000 802 0 0 Edit 0 0 400 0 QLayout::SetDefaultConstraint 0 0 Common Type Port Address Name 0 0 Bean 0 0 Custom Outbound Settings Edit Custom Config Settings Edit Apply settings to this group 0 0 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok 0 0 400 0 QLayout::SetDefaultConstraint 0 0 Settings 0 0 tcp ws httpupgrade http grpc quic packetaddr xudp The underlying transport method. It must be consistent with the server, otherwise, the connection cannot be established. Network tls Transport Layer Security. It must be consistent with the server, otherwise, the connection cannot be established. Security UDP FullCone Packet encoding for implementing features such as UDP FullCone. Server support is required, if the wrong selection is made, the connection cannot be made. Please leave it blank. Packet Encoding Server support is required Multiplex Keep Default On Off 0 0 Network Settings (%1) http path (ws/http/伪装http) serviceName (gRPC) key (QUIC) Path http host (ws/http/伪装http) security (QUIC) Host 0 0 伪装头部类型 (tcp/quic) header 0 0 true http EarlyData Length EarlyData Name 0 0 TLS Security Settings 0 0 When enabled, V2Ray will not check the validity of the TLS certificate provided by the remote host (the security is equivalent to plaintext) Allow insecure Qt::Vertical 0 0 Certificate Edit Server name indication, clear text. SNI Application layer protocol negotiation, clear text. Please separate them with commas. ALPN TLS Camouflage Settings true 0 0 Fingerprint Reality public key. If not empty, turn TLS into REALITY. Reality Pbk Reality short id. Accept only one value. Reality Sid SpiderX MyLineEdit QLineEdit
ui/widget/MyLineEdit.h
type name address port custom_outbound_edit custom_config_edit apply_to_group network security packet_encoding multiplex header_type path host ws_early_data_length ws_early_data_name insecure certificate_edit sni alpn utlsFingerprint reality_pbk reality_sid buttonBox accepted() DialogEditProfile accept() 151 500 299 299 buttonBox rejected() DialogEditProfile reject() 151 500 299 299
================================================ FILE: ui/edit/edit_chain.cpp ================================================ #include "edit_chain.h" #include "ui_edit_chain.h" #include "ui/mainwindow_interface.h" #include "ui/widget/ProxyItem.h" #include "db/Database.hpp" #include "fmt/ChainBean.hpp" EditChain::EditChain(QWidget *parent) : QWidget(parent), ui(new Ui::EditChain) { ui->setupUi(this); } EditChain::~EditChain() { delete ui; } void EditChain::onStart(std::shared_ptr _ent) { this->ent = _ent; auto bean = this->ent->ChainBean(); for (auto id: bean->list) { AddProfileToListIfExist(id); } } bool EditChain::onEnd() { if (get_edit_text_name().isEmpty()) { MessageBoxWarning(software_name, tr("Name cannot be empty.")); return false; } auto bean = this->ent->ChainBean(); QList idList; for (int i = 0; i < ui->listWidget->count(); i++) { idList << ui->listWidget->item(i)->data(114514).toInt(); } bean->list = idList; return true; } void EditChain::on_select_profile_clicked() { get_edit_dialog()->hide(); GetMainWindow()->start_select_mode(this, [=](int id) { get_edit_dialog()->show(); AddProfileToListIfExist(id); }); } void EditChain::AddProfileToListIfExist(int profileId) { auto _ent = NekoGui::profileManager->GetProfile(profileId); if (_ent != nullptr && _ent->type != "chain") { auto wI = new QListWidgetItem(); wI->setData(114514, profileId); auto w = new ProxyItem(this, _ent, wI); ui->listWidget->addItem(wI); ui->listWidget->setItemWidget(wI, w); // change button connect(w->get_change_button(), &QPushButton::clicked, w, [=] { get_edit_dialog()->hide(); GetMainWindow()->start_select_mode(w, [=](int newId) { get_edit_dialog()->show(); ReplaceProfile(w, newId); }); }); } } void EditChain::ReplaceProfile(ProxyItem *w, int profileId) { auto _ent = NekoGui::profileManager->GetProfile(profileId); if (_ent != nullptr && _ent->type != "chain") { w->item->setData(114514, profileId); w->ent = _ent; w->refresh_data(); } } ================================================ FILE: ui/edit/edit_chain.h ================================================ #pragma once #include #include "profile_editor.h" QT_BEGIN_NAMESPACE namespace Ui { class EditChain; } QT_END_NAMESPACE class ProxyItem; class EditChain : public QWidget, public ProfileEditor { Q_OBJECT public: explicit EditChain(QWidget *parent = nullptr); ~EditChain() override; void onStart(std::shared_ptr _ent) override; bool onEnd() override; private: Ui::EditChain *ui; std::shared_ptr ent; void AddProfileToListIfExist(int profileId); static void ReplaceProfile(ProxyItem *w, int profileId); private slots: void on_select_profile_clicked(); }; ================================================ FILE: ui/edit/edit_chain.ui ================================================ EditChain 0 0 400 400 EditChain Traffic order is from top to bottom 0 300 Qt::ActionsContextMenu QAbstractItemView::InternalMove Qt::MoveAction QListView::Free Select Profile ================================================ FILE: ui/edit/edit_custom.cpp ================================================ #include "edit_custom.h" #include "ui_edit_custom.h" #include "3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp" #include "fmt/CustomBean.hpp" #include "fmt/Preset.hpp" #include "db/ConfigBuilder.hpp" #include "db/Database.hpp" #include #include EditCustom::EditCustom(QWidget *parent) : QWidget(parent), ui(new Ui::EditCustom) { ui->setupUi(this); ui->config_simple->setPlaceholderText( "example:\n" " server-address: \"127.0.0.1:%mapping_port%\"\n" " listen-address: \"127.0.0.1\"\n" " listen-port: %socks_port%\n" " host: your-domain.com\n" " sni: your-domain.com\n"); } EditCustom::~EditCustom() { delete ui; } #define SAVE_CUSTOM_BEAN \ P_SAVE_COMBO_STRING(core) \ bean->command = ui->command->text().split(" "); \ P_SAVE_STRING_PLAIN(config_simple) \ P_SAVE_COMBO_STRING(config_suffix) \ P_SAVE_INT(mapping_port) \ P_SAVE_INT(socks_port) void EditCustom::onStart(std::shared_ptr _ent) { this->ent = _ent; auto bean = this->ent->CustomBean(); // load known core auto core_map = QString2QJsonObject(NekoGui::dataStore->extraCore->core_map); for (const auto &key: core_map.keys()) { ui->core->addItem(key); } if (preset_core == "internal") { preset_command = preset_config = ""; ui->config_simple->setPlaceholderText( "{\n" " \"type\": \"socks\",\n" " // ...\n" "}"); } else if (preset_core == "internal-full") { preset_command = preset_config = ""; ui->config_simple->setPlaceholderText( "{\n" " \"inbounds\": [],\n" " \"outbounds\": []\n" "}"); } // load core ui P_LOAD_COMBO_STRING(core) ui->command->setText(bean->command.join(" ")); ui->config_simple->setPlainText(bean->config_simple); P_LOAD_COMBO_STRING(config_suffix) P_LOAD_INT(mapping_port) P_LOAD_INT(socks_port) // custom external if (!bean->core.isEmpty()) { ui->core->setDisabled(true); } else if (!preset_core.isEmpty()) { bean->core = preset_core; ui->core->setDisabled(true); ui->core->setCurrentText(preset_core); ui->command->setText(preset_command); ui->config_simple->setPlainText(preset_config); } // custom internal if (preset_core == "internal" || preset_core == "internal-full") { ui->core->hide(); if (preset_core == "internal") { ui->core_l->setText(tr("Outbound JSON, please read the documentation.")); } else { ui->core_l->setText(tr("Please fill the complete config.")); } ui->w_ext1->hide(); ui->w_ext2->hide(); } // Preview connect(ui->preview, &QPushButton::clicked, this, [=] { // CustomBean::BuildExternal QStringList th; auto mapping_port = ui->mapping_port->text().toInt(); auto socks_port = ui->socks_port->text().toInt(); th << "%mapping_port% => " + (mapping_port <= 0 ? "Random" : Int2String(mapping_port)); th << "%socks_port% => " + (socks_port <= 0 ? "Random" : Int2String(socks_port)); th << "%server_addr% => " + get_edit_text_serverAddress(); th << "%server_port% => " + get_edit_text_serverPort(); MessageBoxInfo(tr("Preview replace"), th.join("\n")); // EditCustom::onEnd auto tmpEnt = NekoGui::ProfileManager::NewProxyEntity("custom"); auto bean = tmpEnt->CustomBean(); SAVE_CUSTOM_BEAN // 补充 bean->serverAddress = get_edit_text_serverAddress(); bean->serverPort = get_edit_text_serverPort().toInt(); if (bean->core.isEmpty()) return; // auto result = NekoGui::BuildConfig(tmpEnt, false, false); if (!result->error.isEmpty()) { MessageBoxInfo(software_name, result->error); return; } for (const auto &extR: result->extRs) { auto command = QStringList{extR->program}; command += extR->arguments; auto btn = QMessageBox::information(this, tr("Preview config"), QStringLiteral("Command: %1\n\n%2").arg(QStringList2Command(command), extR->config_export), "OK", "Copy", "", 0, 0); if (btn == 1) { QApplication::clipboard()->setText(extR->config_export); } } }); } bool EditCustom::onEnd() { if (get_edit_text_name().isEmpty()) { MessageBoxWarning(software_name, tr("Name cannot be empty.")); return false; } if (ui->core->currentText().isEmpty()) { MessageBoxWarning(software_name, tr("Please pick a core.")); return false; } auto bean = this->ent->CustomBean(); SAVE_CUSTOM_BEAN return true; } void EditCustom::on_as_json_clicked() { auto editor = new JsonEditor(QString2QJsonObject(ui->config_simple->toPlainText()), this); auto result = editor->OpenEditor(); if (!result.isEmpty()) { ui->config_simple->setPlainText(QJsonObject2QString(result, false)); } } ================================================ FILE: ui/edit/edit_custom.h ================================================ #pragma once #include #include "profile_editor.h" QT_BEGIN_NAMESPACE namespace Ui { class EditCustom; } QT_END_NAMESPACE class EditCustom : public QWidget, public ProfileEditor { Q_OBJECT public: QString preset_core; QString preset_command; QString preset_config; explicit EditCustom(QWidget *parent = nullptr); ~EditCustom() override; void onStart(std::shared_ptr _ent) override; bool onEnd() override; private: Ui::EditCustom *ui; std::shared_ptr ent; private slots: void on_as_json_clicked(); }; ================================================ FILE: ui/edit/edit_custom.ui ================================================ EditCustom 0 0 400 450 EditCustom 0 0 Core 0 0 true Qt::Horizontal 40 20 0 0 Json Editor 0 0 0 0 Command %config% Config Suffix true json yml 0 0 0 0 Random if it's empty or zero. Mapping Port Random if it's empty or zero. Socks Port Preview 0 300 core as_json command config_suffix mapping_port socks_port preview config_simple ================================================ FILE: ui/edit/edit_naive.cpp ================================================ #include "edit_naive.h" #include "ui_edit_naive.h" #include "fmt/NaiveBean.hpp" #include EditNaive::EditNaive(QWidget *parent) : QWidget(parent), ui(new Ui::EditNaive) { ui->setupUi(this); } EditNaive::~EditNaive() { delete ui; } void EditNaive::onStart(std::shared_ptr _ent) { this->ent = _ent; auto bean = this->ent->NaiveBean(); P_LOAD_STRING(username); P_LOAD_STRING(password); P_LOAD_COMBO_STRING(protocol); P_C_LOAD_STRING(extra_headers); P_LOAD_STRING(sni); P_C_LOAD_STRING(certificate); P_LOAD_INT(insecure_concurrency); P_LOAD_BOOL(disable_log); } bool EditNaive::onEnd() { auto bean = this->ent->NaiveBean(); P_SAVE_STRING(username); P_SAVE_STRING(password); P_SAVE_COMBO_STRING(protocol); P_C_SAVE_STRING(extra_headers); P_SAVE_STRING(sni); P_C_SAVE_STRING(certificate); P_SAVE_INT(insecure_concurrency); P_SAVE_BOOL(disable_log); return true; } QList> EditNaive::get_editor_cached() { return { {ui->certificate, CACHE.certificate}, {ui->extra_headers, CACHE.extra_headers}, }; } void EditNaive::on_certificate_clicked() { bool ok; auto txt = QInputDialog::getMultiLineText(this, tr("Certificate"), "", CACHE.certificate, &ok); if (ok) { CACHE.certificate = txt; editor_cache_updated(); } } void EditNaive::on_extra_headers_clicked() { bool ok; auto txt = QInputDialog::getMultiLineText(this, tr("Extra headers"), "", CACHE.extra_headers, &ok); if (ok) { CACHE.extra_headers = txt; editor_cache_updated(); } } ================================================ FILE: ui/edit/edit_naive.h ================================================ #pragma once #include #include "profile_editor.h" QT_BEGIN_NAMESPACE namespace Ui { class EditNaive; } QT_END_NAMESPACE class EditNaive : public QWidget, public ProfileEditor { Q_OBJECT public: explicit EditNaive(QWidget *parent = nullptr); ~EditNaive() override; void onStart(std::shared_ptr _ent) override; bool onEnd() override; QList> get_editor_cached() override; private: Ui::EditNaive *ui; std::shared_ptr ent; struct { QString certificate; QString extra_headers; } CACHE; private slots: void on_certificate_clicked(); void on_extra_headers_clicked(); }; ================================================ FILE: ui/edit/edit_naive.ui ================================================ EditNaive 0 0 525 304 EditNaive Username Password Protocol https quic Extra headers PushButton SNI Certificate PushButton Insecure concurrency Disable logs Turn on this option if your connection is lost after a while MyLineEdit QLineEdit
ui/widget/MyLineEdit.h
================================================ FILE: ui/edit/edit_quic.cpp ================================================ #include "edit_quic.h" #include "ui_edit_quic.h" #include "fmt/QUICBean.hpp" #include #include EditQUIC::EditQUIC(QWidget *parent) : QWidget(parent), ui(new Ui::EditQUIC) { ui->setupUi(this); connect(ui->uuidgen, &QPushButton::clicked, this, [=] { ui->uuid->setText(QUuid::createUuid().toString().remove("{").remove("}")); }); } EditQUIC::~EditQUIC() { delete ui; } void EditQUIC::onStart(std::shared_ptr _ent) { this->ent = _ent; auto bean = this->ent->QUICBean(); P_LOAD_STRING(hopPort); P_LOAD_INT(hopInterval); P_LOAD_INT(uploadMbps); P_LOAD_INT(downloadMbps); P_LOAD_BOOL(disableMtuDiscovery) P_LOAD_STRING(obfsPassword); P_LOAD_INT(streamReceiveWindow); P_LOAD_INT(connectionReceiveWindow); P_LOAD_BOOL(forceExternal); P_LOAD_STRING(uuid); P_LOAD_STRING(password); P_LOAD_COMBO_STRING(congestionControl); P_LOAD_COMBO_STRING(udpRelayMode); P_LOAD_BOOL(zeroRttHandshake); P_LOAD_STRING(heartbeat); P_LOAD_BOOL(uos); // TLS P_LOAD_STRING(sni); P_LOAD_STRING(alpn); P_C_LOAD_STRING(caText); P_LOAD_BOOL(allowInsecure); P_LOAD_BOOL(disableSni); if (bean->proxy_type == NekoGui_fmt::QUICBean::proxy_Hysteria2) { ui->uuid->hide(); ui->uuid_l->hide(); ui->uuidgen->hide(); ui->congestionControl->hide(); ui->congestionControl_l->hide(); ui->udpRelayMode->hide(); ui->udpRelayMode_l->hide(); ui->zeroRttHandshake->hide(); ui->heartbeat->hide(); ui->heartbeat_l->hide(); ui->uos->hide(); ui->alpn->hide(); ui->alpn_l->hide(); ui->TLS->removeItem(ui->alpn_sp); ui->disableMtuDiscovery->hide(); ui->connectionReceiveWindow->hide(); ui->connectionReceiveWindow_l->hide(); ui->streamReceiveWindow->hide(); ui->streamReceiveWindow_l->hide(); } else if (bean->proxy_type == NekoGui_fmt::QUICBean::proxy_TUIC) { ui->hopPort->hide(); ui->hopPort_l->hide(); ui->hopInterval->hide(); ui->hopInterval_l->hide(); ui->uploadMbps->hide(); ui->uploadMbps_l->hide(); ui->downloadMbps->hide(); ui->downloadMbps_l->hide(); ui->disableMtuDiscovery->hide(); ui->obfsPassword->hide(); ui->obfsPassword_l->hide(); ui->streamReceiveWindow->hide(); ui->streamReceiveWindow_l->hide(); ui->connectionReceiveWindow->hide(); ui->connectionReceiveWindow_l->hide(); ui->uos->hide(); } } bool EditQUIC::onEnd() { auto bean = this->ent->QUICBean(); P_SAVE_BOOL(forceExternal); // Hysteria 2 P_SAVE_STRING(hopPort); P_SAVE_INT(hopInterval); P_SAVE_INT(uploadMbps); P_SAVE_INT(downloadMbps); P_SAVE_BOOL(disableMtuDiscovery) P_SAVE_STRING(obfsPassword); P_SAVE_INT(streamReceiveWindow); P_SAVE_INT(connectionReceiveWindow); // TUIC P_SAVE_STRING(uuid); P_SAVE_STRING(password); P_SAVE_COMBO_STRING(congestionControl); P_SAVE_COMBO_STRING(udpRelayMode); P_SAVE_BOOL(zeroRttHandshake); P_SAVE_STRING(heartbeat); P_SAVE_BOOL(uos); // TLS P_SAVE_STRING(sni); P_SAVE_STRING(alpn); P_SAVE_BOOL(allowInsecure); P_C_SAVE_STRING(caText); P_SAVE_BOOL(disableSni); return true; } QList> EditQUIC::get_editor_cached() { return { {ui->certificate, CACHE.caText}, }; } void EditQUIC::on_certificate_clicked() { bool ok; auto txt = QInputDialog::getMultiLineText(this, tr("Certificate"), "", CACHE.caText, &ok); if (ok) { CACHE.caText = txt; editor_cache_updated(); } } ================================================ FILE: ui/edit/edit_quic.h ================================================ #pragma once #include #include #include #include "profile_editor.h" QT_BEGIN_NAMESPACE namespace Ui { class EditQUIC; } QT_END_NAMESPACE class EditQUIC : public QWidget, public ProfileEditor { Q_OBJECT public: explicit EditQUIC(QWidget *parent = nullptr); ~EditQUIC() override; void onStart(std::shared_ptr _ent) override; bool onEnd() override; QList> get_editor_cached() override; private: Ui::EditQUIC *ui; std::shared_ptr ent; struct { QString caText; } CACHE; private slots: void on_certificate_clicked(); }; ================================================ FILE: ui/edit/edit_quic.ui ================================================ EditQUIC 0 0 500 628 EditHysteria Download (Mbps) Hop Port Hop Interval (s) Heartbeat 0 0 Upload (Mbps) Zero Rtt Handshake Congestion Control bbr cubic new_reno UDP Relay Mode native quic Force use external core Requires sing-box server UDP over Stream 0 0 Disable MTU Discovery Obfs Password Generate UUID UUID 0 0 Password Certificate PushButton Disable SNI ALPN SNI Allow Insecure Qt::Horizontal QSizePolicy::Maximum 40 20 recv_window recv_window_conn MyLineEdit QLineEdit
ui/widget/MyLineEdit.h
hopPort hopInterval uploadMbps downloadMbps congestionControl udpRelayMode heartbeat zeroRttHandshake forceExternal uos disableMtuDiscovery obfsPassword uuid uuidgen password sni disableSni alpn certificate allowInsecure streamReceiveWindow connectionReceiveWindow
================================================ FILE: ui/edit/edit_shadowsocks.cpp ================================================ #include "edit_shadowsocks.h" #include "ui_edit_shadowsocks.h" #include "fmt/ShadowSocksBean.hpp" #include "fmt/Preset.hpp" EditShadowSocks::EditShadowSocks(QWidget *parent) : QWidget(parent), ui(new Ui::EditShadowSocks) { ui->setupUi(this); ui->method->addItems(Preset::SingBox::ShadowsocksMethods); } EditShadowSocks::~EditShadowSocks() { delete ui; } void EditShadowSocks::onStart(std::shared_ptr _ent) { this->ent = _ent; auto bean = this->ent->ShadowSocksBean(); ui->method->setCurrentText(bean->method); ui->uot->setCurrentIndex(bean->uot); ui->password->setText(bean->password); auto ssPlugin = bean->plugin.split(";"); if (!ssPlugin.empty()) { ui->plugin->setCurrentText(ssPlugin[0]); ui->plugin_opts->setText(SubStrAfter(bean->plugin, ";")); } } bool EditShadowSocks::onEnd() { auto bean = this->ent->ShadowSocksBean(); bean->method = ui->method->currentText(); bean->password = ui->password->text(); bean->uot = ui->uot->currentIndex(); bean->plugin = ui->plugin->currentText(); if (!bean->plugin.isEmpty()) { bean->plugin += ";" + ui->plugin_opts->text(); } return true; } ================================================ FILE: ui/edit/edit_shadowsocks.h ================================================ #ifndef EDIT_SHADOWSOCKS_H #define EDIT_SHADOWSOCKS_H #include #include "profile_editor.h" namespace Ui { class EditShadowSocks; } class EditShadowSocks : public QWidget, public ProfileEditor { Q_OBJECT public: explicit EditShadowSocks(QWidget *parent = nullptr); ~EditShadowSocks() override; void onStart(std::shared_ptr _ent) override; bool onEnd() override; private: Ui::EditShadowSocks *ui; std::shared_ptr ent; }; #endif // EDIT_SHADOWSOCKS_H ================================================ FILE: ui/edit/edit_shadowsocks.ui ================================================ EditShadowSocks 0 0 400 300 Form true Plugin Encryption Plugin Args Password obfs-local v2ray-plugin Version of UDP over TCP protocol, server support is required. UoT Off 1 2 MyLineEdit QLineEdit
ui/widget/MyLineEdit.h
method password plugin plugin_opts
================================================ FILE: ui/edit/edit_socks_http.cpp ================================================ #include "edit_socks_http.h" #include "ui_edit_socks_http.h" #include "fmt/SocksHttpBean.hpp" EditSocksHttp::EditSocksHttp(QWidget *parent) : QWidget(parent), ui(new Ui::EditSocksHttp) { ui->setupUi(this); } EditSocksHttp::~EditSocksHttp() { delete ui; } void EditSocksHttp::onStart(std::shared_ptr _ent) { this->ent = _ent; auto bean = this->ent->SocksHTTPBean(); if (bean->socks_http_type == NekoGui_fmt::SocksHttpBean::type_Socks4) { ui->version->setCurrentIndex(1); } else { ui->version->setCurrentIndex(0); } if (bean->socks_http_type == NekoGui_fmt::SocksHttpBean::type_HTTP) { ui->version->setVisible(false); ui->version_l->setVisible(false); } ui->username->setText(bean->username); ui->password->setText(bean->password); } bool EditSocksHttp::onEnd() { auto bean = this->ent->SocksHTTPBean(); if (ui->version->isVisible()) { if (ui->version->currentIndex() == 1) { bean->socks_http_type = NekoGui_fmt::SocksHttpBean::type_Socks4; } else { bean->socks_http_type = NekoGui_fmt::SocksHttpBean::type_Socks5; } } bean->username = ui->username->text(); bean->password = ui->password->text(); return true; } ================================================ FILE: ui/edit/edit_socks_http.h ================================================ #pragma once #include #include "profile_editor.h" namespace Ui { class EditSocksHttp; } class EditSocksHttp : public QWidget, public ProfileEditor { Q_OBJECT public: explicit EditSocksHttp(QWidget *parent = nullptr); ~EditSocksHttp() override; void onStart(std::shared_ptr _ent) override; bool onEnd() override; private: Ui::EditSocksHttp *ui; std::shared_ptr ent; }; ================================================ FILE: ui/edit/edit_socks_http.ui ================================================ EditSocksHttp 0 0 400 300 Form Version Username 5 4 Password version username password ================================================ FILE: ui/edit/edit_trojan_vless.cpp ================================================ #include "edit_trojan_vless.h" #include "ui_edit_trojan_vless.h" #include "fmt/TrojanVLESSBean.hpp" #include "fmt/Preset.hpp" EditTrojanVLESS::EditTrojanVLESS(QWidget *parent) : QWidget(parent), ui(new Ui::EditTrojanVLESS) { ui->setupUi(this); } EditTrojanVLESS::~EditTrojanVLESS() { delete ui; } void EditTrojanVLESS::onStart(std::shared_ptr _ent) { this->ent = _ent; auto bean = this->ent->TrojanVLESSBean(); if (bean->proxy_type == NekoGui_fmt::TrojanVLESSBean::proxy_VLESS) { ui->label->setText("UUID"); } if (bean->proxy_type != NekoGui_fmt::TrojanVLESSBean::proxy_VLESS) { ui->flow->hide(); ui->flow_l->hide(); } ui->password->setText(bean->password); ui->flow->addItems(Preset::SingBox::Flows); ui->flow->setCurrentText(bean->flow); } bool EditTrojanVLESS::onEnd() { auto bean = this->ent->TrojanVLESSBean(); bean->password = ui->password->text(); bean->flow = ui->flow->currentText(); return true; } ================================================ FILE: ui/edit/edit_trojan_vless.h ================================================ #pragma once #include #include "profile_editor.h" QT_BEGIN_NAMESPACE namespace Ui { class EditTrojanVLESS; } QT_END_NAMESPACE class EditTrojanVLESS : public QWidget, public ProfileEditor { Q_OBJECT public: explicit EditTrojanVLESS(QWidget *parent = nullptr); ~EditTrojanVLESS() override; void onStart(std::shared_ptr _ent) override; bool onEnd() override; private: Ui::EditTrojanVLESS *ui; std::shared_ptr ent; }; ================================================ FILE: ui/edit/edit_trojan_vless.ui ================================================ EditTrojanVLESS 0 0 400 300 Password Flow MyLineEdit QLineEdit
ui/widget/MyLineEdit.h
================================================ FILE: ui/edit/edit_vmess.cpp ================================================ #include "edit_vmess.h" #include "ui_edit_vmess.h" #include "fmt/VMessBean.hpp" #include EditVMess::EditVMess(QWidget *parent) : QWidget(parent), ui(new Ui::EditVMess) { ui->setupUi(this); connect(ui->uuidgen, &QPushButton::clicked, this, [=] { ui->uuid->setText(QUuid::createUuid().toString().remove("{").remove("}")); }); } EditVMess::~EditVMess() { delete ui; } void EditVMess::onStart(std::shared_ptr _ent) { this->ent = _ent; auto bean = this->ent->VMessBean(); ui->uuid->setText(bean->uuid); ui->aid->setText(Int2String(bean->aid)); ui->security->setCurrentText(bean->security); } bool EditVMess::onEnd() { auto bean = this->ent->VMessBean(); bean->uuid = ui->uuid->text(); bean->aid = ui->aid->text().toInt(); bean->security = ui->security->currentText(); return true; } ================================================ FILE: ui/edit/edit_vmess.h ================================================ #pragma once #include #include "profile_editor.h" QT_BEGIN_NAMESPACE namespace Ui { class EditVMess; } QT_END_NAMESPACE class EditVMess : public QWidget, public ProfileEditor { Q_OBJECT public: explicit EditVMess(QWidget *parent = nullptr); ~EditVMess() override; void onStart(std::shared_ptr _ent) override; bool onEnd() override; private: Ui::EditVMess *ui; std::shared_ptr ent; }; ================================================ FILE: ui/edit/edit_vmess.ui ================================================ EditVMess 0 0 400 300 EditVMess true auto zero none chacha20-poly1305 aes-128-gcm Security Alter Id UUID 0 0 0 0 0 0 Generate UUID MyLineEdit QLineEdit
ui/widget/MyLineEdit.h
uuid aid uuidgen security
================================================ FILE: ui/edit/profile_editor.h ================================================ #pragma once #include #include "db/ProxyEntity.hpp" #include "main/GuiUtils.hpp" class ProfileEditor { public: virtual void onStart(std::shared_ptr ent) = 0; virtual bool onEnd() = 0; std::function get_edit_dialog; std::function get_edit_text_name; std::function get_edit_text_serverAddress; std::function get_edit_text_serverPort; // cached editor std::function editor_cache_updated; virtual QList> get_editor_cached() { return {}; }; }; ================================================ FILE: ui/mainwindow.cpp ================================================ #include "./ui_mainwindow.h" #include "mainwindow.h" #include "fmt/Preset.hpp" #include "db/ProfileFilter.hpp" #include "db/ConfigBuilder.hpp" #include "sub/GroupUpdater.hpp" #include "sys/ExternalProcess.hpp" #include "sys/AutoRun.hpp" #include "ui/ThemeManager.hpp" #include "ui/Icon.hpp" #include "ui/edit/dialog_edit_profile.h" #include "ui/dialog_basic_settings.h" #include "ui/dialog_manage_groups.h" #include "ui/dialog_manage_routes.h" #include "ui/dialog_vpn_settings.h" #include "ui/dialog_hotkey.h" #include "3rdparty/fix_old_qt.h" #include "3rdparty/qrcodegen.hpp" #include "3rdparty/VT100Parser.hpp" #include "3rdparty/qv2ray/v2/components/proxy/QvProxyConfigurator.hpp" #ifndef NKR_NO_ZXING #include "3rdparty/ZxingQtReader.hpp" #endif #ifdef Q_OS_WIN #include "3rdparty/WinCommander.hpp" #else #ifdef Q_OS_LINUX #include "sys/linux/LinuxCap.h" #endif #include #endif #include #include #include #include #include #include #include #include #include #include #include #include void UI_InitMainWindow() { mainwindow = new MainWindow; } MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { mainwindow = this; MW_dialog_message = [=](const QString &a, const QString &b) { runOnUiThread([=] { dialog_message_impl(a, b); }); }; // Load Manager NekoGui::profileManager->LoadManager(); // Setup misc UI themeManager->ApplyTheme(NekoGui::dataStore->theme); ui->setupUi(this); // connect(ui->menu_start, &QAction::triggered, this, [=]() { neko_start(); }); connect(ui->menu_stop, &QAction::triggered, this, [=]() { neko_stop(); }); connect(ui->tabWidget->tabBar(), &QTabBar::tabMoved, this, [=](int from, int to) { // use tabData to track tab & gid NekoGui::profileManager->groupsTabOrder.clear(); for (int i = 0; i < ui->tabWidget->tabBar()->count(); i++) { NekoGui::profileManager->groupsTabOrder += ui->tabWidget->tabBar()->tabData(i).toInt(); } NekoGui::profileManager->SaveManager(); }); ui->label_running->installEventFilter(this); ui->label_inbound->installEventFilter(this); ui->splitter->installEventFilter(this); // RegisterHotkey(false); // auto last_size = NekoGui::dataStore->mw_size.split("x"); if (last_size.length() == 2) { auto w = last_size[0].toInt(); auto h = last_size[1].toInt(); if (w > 0 && h > 0) { resize(w, h); } } if (QDir("dashboard").count() == 0) { QDir().mkdir("dashboard"); QFile::copy(":/neko/dashboard-notice.html", "dashboard/index.html"); } // top bar ui->toolButton_program->setMenu(ui->menu_program); ui->toolButton_preferences->setMenu(ui->menu_preferences); ui->toolButton_server->setMenu(ui->menu_server); ui->menubar->setVisible(false); connect(ui->toolButton_document, &QToolButton::clicked, this, [=] { QDesktopServices::openUrl(QUrl("https://matsuridayo.github.io/")); }); connect(ui->toolButton_ads, &QToolButton::clicked, this, [=] { QDesktopServices::openUrl(QUrl("https://neko-box.pages.dev/喵")); }); connect(ui->toolButton_update, &QToolButton::clicked, this, [=] { runOnNewThread([=] { CheckUpdate(); }); }); connect(ui->toolButton_url_test, &QToolButton::clicked, this, [=] { speedtest_current_group(1, true); }); // Setup log UI ui->splitter->restoreState(DecodeB64IfValid(NekoGui::dataStore->splitter_state)); qvLogDocument->setUndoRedoEnabled(false); ui->masterLogBrowser->setUndoRedoEnabled(false); ui->masterLogBrowser->setDocument(qvLogDocument); ui->masterLogBrowser->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); { auto font = ui->masterLogBrowser->font(); font.setPointSize(9); ui->masterLogBrowser->setFont(font); qvLogDocument->setDefaultFont(font); } connect(ui->masterLogBrowser->verticalScrollBar(), &QSlider::valueChanged, this, [=](int value) { if (ui->masterLogBrowser->verticalScrollBar()->maximum() == value) qvLogAutoScoll = true; else qvLogAutoScoll = false; }); connect(ui->masterLogBrowser, &QTextBrowser::textChanged, this, [=]() { if (!qvLogAutoScoll) return; auto bar = ui->masterLogBrowser->verticalScrollBar(); bar->setValue(bar->maximum()); }); MW_show_log = [=](const QString &log) { runOnUiThread([=] { show_log_impl(log); }); }; MW_show_log_ext = [=](const QString &tag, const QString &log) { runOnUiThread([=] { show_log_impl("[" + tag + "] " + log); }); }; MW_show_log_ext_vt100 = [=](const QString &log) { runOnUiThread([=] { show_log_impl(cleanVT100String(log)); }); }; // table UI ui->proxyListTable->callback_save_order = [=] { auto group = NekoGui::profileManager->CurrentGroup(); group->order = ui->proxyListTable->order; group->Save(); }; ui->proxyListTable->refresh_data = [=](int id) { refresh_proxy_list_impl_refresh_data(id); }; if (auto button = ui->proxyListTable->findChild(QString(), Qt::FindDirectChildrenOnly)) { // Corner Button connect(button, &QAbstractButton::clicked, this, [=] { refresh_proxy_list_impl(-1, {GroupSortMethod::ById}); }); } connect(ui->proxyListTable->horizontalHeader(), &QHeaderView::sectionClicked, this, [=](int logicalIndex) { GroupSortAction action; // 不正确的descending实现 if (proxy_last_order == logicalIndex) { action.descending = true; proxy_last_order = -1; } else { proxy_last_order = logicalIndex; } action.save_sort = true; // 表头 if (logicalIndex == 0) { action.method = GroupSortMethod::ByType; } else if (logicalIndex == 1) { action.method = GroupSortMethod::ByAddress; } else if (logicalIndex == 2) { action.method = GroupSortMethod::ByName; } else if (logicalIndex == 3) { action.method = GroupSortMethod::ByLatency; } else { return; } refresh_proxy_list_impl(-1, action); }); connect(ui->proxyListTable->horizontalHeader(), &QHeaderView::sectionResized, this, [=](int logicalIndex, int oldSize, int newSize) { auto group = NekoGui::profileManager->CurrentGroup(); if (NekoGui::dataStore->refreshing_group || group == nullptr || !group->manually_column_width) return; // save manually column width group->column_width.clear(); for (int i = 0; i < ui->proxyListTable->horizontalHeader()->count(); i++) { group->column_width.push_back(ui->proxyListTable->horizontalHeader()->sectionSize(i)); } group->column_width[logicalIndex] = newSize; group->Save(); }); ui->tableWidget_conn->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); ui->tableWidget_conn->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); ui->tableWidget_conn->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); ui->proxyListTable->verticalHeader()->setDefaultSectionSize(24); // search box ui->search->setVisible(false); connect(shortcut_ctrl_f, &QShortcut::activated, this, [=] { ui->search->setVisible(true); ui->search->setFocus(); }); connect(shortcut_esc, &QShortcut::activated, this, [=] { if (ui->search->isVisible()) { ui->search->setText(""); ui->search->textChanged(""); ui->search->setVisible(false); } if (select_mode) { emit profile_selected(-1); select_mode = false; refresh_status(); } }); connect(ui->search, &QLineEdit::textChanged, this, [=](const QString &text) { if (text.isEmpty()) { for (int i = 0; i < ui->proxyListTable->rowCount(); i++) { ui->proxyListTable->setRowHidden(i, false); } } else { QList findItem = ui->proxyListTable->findItems(text, Qt::MatchContains); for (int i = 0; i < ui->proxyListTable->rowCount(); i++) { ui->proxyListTable->setRowHidden(i, true); } for (auto item: findItem) { if (item != nullptr) ui->proxyListTable->setRowHidden(item->row(), false); } } }); // refresh this->refresh_groups(); // Setup Tray tray = new QSystemTrayIcon(this); // 初始化托盘对象tray tray->setIcon(Icon::GetTrayIcon(Icon::NONE)); tray->setContextMenu(ui->menu_program); // 创建托盘菜单 tray->show(); // 让托盘图标显示在系统托盘上 connect(tray, &QSystemTrayIcon::activated, this, [=](QSystemTrayIcon::ActivationReason reason) { if (reason == QSystemTrayIcon::Trigger) { if (this->isVisible()) { hide(); } else { ActivateWindow(this); } } }); // Misc menu connect(ui->menu_open_config_folder, &QAction::triggered, this, [=] { QDesktopServices::openUrl(QUrl::fromLocalFile(QDir::currentPath())); }); ui->menu_program_preference->addActions(ui->menu_preferences->actions()); connect(ui->menu_add_from_clipboard2, &QAction::triggered, ui->menu_add_from_clipboard, &QAction::trigger); connect(ui->actionRestart_Proxy, &QAction::triggered, this, [=] { if (NekoGui::dataStore->started_id>=0) neko_start(NekoGui::dataStore->started_id); }); connect(ui->actionRestart_Program, &QAction::triggered, this, [=] { MW_dialog_message("", "RestartProgram"); }); connect(ui->actionShow_window, &QAction::triggered, this, [=] { tray->activated(QSystemTrayIcon::ActivationReason::Trigger); }); // connect(ui->menu_program, &QMenu::aboutToShow, this, [=]() { ui->actionRemember_last_proxy->setChecked(NekoGui::dataStore->remember_enable); ui->actionStart_with_system->setChecked(AutoRun_IsEnabled()); ui->actionAllow_LAN->setChecked(QStringList{"::", "0.0.0.0"}.contains(NekoGui::dataStore->inbound_address)); // active server for (const auto &old: ui->menuActive_Server->actions()) { ui->menuActive_Server->removeAction(old); old->deleteLater(); } int active_server_item_count = 0; for (const auto &pf: NekoGui::profileManager->CurrentGroup()->ProfilesWithOrder()) { auto a = new QAction(pf->bean->DisplayTypeAndName(), this); a->setProperty("id", pf->id); a->setCheckable(true); if (NekoGui::dataStore->started_id == pf->id) a->setChecked(true); ui->menuActive_Server->addAction(a); if (++active_server_item_count == 100) break; } // active routing for (const auto &old: ui->menuActive_Routing->actions()) { ui->menuActive_Routing->removeAction(old); old->deleteLater(); } for (const auto &name: NekoGui::Routing::List()) { auto a = new QAction(name, this); a->setCheckable(true); a->setChecked(name == NekoGui::dataStore->active_routing); ui->menuActive_Routing->addAction(a); } }); connect(ui->menuActive_Server, &QMenu::triggered, this, [=](QAction *a) { bool ok; auto id = a->property("id").toInt(&ok); if (!ok) return; if (NekoGui::dataStore->started_id == id) { neko_stop(); } else { neko_start(id); } }); connect(ui->menuActive_Routing, &QMenu::triggered, this, [=](QAction *a) { auto fn = a->text(); if (!fn.isEmpty()) { NekoGui::Routing r; r.load_control_must = true; r.fn = ROUTES_PREFIX + fn; if (r.Load()) { if (QMessageBox::question(GetMessageBoxParent(), software_name, tr("Load routing and apply: %1").arg(fn) + "\n" + r.DisplayRouting()) == QMessageBox::Yes) { NekoGui::Routing::SetToActive(fn); if (NekoGui::dataStore->started_id >= 0) { neko_start(NekoGui::dataStore->started_id); } else { refresh_status(); } } } } }); connect(ui->actionRemember_last_proxy, &QAction::triggered, this, [=](bool checked) { NekoGui::dataStore->remember_enable = checked; NekoGui::dataStore->Save(); }); connect(ui->actionStart_with_system, &QAction::triggered, this, [=](bool checked) { AutoRun_SetEnabled(checked); }); connect(ui->actionAllow_LAN, &QAction::triggered, this, [=](bool checked) { NekoGui::dataStore->inbound_address = checked ? "::" : "127.0.0.1"; MW_dialog_message("", "UpdateDataStore"); }); // connect(ui->checkBox_VPN, &QCheckBox::clicked, this, [=](bool checked) { neko_set_spmode_vpn(checked); }); connect(ui->checkBox_SystemProxy, &QCheckBox::clicked, this, [=](bool checked) { neko_set_spmode_system_proxy(checked); }); connect(ui->menu_spmode, &QMenu::aboutToShow, this, [=]() { ui->menu_spmode_disabled->setChecked(!(NekoGui::dataStore->spmode_system_proxy || NekoGui::dataStore->spmode_vpn)); ui->menu_spmode_system_proxy->setChecked(NekoGui::dataStore->spmode_system_proxy); ui->menu_spmode_vpn->setChecked(NekoGui::dataStore->spmode_vpn); }); connect(ui->menu_spmode_system_proxy, &QAction::triggered, this, [=](bool checked) { neko_set_spmode_system_proxy(checked); }); connect(ui->menu_spmode_vpn, &QAction::triggered, this, [=](bool checked) { neko_set_spmode_vpn(checked); }); connect(ui->menu_spmode_disabled, &QAction::triggered, this, [=]() { neko_set_spmode_system_proxy(false); neko_set_spmode_vpn(false); }); connect(ui->menu_qr, &QAction::triggered, this, [=]() { display_qr_link(false); }); connect(ui->menu_tcp_ping, &QAction::triggered, this, [=]() { speedtest_current_group(0, false); }); connect(ui->menu_url_test, &QAction::triggered, this, [=]() { speedtest_current_group(1, false); }); connect(ui->menu_full_test, &QAction::triggered, this, [=]() { speedtest_current_group(2, false); }); connect(ui->menu_stop_testing, &QAction::triggered, this, [=]() { speedtest_current_group(114514, false); }); // auto set_selected_or_group = [=](int mode) { // 0=group 1=select 2=unknown(menu is hide) ui->menu_server->setProperty("selected_or_group", mode); }; auto move_tests_to_menu = [=](bool menuCurrent_Select) { return [=] { if (menuCurrent_Select) { ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_tcp_ping); ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_url_test); ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_full_test); ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_stop_testing); ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_clear_test_result); ui->menuCurrent_Select->insertAction(ui->actionfake_4, ui->menu_resolve_domain); } else { ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_tcp_ping); ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_url_test); ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_full_test); ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_stop_testing); ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_clear_test_result); ui->menuCurrent_Group->insertAction(ui->actionfake_5, ui->menu_resolve_domain); } set_selected_or_group(menuCurrent_Select ? 1 : 0); }; }; connect(ui->menuCurrent_Select, &QMenu::aboutToShow, this, move_tests_to_menu(true)); connect(ui->menuCurrent_Group, &QMenu::aboutToShow, this, move_tests_to_menu(false)); connect(ui->menu_server, &QMenu::aboutToHide, this, [=] { setTimeout([=] { set_selected_or_group(2); }, this, 200); }); set_selected_or_group(2); // connect(ui->menu_share_item, &QMenu::aboutToShow, this, [=] { QString name; auto selected = get_now_selected_list(); if (!selected.isEmpty()) { auto ent = selected.first(); name = ent->bean->DisplayCoreType(); } ui->menu_export_config->setVisible(name == software_core_name); ui->menu_export_config->setText(tr("Export %1 config").arg(name)); }); refresh_status(); // Prepare core NekoGui::dataStore->core_token = GetRandomString(32); NekoGui::dataStore->core_port = MkPort(); if (NekoGui::dataStore->core_port <= 0) NekoGui::dataStore->core_port = 19810; auto core_path = QApplication::applicationDirPath() + "/"; core_path += "nekobox_core"; QStringList args; args.push_back("nekobox"); args.push_back("-port"); args.push_back(Int2String(NekoGui::dataStore->core_port)); if (NekoGui::dataStore->flag_debug) args.push_back("-debug"); // Start core runOnUiThread( [=] { core_process = new NekoGui_sys::CoreProcess(core_path, args); // Remember last started if (NekoGui::dataStore->remember_enable && NekoGui::dataStore->remember_id >= 0) { core_process->start_profile_when_core_is_up = NekoGui::dataStore->remember_id; } // Setup core_process->Start(); setup_grpc(); }, DS_cores); // Remember system proxy if (NekoGui::dataStore->remember_enable || NekoGui::dataStore->flag_restart_tun_on) { if (NekoGui::dataStore->remember_spmode.contains("system_proxy")) { neko_set_spmode_system_proxy(true, false); } if (NekoGui::dataStore->remember_spmode.contains("vpn") || NekoGui::dataStore->flag_restart_tun_on) { neko_set_spmode_vpn(true, false); } } connect(qApp, &QGuiApplication::commitDataRequest, this, &MainWindow::on_commitDataRequest); auto t = new QTimer; connect(t, &QTimer::timeout, this, [=]() { refresh_status(); }); t->start(2000); t = new QTimer; connect(t, &QTimer::timeout, this, [&] { NekoGui_sys::logCounter.fetchAndStoreRelaxed(0); }); t->start(1000); TM_auto_update_subsctiption = new QTimer; TM_auto_update_subsctiption_Reset_Minute = [&](int m) { TM_auto_update_subsctiption->stop(); if (m >= 30) TM_auto_update_subsctiption->start(m * 60 * 1000); }; connect(TM_auto_update_subsctiption, &QTimer::timeout, this, [&] { UI_update_all_groups(true); }); TM_auto_update_subsctiption_Reset_Minute(NekoGui::dataStore->sub_auto_update); if (!NekoGui::dataStore->flag_tray) show(); } void MainWindow::closeEvent(QCloseEvent *event) { if (tray->isVisible()) { hide(); // 隐藏窗口 event->ignore(); // 忽略事件 } } MainWindow::~MainWindow() { delete ui; } // Group tab manage inline int tabIndex2GroupId(int index) { if (NekoGui::profileManager->groupsTabOrder.length() <= index) return -1; return NekoGui::profileManager->groupsTabOrder[index]; } inline int groupId2TabIndex(int gid) { for (int key = 0; key < NekoGui::profileManager->groupsTabOrder.count(); key++) { if (NekoGui::profileManager->groupsTabOrder[key] == gid) return key; } return 0; } void MainWindow::on_tabWidget_currentChanged(int index) { if (NekoGui::dataStore->refreshing_group_list) return; if (tabIndex2GroupId(index) == NekoGui::dataStore->current_group) return; show_group(tabIndex2GroupId(index)); } void MainWindow::show_group(int gid) { if (NekoGui::dataStore->refreshing_group) return; NekoGui::dataStore->refreshing_group = true; auto group = NekoGui::profileManager->GetGroup(gid); if (group == nullptr) { MessageBoxWarning(tr("Error"), QStringLiteral("No such group: %1").arg(gid)); NekoGui::dataStore->refreshing_group = false; return; } if (NekoGui::dataStore->current_group != gid) { NekoGui::dataStore->current_group = gid; NekoGui::dataStore->Save(); } ui->tabWidget->widget(groupId2TabIndex(gid))->layout()->addWidget(ui->proxyListTable); // 列宽是否可调 if (group->manually_column_width) { for (int i = 0; i <= 4; i++) { ui->proxyListTable->horizontalHeader()->setSectionResizeMode(i, QHeaderView::Interactive); auto size = group->column_width.value(i); if (size <= 0) size = ui->proxyListTable->horizontalHeader()->defaultSectionSize(); ui->proxyListTable->horizontalHeader()->resizeSection(i, size); } } else { ui->proxyListTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); ui->proxyListTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); ui->proxyListTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); ui->proxyListTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::ResizeToContents); ui->proxyListTable->horizontalHeader()->setSectionResizeMode(4, QHeaderView::ResizeToContents); } // show proxies GroupSortAction gsa; gsa.scroll_to_started = true; refresh_proxy_list_impl(-1, gsa); NekoGui::dataStore->refreshing_group = false; } // callback void MainWindow::dialog_message_impl(const QString &sender, const QString &info) { // info if (info.contains("UpdateIcon")) { icon_status = -1; refresh_status(); } if (info.contains("UpdateDataStore")) { auto suggestRestartProxy = NekoGui::dataStore->Save(); if (info.contains("RouteChanged")) { suggestRestartProxy = true; } if (info.contains("NeedRestart")) { suggestRestartProxy = false; } refresh_proxy_list(); if (info.contains("VPNChanged") && NekoGui::dataStore->spmode_vpn) { MessageBoxWarning(tr("Tun Settings changed"), tr("Restart Tun to take effect.")); } if (suggestRestartProxy && NekoGui::dataStore->started_id >= 0 && QMessageBox::question(GetMessageBoxParent(), tr("Confirmation"), tr("Settings changed, restart proxy?")) == QMessageBox::StandardButton::Yes) { neko_start(NekoGui::dataStore->started_id); } refresh_status(); } if (info.contains("NeedRestart")) { auto n = QMessageBox::warning(GetMessageBoxParent(), tr("Settings changed"), tr("Restart the program to take effect."), QMessageBox::Yes | QMessageBox::No); if (n == QMessageBox::Yes) { this->exit_reason = 2; on_menu_exit_triggered(); } } // if (info == "RestartProgram") { this->exit_reason = 2; on_menu_exit_triggered(); } else if (info == "Raise") { ActivateWindow(this); } else if (info == "ClearConnectionList") { refresh_connection_list({}); } // sender if (sender == Dialog_DialogEditProfile) { auto msg = info.split(","); if (msg.contains("accept")) { refresh_proxy_list(); if (msg.contains("restart")) { if (QMessageBox::question(GetMessageBoxParent(), tr("Confirmation"), tr("Settings changed, restart proxy?")) == QMessageBox::StandardButton::Yes) { neko_start(NekoGui::dataStore->started_id); } } } } else if (sender == Dialog_DialogManageGroups) { if (info.startsWith("refresh")) { this->refresh_groups(); } } else if (sender == "SubUpdater") { if (info.startsWith("finish")) { refresh_proxy_list(); if (!info.contains("dingyue")) { show_log_impl(tr("Imported %1 profile(s)").arg(NekoGui::dataStore->imported_count)); } } else if (info == "NewGroup") { refresh_groups(); } } else if (sender == "ExternalProcess") { if (info == "Crashed") { neko_stop(); } else if (info == "CoreCrashed") { neko_stop(true); } else if (info.startsWith("CoreStarted")) { neko_start(info.split(",")[1].toInt()); } } } // top bar & tray menu inline bool dialog_is_using = false; #define USE_DIALOG(a) \ if (dialog_is_using) return; \ dialog_is_using = true; \ auto dialog = new a(this); \ connect(dialog, &QDialog::finished, this, [=] { \ dialog->deleteLater(); \ dialog_is_using = false; \ }); \ dialog->show(); void MainWindow::on_menu_basic_settings_triggered() { USE_DIALOG(DialogBasicSettings) } void MainWindow::on_menu_manage_groups_triggered() { USE_DIALOG(DialogManageGroups) } void MainWindow::on_menu_routing_settings_triggered() { USE_DIALOG(DialogManageRoutes) } void MainWindow::on_menu_vpn_settings_triggered() { USE_DIALOG(DialogVPNSettings) } void MainWindow::on_menu_hotkey_settings_triggered() { USE_DIALOG(DialogHotkey) } void MainWindow::on_commitDataRequest() { qDebug() << "Start of data save"; // if (!isMaximized()) { auto olds = NekoGui::dataStore->mw_size; auto news = QStringLiteral("%1x%2").arg(size().width()).arg(size().height()); if (olds != news) { NekoGui::dataStore->mw_size = news; } } // NekoGui::dataStore->splitter_state = ui->splitter->saveState().toBase64(); // auto last_id = NekoGui::dataStore->started_id; if (NekoGui::dataStore->remember_enable && last_id >= 0) { NekoGui::dataStore->remember_id = last_id; } // NekoGui::dataStore->Save(); NekoGui::profileManager->SaveManager(); qDebug() << "End of data save"; } void MainWindow::on_menu_exit_triggered() { if (mu_exit.tryLock()) { NekoGui::dataStore->prepare_exit = true; // neko_set_spmode_system_proxy(false, false); neko_set_spmode_vpn(false, false); if (NekoGui::dataStore->spmode_vpn) { mu_exit.unlock(); // retry return; } RegisterHotkey(true); // on_commitDataRequest(); // NekoGui::dataStore->save_control_no_save = true; // don't change datastore after this line neko_stop(false, true); // hide(); runOnNewThread([=] { sem_stopped.acquire(); stop_core_daemon(); runOnUiThread([=] { on_menu_exit_triggered(); // continue exit progress }); }); return; } // MF_release_runguard(); if (exit_reason == 1) { QDir::setCurrent(QApplication::applicationDirPath()); QProcess::startDetached("./updater", QStringList{}); } else if (exit_reason == 2 || exit_reason == 3) { QDir::setCurrent(QApplication::applicationDirPath()); auto arguments = NekoGui::dataStore->argv; if (arguments.length() > 0) { arguments.removeFirst(); arguments.removeAll("-tray"); arguments.removeAll("-flag_restart_tun_on"); arguments.removeAll("-flag_reorder"); } auto isLauncher = qEnvironmentVariable("NKR_FROM_LAUNCHER") == "1"; if (isLauncher) arguments.prepend("--"); auto program = isLauncher ? "./launcher" : QApplication::applicationFilePath(); if (exit_reason == 3) { // Tun restart as admin arguments << "-flag_restart_tun_on"; #ifdef Q_OS_WIN WinCommander::runProcessElevated(program, arguments, "", WinCommander::SW_NORMAL, false); #else QProcess::startDetached(program, arguments); #endif } else { QProcess::startDetached(program, arguments); } } tray->hide(); QCoreApplication::quit(); } #define neko_set_spmode_FAILED \ refresh_status(); \ return; void MainWindow::neko_set_spmode_system_proxy(bool enable, bool save) { if (enable != NekoGui::dataStore->spmode_system_proxy) { if (enable) { auto socks_port = NekoGui::dataStore->inbound_socks_port; auto http_port = NekoGui::dataStore->inbound_socks_port; SetSystemProxy(http_port, socks_port); } else { ClearSystemProxy(); } } if (save) { NekoGui::dataStore->remember_spmode.removeAll("system_proxy"); if (enable && NekoGui::dataStore->remember_enable) { NekoGui::dataStore->remember_spmode.append("system_proxy"); } NekoGui::dataStore->Save(); } NekoGui::dataStore->spmode_system_proxy = enable; refresh_status(); } void MainWindow::neko_set_spmode_vpn(bool enable, bool save) { if (enable != NekoGui::dataStore->spmode_vpn) { if (enable) { if (NekoGui::dataStore->vpn_internal_tun) { bool requestPermission = !NekoGui::IsAdmin(); if (requestPermission) { #ifdef Q_OS_LINUX if (!Linux_HavePkexec()) { MessageBoxWarning(software_name, "Please install \"pkexec\" first."); neko_set_spmode_FAILED } auto ret = Linux_Pkexec_SetCapString(NekoGui::FindNekoBoxCoreRealPath(), "cap_net_admin=ep"); if (ret == 0) { this->exit_reason = 3; on_menu_exit_triggered(); } else { MessageBoxWarning(software_name, "Setcap for Tun mode failed.\n\n1. You may canceled the dialog.\n2. You may be using an incompatible environment like AppImage."); if (QProcessEnvironment::systemEnvironment().contains("APPIMAGE")) { MW_show_log("If you are using AppImage, it's impossible to start a Tun. Please use other package instead."); } } #endif #ifdef Q_OS_WIN auto n = QMessageBox::warning(GetMessageBoxParent(), software_name, tr("Please run NekoBox as admin"), QMessageBox::Yes | QMessageBox::No); if (n == QMessageBox::Yes) { this->exit_reason = 3; on_menu_exit_triggered(); } #endif neko_set_spmode_FAILED } } else { if (NekoGui::dataStore->need_keep_vpn_off) { MessageBoxWarning(software_name, tr("Current server is incompatible with Tun. Please stop the server first, enable Tun Mode, and then restart.")); neko_set_spmode_FAILED } if (!StartVPNProcess()) { neko_set_spmode_FAILED } } } else { if (NekoGui::dataStore->vpn_internal_tun) { // current core is sing-box } else { if (!StopVPNProcess()) { neko_set_spmode_FAILED } } } } if (save) { NekoGui::dataStore->remember_spmode.removeAll("vpn"); if (enable && NekoGui::dataStore->remember_enable) { NekoGui::dataStore->remember_spmode.append("vpn"); } NekoGui::dataStore->Save(); } NekoGui::dataStore->spmode_vpn = enable; refresh_status(); if (NekoGui::dataStore->vpn_internal_tun && NekoGui::dataStore->started_id >= 0) neko_start(NekoGui::dataStore->started_id); } void MainWindow::refresh_status(const QString &traffic_update) { auto refresh_speed_label = [=] { if (traffic_update_cache == "") { ui->label_speed->setText(QObject::tr("Proxy: %1\nDirect: %2").arg("", "")); } else { ui->label_speed->setText(traffic_update_cache); } }; // From TrafficLooper if (!traffic_update.isEmpty()) { traffic_update_cache = traffic_update; if (traffic_update == "STOP") { traffic_update_cache = ""; } else { refresh_speed_label(); return; } } refresh_speed_label(); // From UI QString group_name; if (running != nullptr) { auto group = NekoGui::profileManager->GetGroup(running->gid); if (group != nullptr) group_name = group->name; } if (last_test_time.addSecs(2) < QTime::currentTime()) { auto txt = running == nullptr ? tr("Not Running") : QStringLiteral("[%1] %2").arg(group_name, running->bean->DisplayName()).left(30); ui->label_running->setText(txt); } // auto display_socks = DisplayAddress(NekoGui::dataStore->inbound_address, NekoGui::dataStore->inbound_socks_port); auto inbound_txt = QStringLiteral("Mixed: %1").arg(display_socks); ui->label_inbound->setText(inbound_txt); // ui->checkBox_VPN->setChecked(NekoGui::dataStore->spmode_vpn); ui->checkBox_SystemProxy->setChecked(NekoGui::dataStore->spmode_system_proxy); if (select_mode) { ui->label_running->setText(tr("Select") + " *"); ui->label_running->setToolTip(tr("Select mode, double-click or press Enter to select a profile, press ESC to exit.")); } else { ui->label_running->setToolTip({}); } auto make_title = [=](bool isTray) { QStringList tt; if (!isTray && NekoGui::IsAdmin()) tt << "[Admin]"; if (select_mode) tt << "[" + tr("Select") + "]"; if (!title_error.isEmpty()) tt << "[" + title_error + "]"; if (NekoGui::dataStore->spmode_vpn && !NekoGui::dataStore->spmode_system_proxy) tt << "[Tun]"; if (!NekoGui::dataStore->spmode_vpn && NekoGui::dataStore->spmode_system_proxy) tt << "[" + tr("System Proxy") + "]"; if (NekoGui::dataStore->spmode_vpn && NekoGui::dataStore->spmode_system_proxy) tt << "[Tun+" + tr("System Proxy") + "]"; tt << software_name; if (!isTray) tt << "(" + QString(NKR_VERSION) + ")"; if (!NekoGui::dataStore->active_routing.isEmpty() && NekoGui::dataStore->active_routing != "Default") { tt << "[" + NekoGui::dataStore->active_routing + "]"; } if (running != nullptr) tt << running->bean->DisplayTypeAndName() + "@" + group_name; return tt.join(isTray ? "\n" : " "); }; auto icon_status_new = Icon::NONE; if (running != nullptr) { if (NekoGui::dataStore->spmode_vpn) { icon_status_new = Icon::VPN; } else if (NekoGui::dataStore->spmode_system_proxy) { icon_status_new = Icon::SYSTEM_PROXY; } else { icon_status_new = Icon::RUNNING; } } // refresh title & window icon setWindowTitle(make_title(false)); if (icon_status_new != icon_status) QApplication::setWindowIcon(Icon::GetTrayIcon(Icon::NONE)); // refresh tray if (tray != nullptr) { tray->setToolTip(make_title(true)); if (icon_status_new != icon_status) tray->setIcon(Icon::GetTrayIcon(icon_status_new)); } icon_status = icon_status_new; } // table显示 // refresh_groups -> show_group -> refresh_proxy_list void MainWindow::refresh_groups() { NekoGui::dataStore->refreshing_group_list = true; // refresh group? for (int i = ui->tabWidget->count() - 1; i > 0; i--) { ui->tabWidget->removeTab(i); } int index = 0; for (const auto &gid: NekoGui::profileManager->groupsTabOrder) { auto group = NekoGui::profileManager->GetGroup(gid); if (index == 0) { ui->tabWidget->setTabText(0, group->name); } else { auto widget2 = new QWidget(); auto layout2 = new QVBoxLayout(); layout2->setContentsMargins(QMargins()); layout2->setSpacing(0); widget2->setLayout(layout2); ui->tabWidget->addTab(widget2, group->name); } ui->tabWidget->tabBar()->setTabData(index, gid); index++; } // show after group changed if (NekoGui::profileManager->CurrentGroup() == nullptr) { NekoGui::dataStore->current_group = -1; ui->tabWidget->setCurrentIndex(groupId2TabIndex(0)); show_group(NekoGui::profileManager->groupsTabOrder.count() > 0 ? NekoGui::profileManager->groupsTabOrder.first() : 0); } else { ui->tabWidget->setCurrentIndex(groupId2TabIndex(NekoGui::dataStore->current_group)); show_group(NekoGui::dataStore->current_group); } NekoGui::dataStore->refreshing_group_list = false; } void MainWindow::refresh_proxy_list(const int &id) { refresh_proxy_list_impl(id, {}); } void MainWindow::refresh_proxy_list_impl(const int &id, GroupSortAction groupSortAction) { // id < 0 重绘 if (id < 0) { // 清空数据 ui->proxyListTable->row2Id.clear(); ui->proxyListTable->setRowCount(0); // 添加行 int row = -1; for (const auto &[id, profile]: NekoGui::profileManager->profiles) { if (NekoGui::dataStore->current_group != profile->gid) continue; row++; ui->proxyListTable->insertRow(row); ui->proxyListTable->row2Id += id; } } // 显示排序 if (id < 0) { switch (groupSortAction.method) { case GroupSortMethod::Raw: { auto group = NekoGui::profileManager->CurrentGroup(); if (group == nullptr) return; ui->proxyListTable->order = group->order; break; } case GroupSortMethod::ById: { // Clear Order ui->proxyListTable->order.clear(); ui->proxyListTable->callback_save_order(); break; } case GroupSortMethod::ByAddress: case GroupSortMethod::ByName: case GroupSortMethod::ByLatency: case GroupSortMethod::ByType: { std::sort(ui->proxyListTable->order.begin(), ui->proxyListTable->order.end(), [=](int a, int b) { QString ms_a; QString ms_b; if (groupSortAction.method == GroupSortMethod::ByType) { ms_a = NekoGui::profileManager->GetProfile(a)->bean->DisplayType(); ms_b = NekoGui::profileManager->GetProfile(b)->bean->DisplayType(); } else if (groupSortAction.method == GroupSortMethod::ByName) { ms_a = NekoGui::profileManager->GetProfile(a)->bean->name; ms_b = NekoGui::profileManager->GetProfile(b)->bean->name; } else if (groupSortAction.method == GroupSortMethod::ByAddress) { ms_a = NekoGui::profileManager->GetProfile(a)->bean->DisplayAddress(); ms_b = NekoGui::profileManager->GetProfile(b)->bean->DisplayAddress(); } else if (groupSortAction.method == GroupSortMethod::ByLatency) { ms_a = NekoGui::profileManager->GetProfile(a)->full_test_report; ms_b = NekoGui::profileManager->GetProfile(b)->full_test_report; } auto get_latency_for_sort = [](int id) { auto i = NekoGui::profileManager->GetProfile(id)->latency; if (i == 0) i = 100000; if (i < 0) i = 99999; return i; }; if (groupSortAction.descending) { if (groupSortAction.method == GroupSortMethod::ByLatency) { if (ms_a.isEmpty() && ms_b.isEmpty()) { // compare latency if full_test_report is empty return get_latency_for_sort(a) > get_latency_for_sort(b); } } return ms_a > ms_b; } else { if (groupSortAction.method == GroupSortMethod::ByLatency) { auto int_a = NekoGui::profileManager->GetProfile(a)->latency; auto int_b = NekoGui::profileManager->GetProfile(b)->latency; if (ms_a.isEmpty() && ms_b.isEmpty()) { // compare latency if full_test_report is empty return get_latency_for_sort(a) < get_latency_for_sort(b); } } return ms_a < ms_b; } }); break; } } ui->proxyListTable->update_order(groupSortAction.save_sort); } // refresh data refresh_proxy_list_impl_refresh_data(id); } void MainWindow::refresh_proxy_list_impl_refresh_data(const int &id) { // 绘制或更新item(s) for (int row = 0; row < ui->proxyListTable->rowCount(); row++) { auto profileId = ui->proxyListTable->row2Id[row]; if (id >= 0 && profileId != id) continue; // refresh ONE item auto profile = NekoGui::profileManager->GetProfile(profileId); if (profile == nullptr) continue; auto isRunning = profileId == NekoGui::dataStore->started_id; auto f0 = std::make_unique(); f0->setData(114514, profileId); // Check state auto check = f0->clone(); check->setText(isRunning ? "✓" : Int2String(row + 1)); ui->proxyListTable->setVerticalHeaderItem(row, check); // C0: Type auto f = f0->clone(); f->setText(profile->bean->DisplayType()); if (isRunning) f->setForeground(palette().link()); ui->proxyListTable->setItem(row, 0, f); // C1: Address+Port f = f0->clone(); f->setText(profile->bean->DisplayAddress()); if (isRunning) f->setForeground(palette().link()); ui->proxyListTable->setItem(row, 1, f); // C2: Name f = f0->clone(); f->setText(profile->bean->name); if (isRunning) f->setForeground(palette().link()); ui->proxyListTable->setItem(row, 2, f); // C3: Test Result f = f0->clone(); if (profile->full_test_report.isEmpty()) { auto color = profile->DisplayLatencyColor(); if (color.isValid()) f->setForeground(color); f->setText(profile->DisplayLatency()); } else { f->setText(profile->full_test_report); } ui->proxyListTable->setItem(row, 3, f); // C4: Traffic f = f0->clone(); f->setText(profile->traffic_data->DisplayTraffic()); ui->proxyListTable->setItem(row, 4, f); } } // table菜单相关 void MainWindow::on_proxyListTable_itemDoubleClicked(QTableWidgetItem *item) { auto id = item->data(114514).toInt(); if (select_mode) { emit profile_selected(id); select_mode = false; refresh_status(); return; } auto dialog = new DialogEditProfile("", id, this); connect(dialog, &QDialog::finished, dialog, &QDialog::deleteLater); } void MainWindow::on_menu_add_from_input_triggered() { auto dialog = new DialogEditProfile("socks", NekoGui::dataStore->current_group, this); connect(dialog, &QDialog::finished, dialog, &QDialog::deleteLater); } void MainWindow::on_menu_add_from_clipboard_triggered() { auto clipboard = QApplication::clipboard()->text(); NekoGui_sub::groupUpdater->AsyncUpdate(clipboard); } void MainWindow::on_menu_clone_triggered() { auto ents = get_now_selected_list(); if (ents.isEmpty()) return; auto btn = QMessageBox::question(this, tr("Clone"), tr("Clone %1 item(s)").arg(ents.count())); if (btn != QMessageBox::Yes) return; QStringList sls; for (const auto &ent: ents) { sls << ent->bean->ToNekorayShareLink(ent->type); } NekoGui_sub::groupUpdater->AsyncUpdate(sls.join("\n")); } void MainWindow::on_menu_move_triggered() { auto ents = get_now_selected_list(); if (ents.isEmpty()) return; auto items = QStringList{}; for (auto gid: NekoGui::profileManager->groupsTabOrder) { auto group = NekoGui::profileManager->GetGroup(gid); if (group == nullptr) continue; items += Int2String(gid) + " " + group->name; } bool ok; auto a = QInputDialog::getItem(nullptr, tr("Move"), tr("Move %1 item(s)").arg(ents.count()), items, 0, false, &ok); if (!ok) return; auto gid = SubStrBefore(a, " ").toInt(); for (const auto &ent: ents) { NekoGui::profileManager->MoveProfile(ent, gid); } refresh_proxy_list(); } void MainWindow::on_menu_delete_triggered() { auto ents = get_now_selected_list(); if (ents.count() == 0) return; if (QMessageBox::question(this, tr("Confirmation"), QString(tr("Remove %1 item(s) ?")).arg(ents.count())) == QMessageBox::StandardButton::Yes) { for (const auto &ent: ents) { NekoGui::profileManager->DeleteProfile(ent->id); } refresh_proxy_list(); } } void MainWindow::on_menu_reset_traffic_triggered() { auto ents = get_now_selected_list(); if (ents.count() == 0) return; for (const auto &ent: ents) { ent->traffic_data->Reset(); ent->Save(); refresh_proxy_list(ent->id); } } void MainWindow::on_menu_profile_debug_info_triggered() { auto ents = get_now_selected_list(); if (ents.count() != 1) return; auto btn = QMessageBox::information(this, software_name, ents.first()->ToJsonBytes(), "OK", "Edit", "Reload", 0, 0); if (btn == 1) { QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(QStringLiteral("profiles/%1.json").arg(ents.first()->id)).absoluteFilePath())); } else if (btn == 2) { NekoGui::dataStore->Load(); NekoGui::profileManager->LoadManager(); refresh_proxy_list(); } } void MainWindow::on_menu_copy_links_triggered() { if (ui->masterLogBrowser->hasFocus()) { ui->masterLogBrowser->copy(); return; } auto ents = get_now_selected_list(); QStringList links; for (const auto &ent: ents) { links += ent->bean->ToShareLink(); } if (links.length() == 0) return; QApplication::clipboard()->setText(links.join("\n")); show_log_impl(tr("Copied %1 item(s)").arg(links.length())); } void MainWindow::on_menu_copy_links_nkr_triggered() { auto ents = get_now_selected_list(); QStringList links; for (const auto &ent: ents) { links += ent->bean->ToNekorayShareLink(ent->type); } if (links.length() == 0) return; QApplication::clipboard()->setText(links.join("\n")); show_log_impl(tr("Copied %1 item(s)").arg(links.length())); } void MainWindow::on_menu_export_config_triggered() { auto ents = get_now_selected_list(); if (ents.count() != 1) return; auto ent = ents.first(); if (ent->bean->DisplayCoreType() != software_core_name) return; auto result = BuildConfig(ent, false, true); QString config_core = QJsonObject2QString(result->coreConfig, false); QApplication::clipboard()->setText(config_core); QMessageBox msg(QMessageBox::Information, tr("Config copied"), tr("Config copied")); msg.addButton("Copy core config", QMessageBox::YesRole); msg.addButton("Copy test config", QMessageBox::NoRole); msg.addButton(QMessageBox::Ok); msg.setEscapeButton(QMessageBox::Ok); msg.setDefaultButton(QMessageBox::Ok); auto ret = msg.exec(); if (ret == 2) { result = BuildConfig(ent, false, false); config_core = QJsonObject2QString(result->coreConfig, false); QApplication::clipboard()->setText(config_core); } else if (ret == 3) { result = BuildConfig(ent, true, false); config_core = QJsonObject2QString(result->coreConfig, false); QApplication::clipboard()->setText(config_core); } } void MainWindow::display_qr_link(bool nkrFormat) { auto ents = get_now_selected_list(); if (ents.count() != 1) return; class W : public QDialog { public: QLabel *l = nullptr; QCheckBox *cb = nullptr; // QPlainTextEdit *l2 = nullptr; QImage im; // QString link; QString link_nk; void show_qr(const QSize &size) const { auto side = size.height() - 20 - l2->size().height() - cb->size().height(); l->setPixmap(QPixmap::fromImage(im.scaled(side, side, Qt::KeepAspectRatio, Qt::FastTransformation), Qt::MonoOnly)); l->resize(side, side); } void refresh(bool is_nk) { auto link_display = is_nk ? link_nk : link; l2->setPlainText(link_display); constexpr qint32 qr_padding = 2; // try { qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(link_display.toUtf8().data(), qrcodegen::QrCode::Ecc::MEDIUM); qint32 sz = qr.getSize(); im = QImage(sz + qr_padding * 2, sz + qr_padding * 2, QImage::Format_RGB32); QRgb black = qRgb(0, 0, 0); QRgb white = qRgb(255, 255, 255); im.fill(white); for (int y = 0; y < sz; y++) for (int x = 0; x < sz; x++) if (qr.getModule(x, y)) im.setPixel(x + qr_padding, y + qr_padding, black); show_qr(size()); } catch (const std::exception &ex) { QMessageBox::warning(nullptr, "error", ex.what()); } } W(const QString &link_, const QString &link_nk_) { link = link_; link_nk = link_nk_; // setLayout(new QVBoxLayout); setMinimumSize(256, 256); QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); sizePolicy.setHeightForWidth(true); setSizePolicy(sizePolicy); // l = new QLabel(); l->setMinimumSize(256, 256); l->setMargin(6); l->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); l->setScaledContents(true); layout()->addWidget(l); cb = new QCheckBox; cb->setText("Neko Links"); layout()->addWidget(cb); l2 = new QPlainTextEdit(); l2->setReadOnly(true); layout()->addWidget(l2); // connect(cb, &QCheckBox::toggled, this, &W::refresh); refresh(false); } void resizeEvent(QResizeEvent *resizeEvent) override { show_qr(resizeEvent->size()); } }; auto link = ents.first()->bean->ToShareLink(); auto link_nk = ents.first()->bean->ToNekorayShareLink(ents.first()->type); auto w = new W(link, link_nk); w->setWindowTitle(ents.first()->bean->DisplayTypeAndName()); w->exec(); w->deleteLater(); } void MainWindow::on_menu_scan_qr_triggered() { #ifndef NKR_NO_ZXING using namespace ZXingQt; hide(); QThread::sleep(1); auto screen = QGuiApplication::primaryScreen(); auto geom = screen->geometry(); auto qpx = screen->grabWindow(0, geom.x(), geom.y(), geom.width(), geom.height()); show(); auto hints = DecodeHints() .setFormats(BarcodeFormat::QRCode) .setTryRotate(false) .setBinarizer(Binarizer::FixedThreshold); auto result = ReadBarcode(qpx.toImage(), hints); const auto &text = result.text(); if (text.isEmpty()) { MessageBoxInfo(software_name, tr("QR Code not found")); } else { show_log_impl("QR Code Result:\n" + text); NekoGui_sub::groupUpdater->AsyncUpdate(text); } #endif } void MainWindow::on_menu_clear_test_result_triggered() { for (const auto &profile: get_selected_or_group()) { profile->latency = 0; profile->full_test_report = ""; profile->Save(); } refresh_proxy_list(); } void MainWindow::on_menu_select_all_triggered() { if (ui->masterLogBrowser->hasFocus()) { ui->masterLogBrowser->selectAll(); return; } ui->proxyListTable->selectAll(); } void MainWindow::on_menu_delete_repeat_triggered() { QList> out; QList> out_del; NekoGui::ProfileFilter::Uniq(NekoGui::profileManager->CurrentGroup()->Profiles(), out, true, false); NekoGui::ProfileFilter::OnlyInSrc_ByPointer(NekoGui::profileManager->CurrentGroup()->Profiles(), out, out_del); int remove_display_count = 0; QString remove_display; for (const auto &ent: out_del) { remove_display += ent->bean->DisplayTypeAndName() + "\n"; if (++remove_display_count == 20) { remove_display += "..."; break; } } if (out_del.length() > 0 && QMessageBox::question(this, tr("Confirmation"), tr("Remove %1 item(s) ?").arg(out_del.length()) + "\n" + remove_display) == QMessageBox::StandardButton::Yes) { for (const auto &ent: out_del) { NekoGui::profileManager->DeleteProfile(ent->id); } refresh_proxy_list(); } } bool mw_sub_updating = false; void MainWindow::on_menu_update_subscription_triggered() { auto group = NekoGui::profileManager->CurrentGroup(); if (group->url.isEmpty()) return; if (mw_sub_updating) return; mw_sub_updating = true; NekoGui_sub::groupUpdater->AsyncUpdate(group->url, group->id, [&] { mw_sub_updating = false; }); } void MainWindow::on_menu_remove_unavailable_triggered() { QList> out_del; for (const auto &[_, profile]: NekoGui::profileManager->profiles) { if (NekoGui::dataStore->current_group != profile->gid) continue; if (profile->latency < 0) out_del += profile; } int remove_display_count = 0; QString remove_display; for (const auto &ent: out_del) { remove_display += ent->bean->DisplayTypeAndName() + "\n"; if (++remove_display_count == 20) { remove_display += "..."; break; } } if (out_del.length() > 0 && QMessageBox::question(this, tr("Confirmation"), tr("Remove %1 item(s) ?").arg(out_del.length()) + "\n" + remove_display) == QMessageBox::StandardButton::Yes) { for (const auto &ent: out_del) { NekoGui::profileManager->DeleteProfile(ent->id); } refresh_proxy_list(); } } void MainWindow::on_menu_resolve_domain_triggered() { auto profiles = get_selected_or_group(); if (profiles.isEmpty()) return; if (QMessageBox::question(this, tr("Confirmation"), tr("Resolving domain to IP, if support.")) != QMessageBox::StandardButton::Yes) { return; } if (mw_sub_updating) return; mw_sub_updating = true; NekoGui::dataStore->resolve_count = profiles.count(); for (const auto &profile: profiles) { profile->bean->ResolveDomainToIP([=] { profile->Save(); if (--NekoGui::dataStore->resolve_count != 0) return; refresh_proxy_list(); mw_sub_updating = false; }); } } void MainWindow::on_proxyListTable_customContextMenuRequested(const QPoint &pos) { ui->menu_server->popup(ui->proxyListTable->viewport()->mapToGlobal(pos)); // 弹出菜单 } QList> MainWindow::get_now_selected_list() { auto items = ui->proxyListTable->selectedItems(); QList> list; for (auto item: items) { auto id = item->data(114514).toInt(); auto ent = NekoGui::profileManager->GetProfile(id); if (ent != nullptr && !list.contains(ent)) list += ent; } return list; } QList> MainWindow::get_selected_or_group() { auto selected_or_group = ui->menu_server->property("selected_or_group").toInt(); QList> profiles; if (selected_or_group > 0) { profiles = get_now_selected_list(); if (profiles.isEmpty() && selected_or_group == 2) profiles = NekoGui::profileManager->CurrentGroup()->ProfilesWithOrder(); } else { profiles = NekoGui::profileManager->CurrentGroup()->ProfilesWithOrder(); } return profiles; } void MainWindow::keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_Escape: // take over by shortcut_esc break; case Qt::Key_Enter: neko_start(); break; default: QMainWindow::keyPressEvent(event); } } // Log inline void FastAppendTextDocument(const QString &message, QTextDocument *doc) { QTextCursor cursor(doc); cursor.movePosition(QTextCursor::End); cursor.beginEditBlock(); cursor.insertBlock(); cursor.insertText(message); cursor.endEditBlock(); } void MainWindow::show_log_impl(const QString &log) { auto lines = SplitLines(log.trimmed()); if (lines.isEmpty()) return; QStringList newLines; auto log_ignore = NekoGui::dataStore->log_ignore; for (const auto &line: lines) { bool showThisLine = true; for (const auto &str: log_ignore) { if (line.contains(str)) { showThisLine = false; break; } } if (showThisLine) newLines << line; } if (newLines.isEmpty()) return; FastAppendTextDocument(newLines.join("\n"), qvLogDocument); // qvLogDocument->setPlainText(qvLogDocument->toPlainText() + log); // From https://gist.github.com/jemyzhang/7130092 auto block = qvLogDocument->begin(); while (block.isValid()) { if (qvLogDocument->blockCount() > NekoGui::dataStore->max_log_line) { QTextCursor cursor(block); block = block.next(); cursor.select(QTextCursor::BlockUnderCursor); cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); cursor.removeSelectedText(); continue; } break; } } #define ADD_TO_CURRENT_ROUTE(a, b) \ NekoGui::dataStore->routing->a = (SplitLines(NekoGui::dataStore->routing->a) << (b)).join("\n"); \ NekoGui::dataStore->routing->Save(); void MainWindow::on_masterLogBrowser_customContextMenuRequested(const QPoint &pos) { QMenu *menu = ui->masterLogBrowser->createStandardContextMenu(); auto sep = new QAction(this); sep->setSeparator(true); menu->addAction(sep); auto action_add_ignore = new QAction(this); action_add_ignore->setText(tr("Set ignore keyword")); connect(action_add_ignore, &QAction::triggered, this, [=] { auto list = NekoGui::dataStore->log_ignore; auto newStr = ui->masterLogBrowser->textCursor().selectedText().trimmed(); if (!newStr.isEmpty()) list << newStr; bool ok; newStr = QInputDialog::getMultiLineText(GetMessageBoxParent(), tr("Set ignore keyword"), tr("Set the following keywords to ignore?\nSplit by line."), list.join("\n"), &ok); if (ok) { NekoGui::dataStore->log_ignore = SplitLines(newStr); NekoGui::dataStore->Save(); } }); menu->addAction(action_add_ignore); auto action_add_route = new QAction(this); action_add_route->setText(tr("Save as route")); connect(action_add_route, &QAction::triggered, this, [=] { auto newStr = ui->masterLogBrowser->textCursor().selectedText().trimmed(); if (newStr.isEmpty()) return; // bool ok; newStr = QInputDialog::getText(GetMessageBoxParent(), tr("Save as route"), tr("Edit"), {}, newStr, &ok).trimmed(); if (!ok) return; if (newStr.isEmpty()) return; // auto select = IsIpAddress(newStr) ? 0 : 3; QStringList items = {"proxyIP", "bypassIP", "blockIP", "proxyDomain", "bypassDomain", "blockDomain"}; auto item = QInputDialog::getItem(GetMessageBoxParent(), tr("Save as route"), tr("Save \"%1\" as a routing rule?").arg(newStr), items, select, false, &ok); if (ok) { auto index = items.indexOf(item); switch (index) { case 0: ADD_TO_CURRENT_ROUTE(proxy_ip, newStr); break; case 1: ADD_TO_CURRENT_ROUTE(direct_ip, newStr); break; case 2: ADD_TO_CURRENT_ROUTE(block_ip, newStr); break; case 3: ADD_TO_CURRENT_ROUTE(proxy_domain, newStr); break; case 4: ADD_TO_CURRENT_ROUTE(direct_domain, newStr); break; case 5: ADD_TO_CURRENT_ROUTE(block_domain, newStr); break; default: break; } MW_dialog_message("", "UpdateDataStore,RouteChanged"); } }); menu->addAction(action_add_route); auto action_clear = new QAction(this); action_clear->setText(tr("Clear")); connect(action_clear, &QAction::triggered, this, [=] { qvLogDocument->clear(); ui->masterLogBrowser->clear(); }); menu->addAction(action_clear); menu->exec(ui->masterLogBrowser->viewport()->mapToGlobal(pos)); // 弹出菜单 } // eventFilter bool MainWindow::eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::MouseButtonPress) { auto mouseEvent = dynamic_cast(event); if (obj == ui->label_running && mouseEvent->button() == Qt::LeftButton && running != nullptr) { speedtest_current(); return true; } else if (obj == ui->label_inbound && mouseEvent->button() == Qt::LeftButton) { on_menu_basic_settings_triggered(); return true; } } else if (event->type() == QEvent::MouseButtonDblClick) { if (obj == ui->splitter) { auto size = ui->splitter->size(); ui->splitter->setSizes({size.height() / 2, size.height() / 2}); } } return QMainWindow::eventFilter(obj, event); } // profile selector void MainWindow::start_select_mode(QObject *context, const std::function &callback) { select_mode = true; connectOnce(this, &MainWindow::profile_selected, context, callback); refresh_status(); } // 连接列表 inline QJsonArray last_arr; // format is nekoray_connections_json void MainWindow::refresh_connection_list(const QJsonArray &arr) { if (last_arr == arr) { return; } last_arr = arr; if (NekoGui::dataStore->flag_debug) qDebug() << arr; ui->tableWidget_conn->setRowCount(0); int row = -1; for (const auto &_item: arr) { auto item = _item.toObject(); if (NekoGui::dataStore->ignoreConnTag.contains(item["Tag"].toString())) continue; row++; ui->tableWidget_conn->insertRow(row); auto f0 = std::make_unique(); f0->setData(114514, item["ID"].toInt()); // C0: Status auto c0 = new QLabel; auto start_t = item["Start"].toInt(); auto end_t = item["End"].toInt(); // icon auto outboundTag = item["Tag"].toString(); if (outboundTag == "block") { c0->setPixmap(Icon::GetMaterialIcon("cancel")); } else { if (end_t > 0) { c0->setPixmap(Icon::GetMaterialIcon("history")); } else { c0->setPixmap(Icon::GetMaterialIcon("swap-vertical")); } } c0->setAlignment(Qt::AlignCenter); c0->setToolTip(tr("Start: %1\nEnd: %2").arg(DisplayTime(start_t), end_t > 0 ? DisplayTime(end_t) : "")); ui->tableWidget_conn->setCellWidget(row, 0, c0); // C1: Outbound auto f = f0->clone(); f->setToolTip(""); f->setText(outboundTag); ui->tableWidget_conn->setItem(row, 1, f); // C2: Destination f = f0->clone(); QString target1 = item["Dest"].toString(); QString target2 = item["RDest"].toString(); if (target2.isEmpty() || target1 == target2) { target2 = ""; } f->setText("[" + target1 + "] " + target2); ui->tableWidget_conn->setItem(row, 2, f); } } // Hotkey #ifndef NKR_NO_QHOTKEY #include inline QList> RegisteredHotkey; void MainWindow::RegisterHotkey(bool unregister) { while (!RegisteredHotkey.isEmpty()) { auto hk = RegisteredHotkey.takeFirst(); hk->deleteLater(); } if (unregister) return; QStringList regstr{ NekoGui::dataStore->hotkey_mainwindow, NekoGui::dataStore->hotkey_group, NekoGui::dataStore->hotkey_route, NekoGui::dataStore->hotkey_system_proxy_menu, }; for (const auto &key: regstr) { if (key.isEmpty()) continue; if (regstr.count(key) > 1) return; // Conflict hotkey } for (const auto &key: regstr) { QKeySequence k(key); if (k.isEmpty()) continue; auto hk = std::make_shared(k, true); if (hk->isRegistered()) { RegisteredHotkey += hk; connect(hk.get(), &QHotkey::activated, this, [=] { HotkeyEvent(key); }); } else { hk->deleteLater(); } } } void MainWindow::HotkeyEvent(const QString &key) { if (key.isEmpty()) return; runOnUiThread([=] { if (key == NekoGui::dataStore->hotkey_mainwindow) { tray->activated(QSystemTrayIcon::ActivationReason::Trigger); } else if (key == NekoGui::dataStore->hotkey_group) { on_menu_manage_groups_triggered(); } else if (key == NekoGui::dataStore->hotkey_route) { on_menu_routing_settings_triggered(); } else if (key == NekoGui::dataStore->hotkey_system_proxy_menu) { ui->menu_spmode->popup(QCursor::pos()); } }); } #else void MainWindow::RegisterHotkey(bool unregister) {} void MainWindow::HotkeyEvent(const QString &key) {} #endif // VPN Launcher bool MainWindow::StartVPNProcess() { // if (vpn_pid != 0) { return true; } // auto configPath = NekoGui::WriteVPNSingBoxConfig(); auto scriptPath = NekoGui::WriteVPNLinuxScript(configPath); // #ifdef Q_OS_WIN runOnNewThread([=] { vpn_pid = 1; // TODO get pid? WinCommander::runProcessElevated(QApplication::applicationDirPath() + "/nekobox_core.exe", {"--disable-color", "run", "-c", configPath}, "", NekoGui::dataStore->vpn_hide_console ? WinCommander::SW_HIDE : WinCommander::SW_SHOWMINIMIZED); // blocking vpn_pid = 0; runOnUiThread([=] { neko_set_spmode_vpn(false); }); }); #else // auto vpn_process = new QProcess; QProcess::connect(vpn_process, &QProcess::stateChanged, this, [=](QProcess::ProcessState state) { if (state == QProcess::NotRunning) { vpn_pid = 0; vpn_process->deleteLater(); GetMainWindow()->neko_set_spmode_vpn(false); } }); // vpn_process->setProcessChannelMode(QProcess::ForwardedChannels); #ifdef Q_OS_MACOS vpn_process->start("osascript", {"-e", QStringLiteral("do shell script \"%1\" with administrator privileges") .arg("bash " + scriptPath)}); #else vpn_process->start("pkexec", {"bash", scriptPath}); #endif vpn_process->waitForStarted(); vpn_pid = vpn_process->processId(); // actually it's pkexec or bash PID #endif return true; } bool MainWindow::StopVPNProcess(bool unconditional) { if (unconditional || vpn_pid != 0) { bool ok; core_process->processId(); #ifdef Q_OS_WIN auto ret = WinCommander::runProcessElevated("taskkill", {"/IM", "nekobox_core.exe", "/FI", "PID ne " + Int2String(core_process->processId())}); ok = ret == 0; #else QProcess p; #ifdef Q_OS_MACOS p.start("osascript", {"-e", QStringLiteral("do shell script \"%1\" with administrator privileges") .arg("pkill -2 -U 0 nekobox_core")}); #else if (unconditional) { p.start("pkexec", {"killall", "-2", "nekobox_core"}); } else { p.start("pkexec", {"pkill", "-2", "-P", Int2String(vpn_pid)}); } #endif p.waitForFinished(); ok = p.exitCode() == 0; #endif if (!unconditional) { ok ? vpn_pid = 0 : MessageBoxWarning(tr("Error"), tr("Failed to stop Tun process")); } return ok; } return true; } ================================================ FILE: ui/mainwindow.h ================================================ #pragma once #include #include "main/NekoGui.hpp" #ifndef MW_INTERFACE #include #include #include #include #include #include #include #include #include #include "GroupSort.hpp" #include "db/ProxyEntity.hpp" #include "main/GuiUtils.hpp" #endif namespace NekoGui_sys { class CoreProcess; } QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow() override; void refresh_proxy_list(const int &id = -1); void show_group(int gid); void refresh_groups(); void refresh_status(const QString &traffic_update = ""); void neko_start(int _id = -1); void neko_stop(bool crash = false, bool sem = false); void neko_set_spmode_system_proxy(bool enable, bool save = true); void neko_set_spmode_vpn(bool enable, bool save = true); void show_log_impl(const QString &log); void start_select_mode(QObject *context, const std::function &callback); void refresh_connection_list(const QJsonArray &arr); void RegisterHotkey(bool unregister); bool StopVPNProcess(bool unconditional = false); signals: void profile_selected(int id); public slots: void on_commitDataRequest(); void on_menu_exit_triggered(); #ifndef MW_INTERFACE private slots: void on_masterLogBrowser_customContextMenuRequested(const QPoint &pos); void on_menu_basic_settings_triggered(); void on_menu_routing_settings_triggered(); void on_menu_vpn_settings_triggered(); void on_menu_hotkey_settings_triggered(); void on_menu_add_from_input_triggered(); void on_menu_add_from_clipboard_triggered(); void on_menu_clone_triggered(); void on_menu_move_triggered(); void on_menu_delete_triggered(); void on_menu_reset_traffic_triggered(); void on_menu_profile_debug_info_triggered(); void on_menu_copy_links_triggered(); void on_menu_copy_links_nkr_triggered(); void on_menu_export_config_triggered(); void display_qr_link(bool nkrFormat = false); void on_menu_scan_qr_triggered(); void on_menu_clear_test_result_triggered(); void on_menu_manage_groups_triggered(); void on_menu_select_all_triggered(); void on_menu_delete_repeat_triggered(); void on_menu_remove_unavailable_triggered(); void on_menu_update_subscription_triggered(); void on_menu_resolve_domain_triggered(); void on_proxyListTable_itemDoubleClicked(QTableWidgetItem *item); void on_proxyListTable_customContextMenuRequested(const QPoint &pos); void on_tabWidget_currentChanged(int index); private: Ui::MainWindow *ui; QSystemTrayIcon *tray; QShortcut *shortcut_ctrl_f = new QShortcut(QKeySequence("Ctrl+F"), this); QShortcut *shortcut_esc = new QShortcut(QKeySequence("Esc"), this); // NekoGui_sys::CoreProcess *core_process; qint64 vpn_pid = 0; // bool qvLogAutoScoll = true; QTextDocument *qvLogDocument = new QTextDocument(this); // QString title_error; int icon_status = -1; std::shared_ptr running; QString traffic_update_cache; QTime last_test_time; // int proxy_last_order = -1; bool select_mode = false; QMutex mu_starting; QMutex mu_stopping; QMutex mu_exit; QSemaphore sem_stopped; int exit_reason = 0; QList> get_now_selected_list(); QList> get_selected_or_group(); void dialog_message_impl(const QString &sender, const QString &info); void refresh_proxy_list_impl(const int &id = -1, GroupSortAction groupSortAction = {}); void refresh_proxy_list_impl_refresh_data(const int &id = -1); void keyPressEvent(QKeyEvent *event) override; void closeEvent(QCloseEvent *event) override; // void HotkeyEvent(const QString &key); bool StartVPNProcess(); // grpc and ... static void setup_grpc(); void speedtest_current_group(int mode, bool test_group); void speedtest_current(); static void stop_core_daemon(); void CheckUpdate(); protected: bool eventFilter(QObject *obj, QEvent *event) override; #endif // MW_INTERFACE }; inline MainWindow *GetMainWindow() { return (MainWindow *) mainwindow; } void UI_InitMainWindow(); ================================================ FILE: ui/mainwindow.ui ================================================ MainWindow 0 0 800 600 800 600 nya Program .. 24 24 QToolButton::InstantPopup Qt::ToolButtonTextUnderIcon Preferences .. 24 24 QToolButton::InstantPopup Qt::ToolButtonTextUnderIcon Server .. 24 24 QToolButton::InstantPopup Qt::ToolButtonTextUnderIcon Ads .. 24 24 QToolButton::InstantPopup Qt::ToolButtonTextUnderIcon Document .. 24 24 QToolButton::InstantPopup Qt::ToolButtonTextUnderIcon Update .. 24 24 QToolButton::InstantPopup Qt::ToolButtonTextUnderIcon 0 0 Tun Mode System Proxy Qt::Horizontal 40 20 true URL Test QToolButton::InstantPopup Qt::ToolButtonTextUnderIcon Qt::Vertical 0 true 0 0 0 0 0 Qt::CustomContextMenu QAbstractItemView::NoEditTriggers true QAbstractItemView::SelectRows QAbstractItemView::ScrollPerPixel false 16 false false 30 Type Address Name Test Result Traffic 0 Log 0 0 0 0 0 Qt::CustomContextMenu false Connection 0 0 0 0 0 Qt::CustomContextMenu QAbstractItemView::NoEditTriggers QAbstractItemView::SelectRows QAbstractItemView::ScrollPerPixel QAbstractItemView::ScrollPerPixel false Status Outbound Destination Qt::Horizontal QSizePolicy::Maximum 0 20 0 0 800 32 Program System Proxy Preferences Active Server Active Routing Preferences Server Share Current Group Current Select Exit Show Window Basic Settings New profile Groups Start Return Stop Ctrl+S Routing Settings Add profile from clipboard Ctrl+V Delete Del Add profile from clipboard Debug Info QR Code and link Ctrl+Q Copy Link Tcp Ping Ctrl+Alt+T Url Test Ctrl+Alt+U Clear Test Result Ctrl+Alt+C Export %1 config Ctrl+E Reset Traffic Ctrl+R Scan QR Code true Enable System Proxy true Disable Remove Duplicates Ctrl+Alt+D fake false Move Ctrl+M true Start with system true Remember last profile true Allow other devices to connect Remove Unavailable Ctrl+Alt+R Full Test Ctrl+Alt+F Hotkey Settings Select All Ctrl+A Copy links of selected (Neko Links) Ctrl+N fake false fake false Copy links of selected Ctrl+C true Enable Tun Clone Ctrl+D Update subscription Ctrl+U Resolve domain Ctrl+Alt+I Tun Settings Restart Program Open Config Folder fake false fake false Restart Proxy Stop Testing Ctrl+Alt+S MyTableWidget QTableWidget
ui/widget/MyTableWidget.h
================================================ FILE: ui/mainwindow_grpc.cpp ================================================ #include "./ui_mainwindow.h" #include "mainwindow.h" #include "db/Database.hpp" #include "db/ConfigBuilder.hpp" #include "db/traffic/TrafficLooper.hpp" #include "rpc/gRPC.h" #include "ui/widget/MessageBoxTimer.h" #include #include #include #include #include #include #include // ext core std::list> CreateExtCFromExtR(const std::list> &extRs, bool start) { // plz run and start in same thread std::list> l; for (const auto &extR: extRs) { std::shared_ptr extC(new NekoGui_sys::ExternalProcess()); extC->tag = extR->tag; extC->program = extR->program; extC->arguments = extR->arguments; extC->env = extR->env; l.emplace_back(extC); // if (start) extC->Start(); } return l; } // grpc #ifndef NKR_NO_GRPC using namespace NekoGui_rpc; #endif void MainWindow::setup_grpc() { #ifndef NKR_NO_GRPC // Setup Connection defaultClient = new Client( [=](const QString &errStr) { MW_show_log("[Error] gRPC: " + errStr); }, "127.0.0.1:" + Int2String(NekoGui::dataStore->core_port), NekoGui::dataStore->core_token); // Looper runOnNewThread([=] { NekoGui_traffic::trafficLooper->Loop(); }); #endif } // 测速 inline bool speedtesting = false; inline QList speedtesting_threads = {}; void MainWindow::speedtest_current_group(int mode, bool test_group) { if (speedtesting) { MessageBoxWarning(software_name, QObject::tr("The last speed test did not exit completely, please wait. If it persists, please restart the program.")); return; } auto profiles = get_selected_or_group(); if (test_group) profiles = NekoGui::profileManager->CurrentGroup()->ProfilesWithOrder(); if (profiles.isEmpty()) return; auto group = NekoGui::profileManager->CurrentGroup(); if (group->archive) return; // menu_stop_testing if (mode == 114514) { while (!speedtesting_threads.isEmpty()) { auto t = speedtesting_threads.takeFirst(); if (t != nullptr) t->exit(); } speedtesting = false; return; } #ifndef NKR_NO_GRPC QStringList full_test_flags; if (mode == libcore::FullTest) { auto w = new QDialog(this); auto layout = new QVBoxLayout(w); w->setWindowTitle(tr("Test Options")); // auto l1 = new QCheckBox(tr("Latency")); auto l2 = new QCheckBox(tr("UDP latency")); auto l3 = new QCheckBox(tr("Download speed")); auto l4 = new QCheckBox(tr("In and Out IP")); // auto box = new QDialogButtonBox; box->setOrientation(Qt::Horizontal); box->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); connect(box, &QDialogButtonBox::accepted, w, &QDialog::accept); connect(box, &QDialogButtonBox::rejected, w, &QDialog::reject); // layout->addWidget(l1); layout->addWidget(l2); layout->addWidget(l3); layout->addWidget(l4); layout->addWidget(box); if (w->exec() != QDialog::Accepted) { w->deleteLater(); return; } // if (l1->isChecked()) full_test_flags << "1"; if (l2->isChecked()) full_test_flags << "2"; if (l3->isChecked()) full_test_flags << "3"; if (l4->isChecked()) full_test_flags << "4"; // w->deleteLater(); if (full_test_flags.isEmpty()) return; } speedtesting = true; runOnNewThread([this, profiles, mode, full_test_flags]() { QMutex lock_write; QMutex lock_return; int threadN = NekoGui::dataStore->test_concurrent; int threadN_finished = 0; auto profiles_test = profiles; // copy // Threads lock_return.lock(); for (int i = 0; i < threadN; i++) { runOnNewThread([&] { speedtesting_threads << QObject::thread(); forever { // lock_write.lock(); if (profiles_test.isEmpty()) { threadN_finished++; if (threadN == threadN_finished) { // quit control thread lock_return.unlock(); } lock_write.unlock(); // quit of this thread speedtesting_threads.removeAll(QObject::thread()); return; } auto profile = profiles_test.takeFirst(); lock_write.unlock(); // libcore::TestReq req; req.set_mode((libcore::TestMode) mode); req.set_timeout(10 * 1000); req.set_url(NekoGui::dataStore->test_latency_url.toStdString()); // std::list> extCs; QSemaphore extSem; if (mode == libcore::TestMode::UrlTest || mode == libcore::FullTest) { auto c = BuildConfig(profile, true, false); if (!c->error.isEmpty()) { profile->full_test_report = c->error; profile->Save(); auto profileId = profile->id; runOnUiThread([this, profileId] { refresh_proxy_list(profileId); }); continue; } // if (!c->extRs.empty()) { runOnUiThread( [&] { extCs = CreateExtCFromExtR(c->extRs, true); QThread::msleep(500); extSem.release(); }, DS_cores); extSem.acquire(); } // auto config = new libcore::LoadConfigReq; config->set_core_config(QJsonObject2QString(c->coreConfig, false).toStdString()); req.set_allocated_config(config); req.set_in_address(profile->bean->serverAddress.toStdString()); req.set_full_latency(full_test_flags.contains("1")); req.set_full_udp_latency(full_test_flags.contains("2")); req.set_full_speed(full_test_flags.contains("3")); req.set_full_in_out(full_test_flags.contains("4")); req.set_full_speed_url(NekoGui::dataStore->test_download_url.toStdString()); req.set_full_speed_timeout(NekoGui::dataStore->test_download_timeout); } else if (mode == libcore::TcpPing) { req.set_address(profile->bean->DisplayAddress().toStdString()); } bool rpcOK; auto result = defaultClient->Test(&rpcOK, req); // if (!extCs.empty()) { runOnUiThread( [&] { for (const auto &extC: extCs) { extC->Kill(); } extSem.release(); }, DS_cores); extSem.acquire(); } // if (!rpcOK) return; if (result.error().empty()) { profile->latency = result.ms(); if (profile->latency == 0) profile->latency = 1; // nekoray use 0 to represents not tested } else { profile->latency = -1; } profile->full_test_report = result.full_report().c_str(); // higher priority profile->Save(); if (!result.error().empty()) { MW_show_log(tr("[%1] test error: %2").arg(profile->bean->DisplayTypeAndName(), result.error().c_str())); } auto profileId = profile->id; runOnUiThread([this, profileId] { refresh_proxy_list(profileId); }); } }); } // Control lock_return.lock(); lock_return.unlock(); speedtesting = false; MW_show_log(QObject::tr("Speedtest finished.")); }); #endif } void MainWindow::speedtest_current() { #ifndef NKR_NO_GRPC last_test_time = QTime::currentTime(); ui->label_running->setText(tr("Testing")); runOnNewThread([=] { libcore::TestReq req; req.set_mode(libcore::UrlTest); req.set_timeout(10 * 1000); req.set_url(NekoGui::dataStore->test_latency_url.toStdString()); bool rpcOK; auto result = defaultClient->Test(&rpcOK, req); if (!rpcOK) return; auto latency = result.ms(); last_test_time = QTime::currentTime(); runOnUiThread([=] { if (!result.error().empty()) { MW_show_log(QStringLiteral("UrlTest error: %1").arg(result.error().c_str())); } if (latency <= 0) { ui->label_running->setText(tr("Test Result") + ": " + tr("Unavailable")); } else if (latency > 0) { ui->label_running->setText(tr("Test Result") + ": " + QStringLiteral("%1 ms").arg(latency)); } }); }); #endif } void MainWindow::stop_core_daemon() { #ifndef NKR_NO_GRPC NekoGui_rpc::defaultClient->Exit(); #endif } void MainWindow::neko_start(int _id) { if (NekoGui::dataStore->prepare_exit) return; auto ents = get_now_selected_list(); auto ent = (_id < 0 && !ents.isEmpty()) ? ents.first() : NekoGui::profileManager->GetProfile(_id); if (ent == nullptr) return; if (select_mode) { emit profile_selected(ent->id); select_mode = false; refresh_status(); return; } auto group = NekoGui::profileManager->GetGroup(ent->gid); if (group == nullptr || group->archive) return; auto result = BuildConfig(ent, false, false); if (!result->error.isEmpty()) { MessageBoxWarning("BuildConfig return error", result->error); return; } auto neko_start_stage2 = [=] { #ifndef NKR_NO_GRPC libcore::LoadConfigReq req; req.set_core_config(QJsonObject2QString(result->coreConfig, false).toStdString()); req.set_enable_nekoray_connections(NekoGui::dataStore->connection_statistics); if (NekoGui::dataStore->traffic_loop_interval > 0) { req.add_stats_outbounds("proxy"); req.add_stats_outbounds("bypass"); } // bool rpcOK; QString error = defaultClient->Start(&rpcOK, req); if (rpcOK && !error.isEmpty()) { runOnUiThread([=] { MessageBoxWarning("LoadConfig return error", error); }); return false; } else if (!rpcOK) { return false; } // NekoGui_traffic::trafficLooper->proxy = result->outboundStat.get(); NekoGui_traffic::trafficLooper->items = result->outboundStats; NekoGui::dataStore->ignoreConnTag = result->ignoreConnTag; NekoGui_traffic::trafficLooper->loop_enabled = true; #endif runOnUiThread( [=] { auto extCs = CreateExtCFromExtR(result->extRs, true); NekoGui_sys::running_ext.splice(NekoGui_sys::running_ext.end(), extCs); }, DS_cores); NekoGui::dataStore->UpdateStartedId(ent->id); running = ent; runOnUiThread([=] { refresh_status(); refresh_proxy_list(ent->id); }); return true; }; if (!mu_starting.tryLock()) { MessageBoxWarning(software_name, "Another profile is starting..."); return; } if (!mu_stopping.tryLock()) { MessageBoxWarning(software_name, "Another profile is stopping..."); mu_starting.unlock(); return; } mu_stopping.unlock(); // check core state if (!NekoGui::dataStore->core_running) { runOnUiThread( [=] { MW_show_log("Try to start the config, but the core has not listened to the grpc port, so restart it..."); core_process->start_profile_when_core_is_up = ent->id; core_process->Restart(); }, DS_cores); mu_starting.unlock(); return; // let CoreProcess call neko_start when core is up } // timeout message auto restartMsgbox = new QMessageBox(QMessageBox::Question, software_name, tr("If there is no response for a long time, it is recommended to restart the software."), QMessageBox::Yes | QMessageBox::No, this); connect(restartMsgbox, &QMessageBox::accepted, this, [=] { MW_dialog_message("", "RestartProgram"); }); auto restartMsgboxTimer = new MessageBoxTimer(this, restartMsgbox, 5000); runOnNewThread([=] { // stop current running if (NekoGui::dataStore->started_id >= 0) { runOnUiThread([=] { neko_stop(false, true); }); sem_stopped.acquire(); } // do start MW_show_log(">>>>>>>> " + tr("Starting profile %1").arg(ent->bean->DisplayTypeAndName())); if (!neko_start_stage2()) { MW_show_log("<<<<<<<< " + tr("Failed to start profile %1").arg(ent->bean->DisplayTypeAndName())); } mu_starting.unlock(); // cancel timeout runOnUiThread([=] { restartMsgboxTimer->cancel(); restartMsgboxTimer->deleteLater(); restartMsgbox->deleteLater(); #ifdef Q_OS_LINUX // Check systemd-resolved if (NekoGui::dataStore->spmode_vpn && NekoGui::dataStore->routing->direct_dns.startsWith("local") && ReadFileText("/etc/resolv.conf").contains("systemd-resolved")) { MW_show_log("[Warning] The default Direct DNS may not works with systemd-resolved, you may consider change your DNS settings."); } #endif }); }); } void MainWindow::neko_stop(bool crash, bool sem) { auto id = NekoGui::dataStore->started_id; if (id < 0) { if (sem) sem_stopped.release(); return; } auto neko_stop_stage2 = [=] { runOnUiThread( [=] { while (!NekoGui_sys::running_ext.empty()) { auto extC = NekoGui_sys::running_ext.front(); extC->Kill(); NekoGui_sys::running_ext.pop_front(); } }, DS_cores); #ifndef NKR_NO_GRPC NekoGui_traffic::trafficLooper->loop_enabled = false; NekoGui_traffic::trafficLooper->loop_mutex.lock(); if (NekoGui::dataStore->traffic_loop_interval != 0) { NekoGui_traffic::trafficLooper->UpdateAll(); for (const auto &item: NekoGui_traffic::trafficLooper->items) { NekoGui::profileManager->GetProfile(item->id)->Save(); runOnUiThread([=] { refresh_proxy_list(item->id); }); } } NekoGui_traffic::trafficLooper->loop_mutex.unlock(); if (!crash) { bool rpcOK; QString error = defaultClient->Stop(&rpcOK); if (rpcOK && !error.isEmpty()) { runOnUiThread([=] { MessageBoxWarning("Stop return error", error); }); return false; } else if (!rpcOK) { return false; } } #endif NekoGui::dataStore->UpdateStartedId(-1919); NekoGui::dataStore->need_keep_vpn_off = false; running = nullptr; runOnUiThread([=] { refresh_status(); refresh_proxy_list(id); }); return true; }; if (!mu_stopping.tryLock()) { if (sem) sem_stopped.release(); return; } // timeout message auto restartMsgbox = new QMessageBox(QMessageBox::Question, software_name, tr("If there is no response for a long time, it is recommended to restart the software."), QMessageBox::Yes | QMessageBox::No, this); connect(restartMsgbox, &QMessageBox::accepted, this, [=] { MW_dialog_message("", "RestartProgram"); }); auto restartMsgboxTimer = new MessageBoxTimer(this, restartMsgbox, 5000); runOnNewThread([=] { // do stop MW_show_log(">>>>>>>> " + tr("Stopping profile %1").arg(running->bean->DisplayTypeAndName())); if (!neko_stop_stage2()) { MW_show_log("<<<<<<<< " + tr("Failed to stop, please restart the program.")); } mu_stopping.unlock(); if (sem) sem_stopped.release(); // cancel timeout runOnUiThread([=] { restartMsgboxTimer->cancel(); restartMsgboxTimer->deleteLater(); restartMsgbox->deleteLater(); }); }); } void MainWindow::CheckUpdate() { // on new thread... #ifndef NKR_NO_GRPC bool ok; libcore::UpdateReq request; request.set_action(libcore::UpdateAction::Check); request.set_check_pre_release(NekoGui::dataStore->check_include_pre); auto response = NekoGui_rpc::defaultClient->Update(&ok, request); if (!ok) return; auto err = response.error(); if (!err.empty()) { runOnUiThread([=] { MessageBoxWarning(QObject::tr("Update"), err.c_str()); }); return; } if (response.release_download_url() == nullptr) { runOnUiThread([=] { MessageBoxInfo(QObject::tr("Update"), QObject::tr("No update")); }); return; } runOnUiThread([=] { auto allow_updater = !NekoGui::dataStore->flag_use_appdata; auto note_pre_release = response.is_pre_release() ? " (Pre-release)" : ""; QMessageBox box(QMessageBox::Question, QObject::tr("Update") + note_pre_release, QObject::tr("Update found: %1\nRelease note:\n%2").arg(response.assets_name().c_str(), response.release_note().c_str())); // QAbstractButton *btn1 = nullptr; if (allow_updater) { btn1 = box.addButton(QObject::tr("Update"), QMessageBox::AcceptRole); } QAbstractButton *btn2 = box.addButton(QObject::tr("Open in browser"), QMessageBox::AcceptRole); box.addButton(QObject::tr("Close"), QMessageBox::RejectRole); box.exec(); // if (btn1 == box.clickedButton() && allow_updater) { // Download Update runOnNewThread([=] { bool ok2; libcore::UpdateReq request2; request2.set_action(libcore::UpdateAction::Download); auto response2 = NekoGui_rpc::defaultClient->Update(&ok2, request2); runOnUiThread([=] { if (response2.error().empty()) { auto q = QMessageBox::question(nullptr, QObject::tr("Update"), QObject::tr("Update is ready, restart to install?")); if (q == QMessageBox::StandardButton::Yes) { this->exit_reason = 1; on_menu_exit_triggered(); } } else { MessageBoxWarning(QObject::tr("Update"), response2.error().c_str()); } }); }); } else if (btn2 == box.clickedButton()) { QDesktopServices::openUrl(QUrl(response.release_url().c_str())); } }); #endif } ================================================ FILE: ui/mainwindow_interface.h ================================================ #pragma once #define MW_INTERFACE #include "mainwindow.h" ================================================ FILE: ui/widget/FloatCheckBox.h ================================================ #pragma once #include #include class FloatCheckBox : public QCheckBox { public: QWidget *parent; QWidget *window; void refresh() { setFixedSize(24, 24); auto pos = parent->rect().topRight(); pos = parent->mapTo(window, pos); pos.setX(pos.x() - 48); // ? move(pos); raise(); setVisible(parent->isVisible()); }; bool eventFilter(QObject *obj, QEvent *e) override { if (obj != window || e->type() != QEvent::Resize) return false; refresh(); return false; }; explicit FloatCheckBox(QWidget *parent, QWidget *window) : QCheckBox(window) { this->parent = parent; this->window = window; window->installEventFilter(this); refresh(); }; }; ================================================ FILE: ui/widget/GroupItem.cpp ================================================ #include "GroupItem.h" #include "ui_GroupItem.h" #include "ui/edit/dialog_edit_group.h" #include "main/GuiUtils.hpp" #include "sub/GroupUpdater.hpp" #include QString ParseSubInfo(const QString &info) { if (info.trimmed().isEmpty()) return ""; QString result; long long used = 0; long long total = 0; long long expire = 0; auto re0m = QRegularExpression("total=([0-9]+)").match(info); if (re0m.lastCapturedIndex() >= 1) { total = re0m.captured(1).toLongLong(); } else { return ""; } auto re1m = QRegularExpression("upload=([0-9]+)").match(info); if (re1m.lastCapturedIndex() >= 1) { used += re1m.captured(1).toLongLong(); } auto re2m = QRegularExpression("download=([0-9]+)").match(info); if (re2m.lastCapturedIndex() >= 1) { used += re2m.captured(1).toLongLong(); } auto re3m = QRegularExpression("expire=([0-9]+)").match(info); if (re3m.lastCapturedIndex() >= 1) { expire = re3m.captured(1).toLongLong(); } result = QObject::tr("Used: %1 Remain: %2 Expire: %3") .arg(ReadableSize(used), ReadableSize(total - used), DisplayTime(expire, QLocale::ShortFormat)); return result; } GroupItem::GroupItem(QWidget *parent, const std::shared_ptr &ent, QListWidgetItem *item) : QWidget(parent), ui(new Ui::GroupItem) { ui->setupUi(this); this->setLayoutDirection(Qt::LeftToRight); this->parentWindow = parent; this->ent = ent; this->item = item; if (ent == nullptr) return; connect(this, &GroupItem::edit_clicked, this, &GroupItem::on_edit_clicked); connect(NekoGui_sub::groupUpdater, &NekoGui_sub::GroupUpdater::asyncUpdateCallback, this, [=](int gid) { if (gid == this->ent->id) refresh_data(); }); refresh_data(); } GroupItem::~GroupItem() { delete ui; } void GroupItem::refresh_data() { ui->name->setText(ent->name); auto type = ent->url.isEmpty() ? tr("Basic") : tr("Subscription"); if (ent->archive) type = tr("Archive") + " " + type; type += " (" + Int2String(ent->Profiles().length()) + ")"; ui->type->setText(type); if (ent->url.isEmpty()) { ui->url->hide(); ui->subinfo->hide(); ui->update_sub->hide(); } else { ui->url->setText(ent->url); QStringList info; if (ent->sub_last_update != 0) { info << tr("Last update: %1").arg(DisplayTime(ent->sub_last_update, QLocale::ShortFormat)); } auto subinfo = ParseSubInfo(ent->info); if (!ent->info.isEmpty()) { info << subinfo; } if (info.isEmpty()) { ui->subinfo->hide(); } else { ui->subinfo->show(); ui->subinfo->setText(info.join(" | ")); } } runOnUiThread( [=] { adjustSize(); item->setSizeHint(sizeHint()); dynamic_cast(parent())->adjustSize(); }, this); } void GroupItem::on_update_sub_clicked() { NekoGui_sub::groupUpdater->AsyncUpdate(ent->url, ent->id); } void GroupItem::on_edit_clicked() { auto dialog = new DialogEditGroup(ent, parentWindow); connect(dialog, &QDialog::finished, this, [=] { if (dialog->result() == QDialog::Accepted) { ent->Save(); refresh_data(); MW_dialog_message(Dialog_DialogManageGroups, "refresh" + Int2String(ent->id)); } dialog->deleteLater(); }); dialog->show(); } void GroupItem::on_remove_clicked() { if (NekoGui::profileManager->groups.size() <= 1) return; if (QMessageBox::question(this, tr("Confirmation"), tr("Remove %1?").arg(ent->name)) == QMessageBox::StandardButton::Yes) { NekoGui::profileManager->DeleteGroup(ent->id); MW_dialog_message(Dialog_DialogManageGroups, "refresh-1"); delete item; } } ================================================ FILE: ui/widget/GroupItem.h ================================================ #pragma once #include #include #include "db/Database.hpp" QT_BEGIN_NAMESPACE namespace Ui { class GroupItem; } QT_END_NAMESPACE class GroupItem : public QWidget { Q_OBJECT public: explicit GroupItem(QWidget *parent, const std::shared_ptr &ent, QListWidgetItem *item); ~GroupItem() override; void refresh_data(); std::shared_ptr ent; QListWidgetItem *item; private: Ui::GroupItem *ui; QWidget *parentWindow; signals: void edit_clicked(); private slots: void on_update_sub_clicked(); void on_edit_clicked(); void on_remove_clicked(); }; ================================================ FILE: ui/widget/GroupItem.ui ================================================ GroupItem 0 0 403 300 0 0 0 0 color: rgb(251, 114, 153); Type 0 0 Name 0 0 Update Subscription 0 0 Edit 0 0 Remove 0 0 color: rgb(102, 102, 102); Url 0 0 订阅流量信息 ================================================ FILE: ui/widget/MessageBoxTimer.h ================================================ #pragma once #include #include class MessageBoxTimer : public QTimer { public: QMessageBox *msgbox = nullptr; bool showed = false; explicit MessageBoxTimer(QObject *parent, QMessageBox *msgbox, int delayMs) : QTimer(parent) { connect(this, &QTimer::timeout, this, &MessageBoxTimer::timeoutFunc, Qt::ConnectionType::QueuedConnection); this->msgbox = msgbox; setSingleShot(true); setInterval(delayMs); start(); }; void cancel() { QTimer::stop(); if (msgbox != nullptr && showed) { msgbox->reject(); // return the timeoutFunc } }; private: void timeoutFunc() { if (msgbox == nullptr) return; showed = true; msgbox->exec(); msgbox = nullptr; } }; ================================================ FILE: ui/widget/MyLineEdit.h ================================================ #pragma once #include class MyLineEdit : public QLineEdit { public slots: explicit MyLineEdit(QWidget *parent = nullptr) : QLineEdit(parent) { } void setText(const QString &s) { QLineEdit::setText(s); QLineEdit::home(false); } }; ================================================ FILE: ui/widget/MyTableWidget.h ================================================ #pragma once #include #include #include #include #include #include class MyTableWidget : public QTableWidget { public: explicit MyTableWidget(QWidget *parent = nullptr) : QTableWidget(parent) { // 拖拽设置 this->setDragDropMode(QAbstractItemView::InternalMove); // 内部移动 this->setDropIndicatorShown(true); // drop位置 提示 this->setSelectionBehavior(QAbstractItemView::SelectRows); }; QList order; // id sorted (save) std::map id2Row; // id2Row QList row2Id; // row2Id: use this to refresh data std::function callback_save_order; std::function refresh_data; void _save_order(bool saveToFile) { order.clear(); id2Row.clear(); for (int i = 0; i < this->rowCount(); i++) { auto id = row2Id[i]; order += id; id2Row[id] = i; } if (callback_save_order != nullptr && saveToFile) callback_save_order(); } void update_order(bool saveToFile) { if (order.isEmpty()) { _save_order(false); return; } // 纠错: order 里面含有不在当前表格控件的 id bool needSave = false; auto deleted_profiles = order; for (int i = 0; i < this->rowCount(); i++) { auto id = row2Id[i]; deleted_profiles.removeAll(id); } for (auto deleted_profile: deleted_profiles) { needSave = true; order.removeAll(deleted_profile); } // map(dstRow -> srcId) QMap newRows; for (int i = 0; i < this->rowCount(); i++) { auto id = row2Id[i]; auto dst = order.indexOf(id); if (dst == i) continue; if (dst == -1) { // 纠错: 新的profile不需要移动 needSave = true; continue; } newRows[dst] = id; } for (int i = 0; i < this->rowCount(); i++) { if (!newRows.contains(i)) continue; row2Id[i] = newRows[i]; } // Then save the order _save_order(needSave || saveToFile); }; protected: /* * 2021.7.6 by gy * 拖拽 继承QTableWidget overwrite dropEvent事件 * 功能:拖动一行到鼠标落下的位置 * 注意:DragDropMode相关参数的设置 */ void dropEvent(QDropEvent *event) override { if (order.isEmpty()) order = row2Id; // 原行号与目标行号的确定 int row_src, row_dst; row_src = this->currentRow(); // 原行号 可加if auto id_src = row2Id[row_src]; // id_src QTableWidgetItem *item = this->itemAt(event->pos()); // 获取落点的item if (item != nullptr) { // 判断是否为空 row_dst = item->row(); // 不为空 获取其行号 // Modify order order.removeAt(row_src); order.insert(row_dst, id_src); } else { // 落点没有item 说明拖动到了最下面 return; } // Do update order & refresh clearSelection(); update_order(true); refresh_data(-1); }; }; ================================================ FILE: ui/widget/ProxyItem.cpp ================================================ #include "ProxyItem.h" #include "ui_ProxyItem.h" #include ProxyItem::ProxyItem(QWidget *parent, const std::shared_ptr &ent, QListWidgetItem *item) : QWidget(parent), ui(new Ui::ProxyItem) { ui->setupUi(this); this->setLayoutDirection(Qt::LeftToRight); this->item = item; this->ent = ent; if (ent == nullptr) return; refresh_data(); } ProxyItem::~ProxyItem() { delete ui; } void ProxyItem::refresh_data() { ui->type->setText(ent->bean->DisplayType()); ui->name->setText(ent->bean->DisplayName()); ui->address->setText(ent->bean->DisplayAddress()); ui->traffic->setText(ent->traffic_data->DisplayTraffic()); ui->test_result->setText(ent->DisplayLatency()); runOnUiThread( [=] { adjustSize(); item->setSizeHint(sizeHint()); dynamic_cast(parent())->adjustSize(); }, this); } void ProxyItem::on_remove_clicked() { if (!this->remove_confirm || QMessageBox::question(this, tr("Confirmation"), tr("Remove %1?").arg(ent->bean->DisplayName())) == QMessageBox::StandardButton::Yes) { // TODO do remove (or not) -> callback delete item; } } QPushButton *ProxyItem::get_change_button() { return ui->change; } ================================================ FILE: ui/widget/ProxyItem.h ================================================ #pragma once #include #include #include "db/ProxyEntity.hpp" QT_BEGIN_NAMESPACE namespace Ui { class ProxyItem; } QT_END_NAMESPACE class QPushButton; class ProxyItem : public QWidget { Q_OBJECT public: explicit ProxyItem(QWidget *parent, const std::shared_ptr &ent, QListWidgetItem *item); ~ProxyItem() override; void refresh_data(); QPushButton *get_change_button(); std::shared_ptr ent; QListWidgetItem *item; bool remove_confirm = false; private: Ui::ProxyItem *ui; private slots: void on_remove_clicked(); }; ================================================ FILE: ui/widget/ProxyItem.ui ================================================ ProxyItem 0 0 400 300 0 0 0 0 名称 0 0 :/icon/material/swap-horizontal.svg:/icon/material/swap-horizontal.svg 0 0 :/icon/material/delete.svg:/icon/material/delete.svg 0 0 color: rgb(102, 102, 102); 地址 0 0 测试结果 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 color: rgb(251, 114, 153); 类型 0 0 流量 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter