├── .gitattributes ├── .github └── workflows │ ├── build.yml │ ├── maven.yml │ └── pages.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── build.gradle.kts ├── docs ├── .gitignore ├── cppreference-doxygen-web.tag.xml ├── doxygen-awesome-css │ ├── .github │ │ └── workflows │ │ │ └── publish.yaml │ ├── .gitignore │ ├── Doxyfile │ ├── LICENSE │ ├── README.md │ ├── doxygen-awesome-darkmode-toggle.js │ ├── doxygen-awesome-fragment-copy-button.js │ ├── doxygen-awesome-paragraph-link.js │ ├── doxygen-awesome-sidebar-only-darkmode-toggle.css │ ├── doxygen-awesome-sidebar-only.css │ ├── doxygen-awesome.css │ ├── doxygen-custom │ │ ├── custom-alternative.css │ │ ├── custom.css │ │ ├── header.html │ │ └── toggle-alternative-theme.js │ ├── img │ │ ├── screenshot.png │ │ └── theme-variants.drawio.svg │ ├── include │ │ └── MyLibrary │ │ │ ├── example.hpp │ │ │ └── subclass-example.hpp │ └── logo.drawio.svg └── doxygen.cfg ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lsplt ├── .gitignore ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── jni │ ├── .clang-format │ ├── .clang-tidy │ ├── CMakeLists.txt │ ├── elf_util.cc │ ├── elf_util.hpp │ ├── include │ └── lsplt.hpp │ ├── logging.hpp │ ├── lsplt.cc │ └── syscall.hpp ├── settings.gradle.kts └── test ├── .gitignore ├── build.gradle.kts └── src └── main ├── AndroidManifest.xml └── jni ├── CMakeLists.txt ├── logging.h └── test.cpp /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto eol=lf 3 | 4 | # Declare files that will always have CRLF line endings on checkout. 5 | *.cmd text eol=crlf 6 | *.bat text eol=crlf 7 | 8 | # Denote all files that are truly binary and should not be modified. 9 | *.so binary 10 | *.dex binary 11 | *.jar binary 12 | *.png binary 13 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: ["master"] 6 | paths-ignore: 7 | - 'README.md' 8 | pull_request: 9 | 10 | 11 | jobs: 12 | build: 13 | name: Build on ${{ matrix.os }} 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: [ ubuntu-latest, windows-latest, macOS-latest ] 19 | 20 | steps: 21 | - name: Check out 22 | uses: actions/checkout@v4 23 | with: 24 | submodules: 'recursive' 25 | fetch-depth: 0 26 | - name: Set up JDK 17 27 | uses: actions/setup-java@v4 28 | with: 29 | distribution: 'temurin' 30 | java-version: '17' 31 | cache: 'gradle' 32 | - name: ccache 33 | uses: hendrikmuhs/ccache-action@v1.2 34 | with: 35 | key: ${{ runner.os }}-${{ github.sha }} 36 | restore-keys: ${{ runner.os }} 37 | - name: Build with Gradle 38 | run: | 39 | ccache -o cache_dir=${{ github.workspace }}/.ccache 40 | ccache -o hash_dir=false 41 | ccache -o compiler_check='%compiler% -dumpmachine; %compiler% -dumpversion' 42 | ccache -p 43 | echo 'android.native.buildOutput=verbose' >> gradle.properties 44 | ./gradlew :lsplt:publishToMavenLocal :lsplt:prefabDebugPackage 45 | env: 46 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.maven_pgp_signingKey }} 47 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.maven_pgp_signingPassword }} 48 | - name: Upload library 49 | uses: actions/upload-artifact@v4 50 | with: 51 | name: ${{ matrix.os }}-library 52 | path: ~/.m2 53 | 54 | test: 55 | needs: build 56 | name: Test on API ${{ matrix.api-level }} ${{ matrix.arch }} 57 | runs-on: ubuntu-latest 58 | strategy: 59 | fail-fast: false 60 | matrix: 61 | include: 62 | - api-level: 21 63 | target: default 64 | arch: x86_64 65 | - api-level: 21 66 | target: default 67 | arch: x86 68 | - api-level: 22 69 | target: default 70 | arch: x86_64 71 | - api-level: 22 72 | target: default 73 | arch: x86 74 | - api-level: 23 75 | target: default 76 | arch: x86_64 77 | - api-level: 23 78 | target: default 79 | arch: x86 80 | - api-level: 24 81 | target: default 82 | arch: x86_64 83 | - api-level: 24 84 | target: default 85 | arch: x86 86 | - api-level: 25 87 | target: default 88 | arch: x86_64 89 | - api-level: 25 90 | target: default 91 | arch: x86 92 | - api-level: 26 93 | target: default 94 | arch: x86_64 95 | - api-level: 26 96 | target: default 97 | arch: x86 98 | - api-level: 27 99 | target: default 100 | arch: x86_64 101 | - api-level: 27 102 | target: default 103 | arch: x86 104 | - api-level: 28 105 | target: default 106 | arch: x86_64 107 | - api-level: 28 108 | target: default 109 | arch: x86 110 | - api-level: 29 111 | target: default 112 | arch: x86_64 113 | - api-level: 29 114 | target: default 115 | arch: x86 116 | - api-level: 30 117 | target: default 118 | arch: x86_64 119 | - api-level: 30 120 | target: google_apis 121 | arch: x86 122 | - api-level: 31 123 | target: default 124 | arch: x86_64 125 | - api-level: 31 126 | target: android-tv 127 | arch: x86 128 | - api-level: 32 129 | target: aosp_atd 130 | arch: x86_64 131 | - api-level: 33 132 | target: aosp_atd 133 | arch: x86_64 134 | - api-level: 34 135 | target: aosp_atd 136 | arch: x86_64 137 | steps: 138 | - name: checkout 139 | uses: actions/checkout@v4 140 | with: 141 | submodules: 'recursive' 142 | - name: Set up JDK 17 143 | uses: actions/setup-java@v4 144 | with: 145 | distribution: 'temurin' 146 | java-version: '17' 147 | cache: 'gradle' 148 | - name: ccache 149 | uses: hendrikmuhs/ccache-action@v1.2 150 | with: 151 | key: ${{ runner.os }}-${{ github.sha }} 152 | restore-keys: ${{ runner.os }} 153 | save: false 154 | - name: Enable KVM group perms 155 | run: | 156 | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules 157 | sudo udevadm control --reload-rules 158 | sudo udevadm trigger --name-match=kvm 159 | - name: run tests 160 | uses: reactivecircus/android-emulator-runner@v2 161 | with: 162 | api-level: ${{ matrix.api-level }} 163 | arch: ${{ matrix.arch }} 164 | target: ${{ matrix.target }} 165 | script: | 166 | ccache -o cache_dir=${{ github.workspace }}/.ccache 167 | ccache -o hash_dir=false 168 | ccache -o compiler_check='%compiler% -dumpmachine; %compiler% -dumpversion' 169 | echo 'android.native.buildOutput=verbose' >> gradle.properties 170 | ./gradlew :test:connectedCheck 171 | emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none 172 | disable-animations: true 173 | avd-name: ${{ matrix.api-level }}_${{ matrix.arch }} 174 | - name: Upload outputs 175 | if: always() 176 | uses: actions/upload-artifact@v4 177 | with: 178 | name: ${{ matrix.api-level }}-${{ matrix.arch }}-test-outputs 179 | path: test/build/outputs 180 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | name: Maven 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | name: Build 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Check out 12 | uses: actions/checkout@v4 13 | with: 14 | submodules: 'recursive' 15 | fetch-depth: 0 16 | - name: Set up JDK 17 17 | uses: actions/setup-java@v4 18 | with: 19 | distribution: 'temurin' 20 | java-version: '17' 21 | cache: 'gradle' 22 | - name: Build with Gradle 23 | run: ./gradlew :lsplt:publish 24 | env: 25 | ORG_GRADLE_PROJECT_signingKey: ${{ secrets.maven_pgp_signingKey }} 26 | ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.maven_pgp_signingPassword }} 27 | ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.maven_ossrhUsername }} 28 | ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.maven_ossrhPassword }} 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | -------------------------------------------------------------------------------- /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["master"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow one concurrent deployment 19 | concurrency: 20 | group: "pages" 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | # Single deploy job since we're just deploying 25 | deploy: 26 | environment: 27 | name: github-pages 28 | url: ${{ steps.deployment.outputs.page_url }} 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v4 33 | with: 34 | submodules: 'recursive' 35 | - name: Install doxygen 36 | run: sudo apt install -y doxygen 37 | - name: Generate doxygen 38 | run: doxygen docs/doxygen.cfg 39 | - name: Upload artifact 40 | uses: actions/upload-pages-artifact@v3 41 | with: 42 | # Upload entire repository 43 | path: 'docs/docs' 44 | - name: Deploy to GitHub Pages 45 | id: deployment 46 | uses: actions/deploy-pages@main 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs/doxygen-awesome-css"] 2 | path = docs/doxygen-awesome-css 3 | url = https://github.com/jothepro/doxygen-awesome-css.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2022 LSPosed 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LSPlt 2 | 3 | 4 | ![](https://img.shields.io/badge/license-LGPL--3.0-orange.svg) 5 | ![](https://img.shields.io/badge/Android-5.0%20--%2015-blue.svg) 6 | ![](https://img.shields.io/badge/arch-armeabi--v7a%20%7C%20arm64--v8a%20%7C%20x86%20%7C%20x86--64%20%7C%20riscv64-brightgreen.svg) 7 | ![](https://github.com/LSPosed/LSPlt/actions/workflows/build.yml/badge.svg?branch=master&event=push) 8 | ![](https://img.shields.io/maven-central/v/org.lsposed.lsplt/lsplt.svg) 9 | 10 | A simple PLT hook for Android. 11 | 12 | ### Docs 13 | 14 | Check https://lsposed.org/LSPlt/namespacelsplt.html for the documentation. 15 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | val androidTargetSdkVersion by extra(34) 2 | val androidMinSdkVersion by extra(21) 3 | val androidBuildToolsVersion by extra("34.0.0") 4 | val androidCompileSdkVersion by extra(34) 5 | val androidNdkVersion by extra("26.2.11394342") 6 | val androidCmakeVersion by extra("3.22.1+") 7 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | docs 2 | -------------------------------------------------------------------------------- /docs/doxygen-awesome-css/.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: publish 2 | on: 3 | release: 4 | types: [published] 5 | jobs: 6 | deploy: 7 | runs-on: ubuntu-20.04 8 | steps: 9 | - name: Checkout repository 10 | uses: actions/checkout@v2 11 | with: 12 | fetch-depth: 0 13 | - name: install Doxygen 1.9.2 14 | run: | 15 | sudo apt-get update 16 | sudo apt-get install -y graphviz libclang-cpp1-9 libclang1-9 17 | wget https://www.doxygen.nl/files/doxygen-1.9.2.linux.bin.tar.gz 18 | tar -xvzf doxygen-1.9.2.linux.bin.tar.gz 19 | ln -s doxygen-1.9.2/bin/doxygen doxygen 20 | - name: set version 21 | run: echo "PROJECT_NUMBER = `git describe --tags`" >> Doxyfile 22 | - name: Generate Documentation 23 | run: ./doxygen Doxyfile 24 | - name: Publish generated content to GitHub Pages 25 | uses: tsunematsu21/actions-publish-gh-pages@v1.0.1 26 | with: 27 | dir: docs/html 28 | branch: gh-pages 29 | token: ${{ secrets.ACCESS_TOKEN }} -------------------------------------------------------------------------------- /docs/doxygen-awesome-css/.gitignore: -------------------------------------------------------------------------------- 1 | docs/html 2 | .DS_Store 3 | .idea -------------------------------------------------------------------------------- /docs/doxygen-awesome-css/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 jothepro 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/doxygen-awesome-css/README.md: -------------------------------------------------------------------------------- 1 | # Doxygen Awesome 2 | 3 | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/jothepro/doxygen-awesome-css)](https://github.com/jothepro/doxygen-awesome-css/releases/latest) 4 | [![GitHub](https://img.shields.io/github/license/jothepro/doxygen-awesome-css)](https://github.com/jothepro/doxygen-awesome-css/blob/main/LICENSE) 5 | ![GitHub Repo stars](https://img.shields.io/github/stars/jothepro/doxygen-awesome-css) 6 | 7 |
8 | 9 | ![Screenshot of Doxygen Awesome CSS](img/screenshot.png) 10 | 11 |
12 | 13 | **Doxygen Awesome** is a custom **CSS theme for Doxygen HTML-documentation** with lots of customization parameters. 14 | 15 | ## Motivation 16 | 17 | I really like how the Doxygen HTML-documentation is structured! But IMHO it looks a bit outdated. 18 | 19 | This theme is an attemt to update the visuals of Doxygen without changing it's overall layout too much. 20 | 21 | ## Features 22 | 23 | - 🌈 Clean, modern design 24 | - 🚀 Heavily customizable by adjusting CSS-variables 25 | - 🧩 No changes to the HTML structure of Doxygen required 26 | - 📱 Improved mobile usability 27 | - 🌘 Dark mode support! 28 | - 🥇 Works best with **doxygen 1.9.1** - **1.9.3** 29 | 30 | ## Examples 31 | 32 | - Sidebar-Only theme: [Documentation of this repository](https://jothepro.github.io/doxygen-awesome-css/) 33 | - Base theme: [libsl3](https://a4z.github.io/libsl3/) 34 | 35 | ## Installation 36 | 37 | Copy the file `doxygen-awesome.css` from this repository into your project or add this repository as submodule and check out the latest release: 38 | 39 | ```bash 40 | git submodule add https://github.com/jothepro/doxygen-awesome-css.git 41 | cd doxygen-awesome-css 42 | git checkout v2.0.3 43 | ``` 44 | 45 | Choose one of the theme variants and configure Doxygen accordingly: 46 | 47 | 48 | 49 | ![Available theme variants](img/theme-variants.drawio.svg) 50 | 51 | 52 | 53 | 1. **Base theme**: 54 | ``` 55 | # Doxyfile 56 | GENERATE_TREEVIEW = YES # optional. Also works without treeview 57 | HTML_EXTRA_STYLESHEET = doxygen-awesome-css/doxygen-awesome.css 58 | ``` 59 | 60 | 2. **Sidebar-only theme**: 61 | ``` 62 | # Doxyfile 63 | GENERATE_TREEVIEW = YES # required! 64 | HTML_EXTRA_STYLESHEET = doxygen-awesome-css/doxygen-awesome.css \ 65 | doxygen-awesome-css/doxygen-awesome-sidebar-only.css 66 | ``` 67 | 68 | Further installation instructions: 69 | 70 | - [How to install extensions](docs/extensions.md) 71 | - [How to customize the theme (colors, spacing, border-radius, ...)](docs/customization.md) 72 | - [Tips and Tricks for further configuration](docs/tricks.md) 73 | 74 | ## Browser support 75 | 76 | Tested with 77 | 78 | - Chrome 98, Chrome 98 for Android, Chrome 87 for iOS 79 | - Safari 15, Safari for iOS 15 80 | - Firefox 97, Firefox Daylight 97 for Android, Firefox Daylight 96 for iOS 81 | 82 | ## Credits 83 | 84 | - This theme is inspired by the [vuepress](https://vuepress.vuejs.org/) static site generator default theme. 85 | - Thank you for all the feedback on github! 86 | 87 | 88 | 89 | Read Next: [Extensions](docs/extensions.md) 90 | -------------------------------------------------------------------------------- /docs/doxygen-awesome-css/doxygen-awesome-darkmode-toggle.js: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Doxygen Awesome 4 | https://github.com/jothepro/doxygen-awesome-css 5 | 6 | MIT License 7 | 8 | Copyright (c) 2021 - 2022 jothepro 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | */ 29 | 30 | class DoxygenAwesomeDarkModeToggle extends HTMLElement { 31 | // SVG icons from https://fonts.google.com/icons 32 | // Licensed under the Apache 2.0 license: 33 | // https://www.apache.org/licenses/LICENSE-2.0.html 34 | static lightModeIcon = `` 35 | static darkModeIcon = `` 36 | static title = "Toggle Light/Dark Mode" 37 | 38 | static prefersLightModeInDarkModeKey = "prefers-light-mode-in-dark-mode" 39 | static prefersDarkModeInLightModeKey = "prefers-dark-mode-in-light-mode" 40 | 41 | static _staticConstructor = function() { 42 | DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.userPreference) 43 | // Update the color scheme when the browsers preference changes 44 | // without user interaction on the website. 45 | window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { 46 | DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged() 47 | }) 48 | // Update the color scheme when the tab is made visible again. 49 | // It is possible that the appearance was changed in another tab 50 | // while this tab was in the background. 51 | document.addEventListener("visibilitychange", visibilityState => { 52 | if (document.visibilityState === 'visible') { 53 | DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged() 54 | } 55 | }); 56 | }() 57 | 58 | static init() { 59 | $(function() { 60 | $(document).ready(function() { 61 | const toggleButton = document.createElement('doxygen-awesome-dark-mode-toggle') 62 | toggleButton.title = DoxygenAwesomeDarkModeToggle.title 63 | toggleButton.updateIcon() 64 | 65 | window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { 66 | toggleButton.updateIcon() 67 | }) 68 | document.addEventListener("visibilitychange", visibilityState => { 69 | if (document.visibilityState === 'visible') { 70 | toggleButton.updateIcon() 71 | } 72 | }); 73 | 74 | $(document).ready(function(){ 75 | document.getElementById("MSearchBox").parentNode.appendChild(toggleButton) 76 | }) 77 | $(window).resize(function(){ 78 | document.getElementById("MSearchBox").parentNode.appendChild(toggleButton) 79 | }) 80 | }) 81 | }) 82 | } 83 | 84 | constructor() { 85 | super(); 86 | this.onclick=this.toggleDarkMode 87 | } 88 | 89 | /** 90 | * @returns `true` for dark-mode, `false` for light-mode system preference 91 | */ 92 | static get systemPreference() { 93 | return window.matchMedia('(prefers-color-scheme: dark)').matches 94 | } 95 | 96 | /** 97 | * @returns `true` for dark-mode, `false` for light-mode user preference 98 | */ 99 | static get userPreference() { 100 | return (!DoxygenAwesomeDarkModeToggle.systemPreference && localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey)) || 101 | (DoxygenAwesomeDarkModeToggle.systemPreference && !localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey)) 102 | } 103 | 104 | static set userPreference(userPreference) { 105 | DoxygenAwesomeDarkModeToggle.darkModeEnabled = userPreference 106 | if(!userPreference) { 107 | if(DoxygenAwesomeDarkModeToggle.systemPreference) { 108 | localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey, true) 109 | } else { 110 | localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey) 111 | } 112 | } else { 113 | if(!DoxygenAwesomeDarkModeToggle.systemPreference) { 114 | localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey, true) 115 | } else { 116 | localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey) 117 | } 118 | } 119 | DoxygenAwesomeDarkModeToggle.onUserPreferenceChanged() 120 | } 121 | 122 | static enableDarkMode(enable) { 123 | if(enable) { 124 | DoxygenAwesomeDarkModeToggle.darkModeEnabled = true 125 | document.documentElement.classList.add("dark-mode") 126 | document.documentElement.classList.remove("light-mode") 127 | } else { 128 | DoxygenAwesomeDarkModeToggle.darkModeEnabled = false 129 | document.documentElement.classList.remove("dark-mode") 130 | document.documentElement.classList.add("light-mode") 131 | } 132 | } 133 | 134 | static onSystemPreferenceChanged() { 135 | DoxygenAwesomeDarkModeToggle.darkModeEnabled = DoxygenAwesomeDarkModeToggle.userPreference 136 | DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled) 137 | } 138 | 139 | static onUserPreferenceChanged() { 140 | DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled) 141 | } 142 | 143 | toggleDarkMode() { 144 | DoxygenAwesomeDarkModeToggle.userPreference = !DoxygenAwesomeDarkModeToggle.userPreference 145 | this.updateIcon() 146 | } 147 | 148 | updateIcon() { 149 | if(DoxygenAwesomeDarkModeToggle.darkModeEnabled) { 150 | this.innerHTML = DoxygenAwesomeDarkModeToggle.darkModeIcon 151 | } else { 152 | this.innerHTML = DoxygenAwesomeDarkModeToggle.lightModeIcon 153 | } 154 | } 155 | } 156 | 157 | customElements.define("doxygen-awesome-dark-mode-toggle", DoxygenAwesomeDarkModeToggle); 158 | -------------------------------------------------------------------------------- /docs/doxygen-awesome-css/doxygen-awesome-fragment-copy-button.js: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Doxygen Awesome 4 | https://github.com/jothepro/doxygen-awesome-css 5 | 6 | MIT License 7 | 8 | Copyright (c) 2022 jothepro 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | */ 29 | 30 | class DoxygenAwesomeFragmentCopyButton extends HTMLElement { 31 | constructor() { 32 | super(); 33 | this.onclick=this.copyContent 34 | } 35 | static title = "Copy to clipboard" 36 | static copyIcon = `` 37 | static successIcon = `` 38 | static successDuration = 980 39 | static init() { 40 | $(function() { 41 | $(document).ready(function() { 42 | if(navigator.clipboard) { 43 | const fragments = document.getElementsByClassName("fragment") 44 | for(const fragment of fragments) { 45 | const fragmentWrapper = document.createElement("div") 46 | fragmentWrapper.className = "doxygen-awesome-fragment-wrapper" 47 | const fragmentCopyButton = document.createElement("doxygen-awesome-fragment-copy-button") 48 | fragmentCopyButton.innerHTML = DoxygenAwesomeFragmentCopyButton.copyIcon 49 | fragmentCopyButton.title = DoxygenAwesomeFragmentCopyButton.title 50 | 51 | fragment.parentNode.replaceChild(fragmentWrapper, fragment) 52 | fragmentWrapper.appendChild(fragment) 53 | fragmentWrapper.appendChild(fragmentCopyButton) 54 | 55 | } 56 | } 57 | }) 58 | }) 59 | } 60 | 61 | 62 | copyContent() { 63 | const content = this.previousSibling.cloneNode(true) 64 | // filter out line number from file listings 65 | content.querySelectorAll(".lineno, .ttc").forEach((node) => { 66 | node.remove() 67 | }) 68 | let textContent = content.textContent 69 | // remove trailing newlines that appear in file listings 70 | let numberOfTrailingNewlines = 0 71 | while(textContent.charAt(textContent.length - (numberOfTrailingNewlines + 1)) == '\n') { 72 | numberOfTrailingNewlines++; 73 | } 74 | textContent = textContent.substring(0, textContent.length - numberOfTrailingNewlines) 75 | navigator.clipboard.writeText(textContent); 76 | this.classList.add("success") 77 | this.innerHTML = DoxygenAwesomeFragmentCopyButton.successIcon 78 | window.setTimeout(() => { 79 | this.classList.remove("success") 80 | this.innerHTML = DoxygenAwesomeFragmentCopyButton.copyIcon 81 | }, DoxygenAwesomeFragmentCopyButton.successDuration); 82 | } 83 | } 84 | 85 | customElements.define("doxygen-awesome-fragment-copy-button", DoxygenAwesomeFragmentCopyButton) 86 | -------------------------------------------------------------------------------- /docs/doxygen-awesome-css/doxygen-awesome-paragraph-link.js: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Doxygen Awesome 4 | https://github.com/jothepro/doxygen-awesome-css 5 | 6 | MIT License 7 | 8 | Copyright (c) 2022 jothepro 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | */ 29 | 30 | class DoxygenAwesomeParagraphLink { 31 | // Icon from https://fonts.google.com/icons 32 | // Licensed under the Apache 2.0 license: 33 | // https://www.apache.org/licenses/LICENSE-2.0.html 34 | static icon = `` 35 | static title = "Permanent Link" 36 | static init() { 37 | $(function() { 38 | $(document).ready(function() { 39 | document.querySelectorAll(".contents a.anchor[id], .contents .groupheader > a[id]").forEach((node) => { 40 | let anchorlink = document.createElement("a") 41 | anchorlink.setAttribute("href", `#${node.getAttribute("id")}`) 42 | anchorlink.setAttribute("title", DoxygenAwesomeParagraphLink.title) 43 | anchorlink.classList.add("anchorlink") 44 | node.classList.add("anchor") 45 | anchorlink.innerHTML = DoxygenAwesomeParagraphLink.icon 46 | node.parentElement.appendChild(anchorlink) 47 | }) 48 | }) 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /docs/doxygen-awesome-css/doxygen-awesome-sidebar-only-darkmode-toggle.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | 4 | Doxygen Awesome 5 | https://github.com/jothepro/doxygen-awesome-css 6 | 7 | MIT License 8 | 9 | Copyright (c) 2021 jothepro 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | 29 | */ 30 | 31 | @media screen and (min-width: 768px) { 32 | 33 | #MSearchBox { 34 | width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - var(--searchbar-height) - 1px); 35 | } 36 | 37 | #MSearchField { 38 | width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 66px - var(--searchbar-height)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /docs/doxygen-awesome-css/doxygen-awesome-sidebar-only.css: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Doxygen Awesome 4 | https://github.com/jothepro/doxygen-awesome-css 5 | 6 | MIT License 7 | 8 | Copyright (c) 2021 jothepro 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | */ 29 | 30 | html { 31 | /* side nav width. MUST be = `TREEVIEW_WIDTH`. 32 | * Make sure it is wide enough to contain the page title (logo + title + version) 33 | */ 34 | --side-nav-fixed-width: 335px; 35 | --menu-display: none; 36 | 37 | --top-height: 120px; 38 | } 39 | 40 | #projectname { 41 | white-space: nowrap; 42 | } 43 | 44 | 45 | @media screen and (min-width: 768px) { 46 | html { 47 | --searchbar-background: var(--page-background-color); 48 | } 49 | 50 | #side-nav { 51 | min-width: var(--side-nav-fixed-width); 52 | max-width: var(--side-nav-fixed-width); 53 | top: var(--top-height); 54 | overflow: visible; 55 | } 56 | 57 | #nav-tree, #side-nav { 58 | height: calc(100vh - var(--top-height)) !important; 59 | } 60 | 61 | #nav-tree { 62 | padding: 0; 63 | } 64 | 65 | #top { 66 | display: block; 67 | border-bottom: none; 68 | height: var(--top-height); 69 | margin-bottom: calc(0px - var(--top-height)); 70 | max-width: var(--side-nav-fixed-width); 71 | overflow: hidden; 72 | background: var(--side-nav-background); 73 | } 74 | #main-nav { 75 | float: left; 76 | padding-right: 0; 77 | } 78 | 79 | .ui-resizable-handle { 80 | cursor: default; 81 | width: 1px !important; 82 | box-shadow: 0 calc(-2 * var(--top-height)) 0 0 var(--separator-color); 83 | } 84 | 85 | #nav-path { 86 | position: fixed; 87 | right: 0; 88 | left: var(--side-nav-fixed-width); 89 | bottom: 0; 90 | width: auto; 91 | } 92 | 93 | #doc-content { 94 | height: calc(100vh - 31px) !important; 95 | padding-bottom: calc(3 * var(--spacing-large)); 96 | padding-top: calc(var(--top-height) - 80px); 97 | box-sizing: border-box; 98 | margin-left: var(--side-nav-fixed-width) !important; 99 | } 100 | 101 | #MSearchBox { 102 | width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium))); 103 | } 104 | 105 | #MSearchField { 106 | width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 65px); 107 | } 108 | 109 | #MSearchResultsWindow { 110 | left: var(--spacing-medium) !important; 111 | right: auto; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /docs/doxygen-awesome-css/doxygen-awesome.css: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Doxygen Awesome 4 | https://github.com/jothepro/doxygen-awesome-css 5 | 6 | MIT License 7 | 8 | Copyright (c) 2021 - 2022 jothepro 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | */ 29 | 30 | html { 31 | /* primary theme color. This will affect the entire websites color scheme: links, arrows, labels, ... */ 32 | --primary-color: #1779c4; 33 | --primary-dark-color: #335c80; 34 | --primary-light-color: #70b1e9; 35 | 36 | /* page base colors */ 37 | --page-background-color: white; 38 | --page-foreground-color: #2f4153; 39 | --page-secondary-foreground-color: #637485; 40 | 41 | /* color for all separators on the website: hr, borders, ... */ 42 | --separator-color: #dedede; 43 | 44 | /* border radius for all rounded components. Will affect many components, like dropdowns, memitems, codeblocks, ... */ 45 | --border-radius-large: 8px; 46 | --border-radius-small: 4px; 47 | --border-radius-medium: 6px; 48 | 49 | /* default spacings. Most compontest reference these values for spacing, to provide uniform spacing on the page. */ 50 | --spacing-small: 5px; 51 | --spacing-medium: 10px; 52 | --spacing-large: 16px; 53 | 54 | /* default box shadow used for raising an element above the normal content. Used in dropdowns, Searchresult, ... */ 55 | --box-shadow: 0 2px 8px 0 rgba(0,0,0,.075); 56 | 57 | --odd-color: rgba(0,0,0,.028); 58 | 59 | /* font-families. will affect all text on the website 60 | * font-family: the normal font for text, headlines, menus 61 | * font-family-monospace: used for preformatted text in memtitle, code, fragments 62 | */ 63 | --font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif; 64 | --font-family-monospace: ui-monospace,SFMono-Regular,SF Mono,Menlo,Consolas,Liberation Mono,monospace; 65 | 66 | /* font sizes */ 67 | --page-font-size: 15.6px; 68 | --navigation-font-size: 14.4px; 69 | --code-font-size: 14px; /* affects code, fragment */ 70 | --title-font-size: 22px; 71 | 72 | /* content text properties. These only affect the page content, not the navigation or any other ui elements */ 73 | --content-line-height: 27px; 74 | /* The content is centered and constraint in it's width. To make the content fill the whole page, set the variable to auto.*/ 75 | --content-maxwidth: 1000px; 76 | 77 | /* colors for various content boxes: @warning, @note, @deprecated @bug */ 78 | --warning-color: #f8d1cc; 79 | --warning-color-dark: #b61825; 80 | --warning-color-darker: #75070f; 81 | --note-color: #faf3d8; 82 | --note-color-dark: #f3a600; 83 | --note-color-darker: #5f4204; 84 | --todo-color: #e4f3ff; 85 | --todo-color-dark: #1879C4; 86 | --todo-color-darker: #274a5c; 87 | --deprecated-color: #ecf0f3; 88 | --deprecated-color-dark: #5b6269; 89 | --deprecated-color-darker: #43454a; 90 | --bug-color: #e4dafd; 91 | --bug-color-dark: #5b2bdd; 92 | --bug-color-darker: #2a0d72; 93 | --invariant-color: #d8f1e3; 94 | --invariant-color-dark: #44b86f; 95 | --invariant-color-darker: #265532; 96 | 97 | /* blockquote colors */ 98 | --blockquote-background: #f8f9fa; 99 | --blockquote-foreground: #636568; 100 | 101 | /* table colors */ 102 | --tablehead-background: #f1f1f1; 103 | --tablehead-foreground: var(--page-foreground-color); 104 | 105 | /* menu-display: block | none 106 | * Visibility of the top navigation on screens >= 768px. On smaller screen the menu is always visible. 107 | * `GENERATE_TREEVIEW` MUST be enabled! 108 | */ 109 | --menu-display: block; 110 | 111 | --menu-focus-foreground: var(--page-background-color); 112 | --menu-focus-background: var(--primary-color); 113 | --menu-selected-background: rgba(0,0,0,.05); 114 | 115 | 116 | --header-background: var(--page-background-color); 117 | --header-foreground: var(--page-foreground-color); 118 | 119 | /* searchbar colors */ 120 | --searchbar-background: var(--side-nav-background); 121 | --searchbar-foreground: var(--page-foreground-color); 122 | 123 | /* searchbar size 124 | * (`searchbar-width` is only applied on screens >= 768px. 125 | * on smaller screens the searchbar will always fill the entire screen width) */ 126 | --searchbar-height: 33px; 127 | --searchbar-width: 210px; 128 | --searchbar-border-radius: var(--searchbar-height); 129 | 130 | /* code block colors */ 131 | --code-background: #f5f5f5; 132 | --code-foreground: var(--page-foreground-color); 133 | 134 | /* fragment colors */ 135 | --fragment-background: #F8F9FA; 136 | --fragment-foreground: #37474F; 137 | --fragment-keyword: #bb6bb2; 138 | --fragment-keywordtype: #8258b3; 139 | --fragment-keywordflow: #d67c3b; 140 | --fragment-token: #438a59; 141 | --fragment-comment: #969696; 142 | --fragment-link: #5383d6; 143 | --fragment-preprocessor: #46aaa5; 144 | --fragment-linenumber-color: #797979; 145 | --fragment-linenumber-background: #f4f4f5; 146 | --fragment-linenumber-border: #e3e5e7; 147 | --fragment-lineheight: 20px; 148 | 149 | /* sidebar navigation (treeview) colors */ 150 | --side-nav-background: #fbfbfb; 151 | --side-nav-foreground: var(--page-foreground-color); 152 | --side-nav-arrow-opacity: 0; 153 | --side-nav-arrow-hover-opacity: 0.9; 154 | 155 | --toc-background: var(--side-nav-background); 156 | --toc-foreground: var(--side-nav-foreground); 157 | 158 | /* height of an item in any tree / collapsable table */ 159 | --tree-item-height: 30px; 160 | 161 | --memname-font-size: var(--code-font-size); 162 | --memtitle-font-size: 18px; 163 | 164 | --webkit-scrollbar-size: 7px; 165 | --webkit-scrollbar-padding: 4px; 166 | --webkit-scrollbar-color: var(--separator-color); 167 | } 168 | 169 | @media screen and (max-width: 767px) { 170 | html { 171 | --page-font-size: 16px; 172 | --navigation-font-size: 16px; 173 | --code-font-size: 15px; /* affects code, fragment */ 174 | --title-font-size: 22px; 175 | } 176 | } 177 | 178 | @media (prefers-color-scheme: dark) { 179 | html:not(.light-mode) { 180 | color-scheme: dark; 181 | 182 | --primary-color: #1982d2; 183 | --primary-dark-color: #86a9c4; 184 | --primary-light-color: #4779ac; 185 | 186 | --box-shadow: 0 2px 8px 0 rgba(0,0,0,.35); 187 | 188 | --odd-color: rgba(100,100,100,.06); 189 | 190 | --menu-selected-background: rgba(0,0,0,.4); 191 | 192 | --page-background-color: #1C1D1F; 193 | --page-foreground-color: #d2dbde; 194 | --page-secondary-foreground-color: #859399; 195 | --separator-color: #38393b; 196 | --side-nav-background: #252628; 197 | 198 | --code-background: #2a2c2f; 199 | 200 | --tablehead-background: #2a2c2f; 201 | 202 | --blockquote-background: #222325; 203 | --blockquote-foreground: #7e8c92; 204 | 205 | --warning-color: #2e1917; 206 | --warning-color-dark: #ad2617; 207 | --warning-color-darker: #f5b1aa; 208 | --note-color: #3b2e04; 209 | --note-color-dark: #f1b602; 210 | --note-color-darker: #ceb670; 211 | --todo-color: #163750; 212 | --todo-color-dark: #1982D2; 213 | --todo-color-darker: #dcf0fa; 214 | --deprecated-color: #2e323b; 215 | --deprecated-color-dark: #738396; 216 | --deprecated-color-darker: #abb0bd; 217 | --bug-color: #2a2536; 218 | --bug-color-dark: #7661b3; 219 | --bug-color-darker: #ae9ed6; 220 | --invariant-color: #303a35; 221 | --invariant-color-dark: #76ce96; 222 | --invariant-color-darker: #cceed5; 223 | 224 | --fragment-background: #282c34; 225 | --fragment-foreground: #dbe4eb; 226 | --fragment-keyword: #cc99cd; 227 | --fragment-keywordtype: #ab99cd; 228 | --fragment-keywordflow: #e08000; 229 | --fragment-token: #7ec699; 230 | --fragment-comment: #999999; 231 | --fragment-link: #98c0e3; 232 | --fragment-preprocessor: #65cabe; 233 | --fragment-linenumber-color: #cccccc; 234 | --fragment-linenumber-background: #35393c; 235 | --fragment-linenumber-border: #1f1f1f; 236 | } 237 | } 238 | 239 | /* dark mode variables are defined twice, to support both the dark-mode without and with doxygen-awesome-darkmode-toggle.js */ 240 | html.dark-mode { 241 | color-scheme: dark; 242 | 243 | --primary-color: #1982d2; 244 | --primary-dark-color: #86a9c4; 245 | --primary-light-color: #4779ac; 246 | 247 | --box-shadow: 0 2px 8px 0 rgba(0,0,0,.30); 248 | 249 | --odd-color: rgba(100,100,100,.06); 250 | 251 | --menu-selected-background: rgba(0,0,0,.4); 252 | 253 | --page-background-color: #1C1D1F; 254 | --page-foreground-color: #d2dbde; 255 | --page-secondary-foreground-color: #859399; 256 | --separator-color: #38393b; 257 | --side-nav-background: #252628; 258 | 259 | --code-background: #2a2c2f; 260 | 261 | --tablehead-background: #2a2c2f; 262 | 263 | --blockquote-background: #222325; 264 | --blockquote-foreground: #7e8c92; 265 | 266 | --warning-color: #2e1917; 267 | --warning-color-dark: #ad2617; 268 | --warning-color-darker: #f5b1aa; 269 | --note-color: #3b2e04; 270 | --note-color-dark: #f1b602; 271 | --note-color-darker: #ceb670; 272 | --todo-color: #163750; 273 | --todo-color-dark: #1982D2; 274 | --todo-color-darker: #dcf0fa; 275 | --deprecated-color: #2e323b; 276 | --deprecated-color-dark: #738396; 277 | --deprecated-color-darker: #abb0bd; 278 | --bug-color: #2a2536; 279 | --bug-color-dark: #7661b3; 280 | --bug-color-darker: #ae9ed6; 281 | --invariant-color: #303a35; 282 | --invariant-color-dark: #76ce96; 283 | --invariant-color-darker: #cceed5; 284 | 285 | --fragment-background: #282c34; 286 | --fragment-foreground: #dbe4eb; 287 | --fragment-keyword: #cc99cd; 288 | --fragment-keywordtype: #ab99cd; 289 | --fragment-keywordflow: #e08000; 290 | --fragment-token: #7ec699; 291 | --fragment-comment: #999999; 292 | --fragment-link: #98c0e3; 293 | --fragment-preprocessor: #65cabe; 294 | --fragment-linenumber-color: #cccccc; 295 | --fragment-linenumber-background: #35393c; 296 | --fragment-linenumber-border: #1f1f1f; 297 | } 298 | 299 | body { 300 | color: var(--page-foreground-color); 301 | background-color: var(--page-background-color); 302 | font-size: var(--page-font-size); 303 | } 304 | 305 | body, table, div, p, dl, #nav-tree .label, .title, .sm-dox a, .sm-dox a:hover, .sm-dox a:focus, #projectname, .SelectItem, #MSearchField, .navpath li.navelem a, .navpath li.navelem a:hover { 306 | font-family: var(--font-family); 307 | } 308 | 309 | h1, h2, h3, h4, h5 { 310 | margin-top: .9em; 311 | font-weight: 600; 312 | line-height: initial; 313 | } 314 | 315 | p, div, table, dl { 316 | font-size: var(--page-font-size); 317 | } 318 | 319 | a:link, a:visited, a:hover, a:focus, a:active { 320 | color: var(--primary-color) !important; 321 | font-weight: 500; 322 | } 323 | 324 | a.anchor { 325 | scroll-margin-top: var(--spacing-large); 326 | } 327 | 328 | /* 329 | Title and top navigation 330 | */ 331 | 332 | #top { 333 | background: var(--header-background); 334 | border-bottom: 1px solid var(--separator-color); 335 | } 336 | 337 | @media screen and (min-width: 768px) { 338 | #top { 339 | display: flex; 340 | flex-wrap: wrap; 341 | justify-content: space-between; 342 | align-items: center; 343 | } 344 | } 345 | 346 | #main-nav { 347 | flex-grow: 5; 348 | padding: var(--spacing-small) var(--spacing-medium); 349 | } 350 | 351 | #titlearea { 352 | width: auto; 353 | padding: var(--spacing-medium) var(--spacing-large); 354 | background: none; 355 | color: var(--header-foreground); 356 | border-bottom: none; 357 | } 358 | 359 | @media screen and (max-width: 767px) { 360 | #titlearea { 361 | padding-bottom: var(--spacing-small); 362 | } 363 | } 364 | 365 | #titlearea table tbody tr { 366 | height: auto !important; 367 | } 368 | 369 | #projectname { 370 | font-size: var(--title-font-size); 371 | font-weight: 600; 372 | } 373 | 374 | #projectnumber { 375 | font-family: inherit; 376 | font-size: 60%; 377 | } 378 | 379 | #projectbrief { 380 | font-family: inherit; 381 | font-size: 80%; 382 | } 383 | 384 | #projectlogo { 385 | vertical-align: middle; 386 | } 387 | 388 | #projectlogo img { 389 | max-height: calc(var(--title-font-size) * 2); 390 | margin-right: var(--spacing-small); 391 | } 392 | 393 | .sm-dox, .tabs, .tabs2, .tabs3 { 394 | background: none; 395 | padding: 0; 396 | } 397 | 398 | .tabs, .tabs2, .tabs3 { 399 | border-bottom: 1px solid var(--separator-color); 400 | margin-bottom: -1px; 401 | } 402 | 403 | @media screen and (max-width: 767px) { 404 | .sm-dox a span.sub-arrow { 405 | background: var(--code-background); 406 | } 407 | 408 | #main-menu a.has-submenu span.sub-arrow { 409 | color: var(--page-secondary-foreground-color); 410 | border-radius: var(--border-radius-medium); 411 | } 412 | 413 | #main-menu a.has-submenu:hover span.sub-arrow { 414 | color: var(--page-foreground-color); 415 | } 416 | } 417 | 418 | @media screen and (min-width: 768px) { 419 | .sm-dox li, .tablist li { 420 | display: var(--menu-display); 421 | } 422 | 423 | .sm-dox a span.sub-arrow { 424 | border-color: var(--header-foreground) transparent transparent transparent; 425 | } 426 | 427 | .sm-dox a:hover span.sub-arrow { 428 | border-color: var(--menu-focus-foreground) transparent transparent transparent; 429 | } 430 | 431 | .sm-dox ul a span.sub-arrow { 432 | border-color: transparent transparent transparent var(--page-foreground-color); 433 | } 434 | 435 | .sm-dox ul a:hover span.sub-arrow { 436 | border-color: transparent transparent transparent var(--menu-focus-foreground); 437 | } 438 | } 439 | 440 | .sm-dox ul { 441 | background: var(--page-background-color); 442 | box-shadow: var(--box-shadow); 443 | border: 1px solid var(--separator-color); 444 | border-radius: var(--border-radius-medium) !important; 445 | padding: var(--spacing-small); 446 | animation: ease-out 150ms slideInMenu; 447 | } 448 | 449 | @keyframes slideInMenu { 450 | from { 451 | opacity: 0; 452 | transform: translate(0px, -2px); 453 | } 454 | 455 | to { 456 | opacity: 1; 457 | transform: translate(0px, 0px); 458 | } 459 | } 460 | 461 | .sm-dox ul a { 462 | color: var(--page-foreground-color) !important; 463 | background: var(--page-background-color); 464 | font-size: var(--navigation-font-size); 465 | } 466 | 467 | .sm-dox>li>ul:after { 468 | border-bottom-color: var(--page-background-color) !important; 469 | } 470 | 471 | .sm-dox>li>ul:before { 472 | border-bottom-color: var(--separator-color) !important; 473 | } 474 | 475 | .sm-dox ul a:hover, .sm-dox ul a:active, .sm-dox ul a:focus { 476 | font-size: var(--navigation-font-size) !important; 477 | color: var(--menu-focus-foreground) !important; 478 | text-shadow: none; 479 | background-color: var(--menu-focus-background); 480 | border-radius: var(--border-radius-small) !important; 481 | } 482 | 483 | .sm-dox a, .sm-dox a:focus, .tablist li, .tablist li a, .tablist li.current a { 484 | text-shadow: none; 485 | background: transparent; 486 | background-image: none !important; 487 | color: var(--header-foreground) !important; 488 | font-weight: normal; 489 | font-size: var(--navigation-font-size); 490 | border-radius: var(--border-radius-small) !important; 491 | } 492 | 493 | .sm-dox a:focus { 494 | outline: auto; 495 | } 496 | 497 | .sm-dox a:hover, .sm-dox a:active, .tablist li a:hover { 498 | text-shadow: none; 499 | font-weight: normal; 500 | background: var(--menu-focus-background); 501 | color: var(--menu-focus-foreground) !important; 502 | border-radius: var(--border-radius-small) !important; 503 | font-size: var(--navigation-font-size); 504 | } 505 | 506 | .tablist li.current { 507 | border-radius: var(--border-radius-small); 508 | background: var(--menu-selected-background); 509 | } 510 | 511 | .tablist li { 512 | margin: var(--spacing-small) 0 var(--spacing-small) var(--spacing-small); 513 | } 514 | 515 | .tablist a { 516 | padding: 0 var(--spacing-large); 517 | } 518 | 519 | 520 | /* 521 | Search box 522 | */ 523 | 524 | #MSearchBox { 525 | height: var(--searchbar-height); 526 | background: var(--searchbar-background); 527 | border-radius: var(--searchbar-border-radius); 528 | border: 1px solid var(--separator-color); 529 | overflow: hidden; 530 | width: var(--searchbar-width); 531 | position: relative; 532 | box-shadow: none; 533 | display: block; 534 | margin-top: 0; 535 | } 536 | 537 | .left #MSearchSelect { 538 | left: 0; 539 | user-select: none; 540 | } 541 | 542 | .SelectionMark { 543 | user-select: none; 544 | } 545 | 546 | .tabs .left #MSearchSelect { 547 | padding-left: 0; 548 | } 549 | 550 | .tabs #MSearchBox { 551 | position: absolute; 552 | right: var(--spacing-medium); 553 | } 554 | 555 | @media screen and (max-width: 767px) { 556 | .tabs #MSearchBox { 557 | position: relative; 558 | right: 0; 559 | margin-left: var(--spacing-medium); 560 | margin-top: 0; 561 | } 562 | } 563 | 564 | #MSearchSelectWindow, #MSearchResultsWindow { 565 | z-index: 9999; 566 | } 567 | 568 | #MSearchBox.MSearchBoxActive { 569 | border-color: var(--primary-color); 570 | box-shadow: inset 0 0 0 1px var(--primary-color); 571 | } 572 | 573 | #main-menu > li:last-child { 574 | margin-right: 0; 575 | } 576 | 577 | @media screen and (max-width: 767px) { 578 | #main-menu > li:last-child { 579 | height: 50px; 580 | } 581 | } 582 | 583 | #MSearchField { 584 | font-size: var(--navigation-font-size); 585 | height: calc(var(--searchbar-height) - 2px); 586 | background: transparent; 587 | width: calc(var(--searchbar-width) - 64px); 588 | } 589 | 590 | .MSearchBoxActive #MSearchField { 591 | color: var(--searchbar-foreground); 592 | } 593 | 594 | #MSearchSelect { 595 | top: calc(calc(var(--searchbar-height) / 2) - 11px); 596 | } 597 | 598 | .left #MSearchSelect { 599 | padding-left: 8px; 600 | } 601 | 602 | #MSearchBox span.left, #MSearchBox span.right { 603 | background: none; 604 | } 605 | 606 | #MSearchBox span.right { 607 | padding-top: calc(calc(var(--searchbar-height) / 2) - 12px); 608 | position: absolute; 609 | right: var(--spacing-small); 610 | } 611 | 612 | .tabs #MSearchBox span.right { 613 | top: calc(calc(var(--searchbar-height) / 2) - 12px); 614 | } 615 | 616 | @keyframes slideInSearchResults { 617 | from { 618 | opacity: 0; 619 | transform: translate(0, 15px); 620 | } 621 | 622 | to { 623 | opacity: 1; 624 | transform: translate(0, 20px); 625 | } 626 | } 627 | 628 | #MSearchResultsWindow { 629 | left: auto !important; 630 | right: var(--spacing-medium); 631 | border-radius: var(--border-radius-large); 632 | border: 1px solid var(--separator-color); 633 | transform: translate(0, 20px); 634 | box-shadow: var(--box-shadow); 635 | animation: ease-out 280ms slideInSearchResults; 636 | background: var(--page-background-color); 637 | } 638 | 639 | iframe#MSearchResults { 640 | margin: 4px; 641 | } 642 | 643 | iframe { 644 | color-scheme: normal; 645 | } 646 | 647 | @media (prefers-color-scheme: dark) { 648 | html:not(.light-mode) iframe#MSearchResults { 649 | filter: invert() hue-rotate(180deg); 650 | } 651 | } 652 | 653 | html.dark-mode iframe#MSearchResults { 654 | filter: invert() hue-rotate(180deg); 655 | } 656 | 657 | #MSearchSelectWindow { 658 | border: 1px solid var(--separator-color); 659 | border-radius: var(--border-radius-medium); 660 | box-shadow: var(--box-shadow); 661 | background: var(--page-background-color); 662 | padding-top: var(--spacing-small); 663 | padding-bottom: var(--spacing-small); 664 | } 665 | 666 | #MSearchSelectWindow a.SelectItem { 667 | font-size: var(--navigation-font-size); 668 | line-height: var(--content-line-height); 669 | margin: 0 var(--spacing-small); 670 | border-radius: var(--border-radius-small); 671 | color: var(--page-foreground-color) !important; 672 | font-weight: normal; 673 | } 674 | 675 | #MSearchSelectWindow a.SelectItem:hover { 676 | background: var(--menu-focus-background); 677 | color: var(--menu-focus-foreground) !important; 678 | } 679 | 680 | @media screen and (max-width: 767px) { 681 | #MSearchBox { 682 | margin-top: var(--spacing-medium); 683 | margin-bottom: var(--spacing-medium); 684 | width: calc(100vw - 30px); 685 | } 686 | 687 | #main-menu > li:last-child { 688 | float: none !important; 689 | } 690 | 691 | #MSearchField { 692 | width: calc(100vw - 110px); 693 | } 694 | 695 | @keyframes slideInSearchResultsMobile { 696 | from { 697 | opacity: 0; 698 | transform: translate(0, 15px); 699 | } 700 | 701 | to { 702 | opacity: 1; 703 | transform: translate(0, 20px); 704 | } 705 | } 706 | 707 | #MSearchResultsWindow { 708 | left: var(--spacing-medium) !important; 709 | right: var(--spacing-medium); 710 | overflow: auto; 711 | transform: translate(0, 20px); 712 | animation: ease-out 280ms slideInSearchResultsMobile; 713 | } 714 | 715 | /* 716 | * Overwrites for fixing the searchbox on mobile in doxygen 1.9.2 717 | */ 718 | label.main-menu-btn ~ #searchBoxPos1 { 719 | top: 3px !important; 720 | right: 6px !important; 721 | left: 45px; 722 | display: flex; 723 | } 724 | 725 | label.main-menu-btn ~ #searchBoxPos1 > #MSearchBox { 726 | margin-top: 0; 727 | margin-bottom: 0; 728 | flex-grow: 2; 729 | float: left; 730 | } 731 | } 732 | 733 | /* 734 | Tree view 735 | */ 736 | 737 | #side-nav { 738 | padding: 0 !important; 739 | background: var(--side-nav-background); 740 | } 741 | 742 | @media screen and (max-width: 767px) { 743 | #side-nav { 744 | display: none; 745 | } 746 | 747 | #doc-content { 748 | margin-left: 0 !important; 749 | } 750 | } 751 | 752 | #nav-tree { 753 | background: transparent; 754 | } 755 | 756 | #nav-tree .label { 757 | font-size: var(--navigation-font-size); 758 | } 759 | 760 | #nav-tree .item { 761 | height: var(--tree-item-height); 762 | line-height: var(--tree-item-height); 763 | } 764 | 765 | #nav-sync { 766 | bottom: 12px; 767 | right: 12px; 768 | top: auto !important; 769 | user-select: none; 770 | } 771 | 772 | #nav-tree .selected { 773 | text-shadow: none; 774 | background-image: none; 775 | background-color: transparent; 776 | position: relative; 777 | } 778 | 779 | #nav-tree .selected::after { 780 | content: ""; 781 | position: absolute; 782 | top: 1px; 783 | bottom: 1px; 784 | left: 0; 785 | width: 4px; 786 | border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0; 787 | background: var(--primary-color); 788 | } 789 | 790 | 791 | #nav-tree a { 792 | color: var(--side-nav-foreground) !important; 793 | font-weight: normal; 794 | } 795 | 796 | #nav-tree a:focus { 797 | outline-style: auto; 798 | } 799 | 800 | #nav-tree .arrow { 801 | opacity: var(--side-nav-arrow-opacity); 802 | } 803 | 804 | .arrow { 805 | color: inherit; 806 | cursor: pointer; 807 | font-size: 45%; 808 | vertical-align: middle; 809 | margin-right: 2px; 810 | font-family: serif; 811 | height: auto; 812 | text-align: right; 813 | } 814 | 815 | #nav-tree div.item:hover .arrow, #nav-tree a:focus .arrow { 816 | opacity: var(--side-nav-arrow-hover-opacity); 817 | } 818 | 819 | #nav-tree .selected a { 820 | color: var(--primary-color) !important; 821 | font-weight: bolder; 822 | font-weight: 600; 823 | } 824 | 825 | .ui-resizable-e { 826 | background: var(--separator-color); 827 | width: 1px; 828 | } 829 | 830 | /* 831 | Contents 832 | */ 833 | 834 | div.header { 835 | border-bottom: 1px solid var(--separator-color); 836 | background-color: var(--page-background-color); 837 | background-image: none; 838 | } 839 | 840 | div.contents, div.header .title, div.header .summary { 841 | max-width: var(--content-maxwidth); 842 | } 843 | 844 | div.contents, div.header .title { 845 | line-height: initial; 846 | margin: calc(var(--spacing-medium) + .2em) auto var(--spacing-medium) auto; 847 | } 848 | 849 | div.header .summary { 850 | margin: var(--spacing-medium) auto 0 auto; 851 | } 852 | 853 | div.headertitle { 854 | padding: 0; 855 | } 856 | 857 | div.header .title { 858 | font-weight: 600; 859 | font-size: 210%; 860 | padding: var(--spacing-medium) var(--spacing-large); 861 | word-break: break-word; 862 | } 863 | 864 | div.header .summary { 865 | width: auto; 866 | display: block; 867 | float: none; 868 | padding: 0 var(--spacing-large); 869 | } 870 | 871 | td.memSeparator { 872 | border-color: var(--separator-color); 873 | } 874 | 875 | span.mlabel { 876 | background: var(--primary-color); 877 | border: none; 878 | padding: 4px 9px; 879 | border-radius: 12px; 880 | margin-right: var(--spacing-medium); 881 | } 882 | 883 | span.mlabel:last-of-type { 884 | margin-right: 2px; 885 | } 886 | 887 | div.contents { 888 | padding: 0 var(--spacing-large); 889 | } 890 | 891 | div.contents p, div.contents li { 892 | line-height: var(--content-line-height); 893 | } 894 | 895 | div.contents div.dyncontent { 896 | margin: var(--spacing-medium) 0; 897 | } 898 | 899 | @media (prefers-color-scheme: dark) { 900 | html:not(.light-mode) div.contents div.dyncontent img, 901 | html:not(.light-mode) div.contents center img, 902 | html:not(.light-mode) div.contents table img, 903 | html:not(.light-mode) div.contents div.dyncontent iframe, 904 | html:not(.light-mode) div.contents center iframe, 905 | html:not(.light-mode) div.contents table iframe { 906 | filter: hue-rotate(180deg) invert(); 907 | } 908 | } 909 | 910 | html.dark-mode div.contents div.dyncontent img, 911 | html.dark-mode div.contents center img, 912 | html.dark-mode div.contents table img, 913 | html.dark-mode div.contents div.dyncontent iframe, 914 | html.dark-mode div.contents center iframe, 915 | html.dark-mode div.contents table iframe { 916 | filter: hue-rotate(180deg) invert(); 917 | } 918 | 919 | h2.groupheader { 920 | border-bottom: 0px; 921 | color: var(--page-foreground-color); 922 | box-shadow: 923 | 100px 0 var(--page-background-color), 924 | -100px 0 var(--page-background-color), 925 | 100px 0.75px var(--separator-color), 926 | -100px 0.75px var(--separator-color), 927 | 500px 0 var(--page-background-color), 928 | -500px 0 var(--page-background-color), 929 | 500px 0.75px var(--separator-color), 930 | -500px 0.75px var(--separator-color), 931 | 1500px 0 var(--page-background-color), 932 | -1500px 0 var(--page-background-color), 933 | 1500px 0.75px var(--separator-color), 934 | -1500px 0.75px var(--separator-color), 935 | 2000px 0 var(--page-background-color), 936 | -2000px 0 var(--page-background-color), 937 | 2000px 0.75px var(--separator-color), 938 | -2000px 0.75px var(--separator-color); 939 | } 940 | 941 | blockquote { 942 | margin: 0 var(--spacing-medium) 0 var(--spacing-medium); 943 | padding: var(--spacing-small) var(--spacing-large); 944 | background: var(--blockquote-background); 945 | color: var(--blockquote-foreground); 946 | border-left: 0; 947 | overflow: visible; 948 | border-radius: var(--border-radius-medium); 949 | overflow: visible; 950 | position: relative; 951 | } 952 | 953 | blockquote::before, blockquote::after { 954 | font-weight: bold; 955 | font-family: serif; 956 | font-size: 360%; 957 | opacity: .15; 958 | position: absolute; 959 | } 960 | 961 | blockquote::before { 962 | content: "“"; 963 | left: -10px; 964 | top: 4px; 965 | } 966 | 967 | blockquote::after { 968 | content: "”"; 969 | right: -8px; 970 | bottom: -25px; 971 | } 972 | 973 | blockquote p { 974 | margin: var(--spacing-small) 0 var(--spacing-medium) 0; 975 | } 976 | .paramname { 977 | font-weight: 600; 978 | color: var(--primary-dark-color); 979 | } 980 | 981 | .paramname > code { 982 | border: 0; 983 | } 984 | 985 | table.params .paramname { 986 | font-weight: 600; 987 | font-family: var(--font-family-monospace); 988 | font-size: var(--code-font-size); 989 | padding-right: var(--spacing-small); 990 | } 991 | 992 | .glow { 993 | text-shadow: 0 0 15px var(--primary-light-color) !important; 994 | } 995 | 996 | .alphachar a { 997 | color: var(--page-foreground-color); 998 | } 999 | 1000 | /* 1001 | Table of Contents 1002 | */ 1003 | 1004 | div.toc { 1005 | z-index: 10; 1006 | position: relative; 1007 | background-color: var(--toc-background); 1008 | border: 1px solid var(--separator-color); 1009 | border-radius: var(--border-radius-medium); 1010 | box-shadow: var(--box-shadow); 1011 | padding: 0 var(--spacing-large); 1012 | margin: 0 0 var(--spacing-medium) var(--spacing-medium); 1013 | } 1014 | 1015 | div.toc h3 { 1016 | color: var(--toc-foreground); 1017 | font-size: var(--navigation-font-size); 1018 | margin: var(--spacing-large) 0; 1019 | } 1020 | 1021 | div.toc li { 1022 | font-size: var(--navigation-font-size); 1023 | padding: 0; 1024 | background: none; 1025 | } 1026 | 1027 | div.toc li:before { 1028 | content: '↓'; 1029 | font-weight: 800; 1030 | font-family: var(--font-family); 1031 | margin-right: var(--spacing-small); 1032 | color: var(--toc-foreground); 1033 | opacity: .4; 1034 | } 1035 | 1036 | div.toc ul li.level1 { 1037 | margin: 0; 1038 | } 1039 | 1040 | div.toc ul li.level2, div.toc ul li.level3 { 1041 | margin-top: 0; 1042 | } 1043 | 1044 | 1045 | @media screen and (max-width: 767px) { 1046 | div.toc { 1047 | float: none; 1048 | width: auto; 1049 | margin: 0 0 var(--spacing-medium) 0; 1050 | } 1051 | } 1052 | 1053 | /* 1054 | Code & Fragments 1055 | */ 1056 | 1057 | code, div.fragment, pre.fragment { 1058 | border-radius: var(--border-radius-small); 1059 | border: 1px solid var(--separator-color); 1060 | overflow: hidden; 1061 | } 1062 | 1063 | code { 1064 | display: inline; 1065 | background: var(--code-background); 1066 | color: var(--code-foreground); 1067 | padding: 2px 6px; 1068 | word-break: break-word; 1069 | } 1070 | 1071 | div.fragment, pre.fragment { 1072 | margin: var(--spacing-medium) 0; 1073 | padding: calc(var(--spacing-large) - (var(--spacing-large) / 6)) var(--spacing-large); 1074 | background: var(--fragment-background); 1075 | color: var(--fragment-foreground); 1076 | overflow-x: auto; 1077 | } 1078 | 1079 | @media screen and (max-width: 767px) { 1080 | div.fragment, pre.fragment { 1081 | border-top-right-radius: 0; 1082 | border-bottom-right-radius: 0; 1083 | border-right: 0; 1084 | } 1085 | 1086 | .contents > div.fragment, 1087 | .textblock > div.fragment, 1088 | .textblock > pre.fragment, 1089 | .contents > .doxygen-awesome-fragment-wrapper > div.fragment, 1090 | .textblock > .doxygen-awesome-fragment-wrapper > div.fragment, 1091 | .textblock > .doxygen-awesome-fragment-wrapper > pre.fragment { 1092 | margin: var(--spacing-medium) calc(0px - var(--spacing-large)); 1093 | border-radius: 0; 1094 | border-left: 0; 1095 | } 1096 | 1097 | .textblock li > .fragment, 1098 | .textblock li > .doxygen-awesome-fragment-wrapper > .fragment { 1099 | margin: var(--spacing-medium) calc(0px - var(--spacing-large)); 1100 | } 1101 | 1102 | .memdoc li > .fragment, 1103 | .memdoc li > .doxygen-awesome-fragment-wrapper > .fragment { 1104 | margin: var(--spacing-medium) calc(0px - var(--spacing-medium)); 1105 | } 1106 | 1107 | .textblock ul, .memdoc ul { 1108 | overflow: initial; 1109 | } 1110 | 1111 | .memdoc > div.fragment, 1112 | .memdoc > pre.fragment, 1113 | dl dd > div.fragment, 1114 | dl dd pre.fragment, 1115 | .memdoc > .doxygen-awesome-fragment-wrapper > div.fragment, 1116 | .memdoc > .doxygen-awesome-fragment-wrapper > pre.fragment, 1117 | dl dd > .doxygen-awesome-fragment-wrapper > div.fragment, 1118 | dl dd .doxygen-awesome-fragment-wrapper > pre.fragment { 1119 | margin: var(--spacing-medium) calc(0px - var(--spacing-medium)); 1120 | border-radius: 0; 1121 | border-left: 0; 1122 | } 1123 | } 1124 | 1125 | code, code a, pre.fragment, div.fragment, div.fragment .line, div.fragment span, div.fragment .line a, div.fragment .line span { 1126 | font-family: var(--font-family-monospace); 1127 | font-size: var(--code-font-size) !important; 1128 | } 1129 | 1130 | div.line:after { 1131 | margin-right: var(--spacing-medium); 1132 | } 1133 | 1134 | div.fragment .line, pre.fragment { 1135 | white-space: pre; 1136 | word-wrap: initial; 1137 | line-height: var(--fragment-lineheight); 1138 | } 1139 | 1140 | div.fragment span.keyword { 1141 | color: var(--fragment-keyword); 1142 | } 1143 | 1144 | div.fragment span.keywordtype { 1145 | color: var(--fragment-keywordtype); 1146 | } 1147 | 1148 | div.fragment span.keywordflow { 1149 | color: var(--fragment-keywordflow); 1150 | } 1151 | 1152 | div.fragment span.stringliteral { 1153 | color: var(--fragment-token) 1154 | } 1155 | 1156 | div.fragment span.comment { 1157 | color: var(--fragment-comment); 1158 | } 1159 | 1160 | div.fragment a.code { 1161 | color: var(--fragment-link) !important; 1162 | } 1163 | 1164 | div.fragment span.preprocessor { 1165 | color: var(--fragment-preprocessor); 1166 | } 1167 | 1168 | div.fragment span.lineno { 1169 | display: inline-block; 1170 | width: 27px; 1171 | border-right: none; 1172 | background: var(--fragment-linenumber-background); 1173 | color: var(--fragment-linenumber-color); 1174 | } 1175 | 1176 | div.fragment span.lineno a { 1177 | background: none; 1178 | color: var(--fragment-link) !important; 1179 | } 1180 | 1181 | div.fragment .line:first-child .lineno { 1182 | box-shadow: -999999px 0px 0 999999px var(--fragment-linenumber-background), -999998px 0px 0 999999px var(--fragment-linenumber-border); 1183 | } 1184 | 1185 | /* 1186 | dl warning, attention, note, deprecated, bug, ... 1187 | */ 1188 | 1189 | dl.bug dt a, dl.deprecated dt a, dl.todo dt a { 1190 | font-weight: bold !important; 1191 | } 1192 | 1193 | dl.warning, dl.attention, dl.note, dl.deprecated, dl.bug, dl.invariant, dl.pre, dl.todo, dl.remark { 1194 | padding: var(--spacing-medium); 1195 | margin: var(--spacing-medium) 0; 1196 | color: var(--page-background-color); 1197 | overflow: hidden; 1198 | margin-left: 0; 1199 | border-radius: var(--border-radius-small); 1200 | } 1201 | 1202 | dl.section dd { 1203 | margin-bottom: 2px; 1204 | } 1205 | 1206 | dl.warning, dl.attention { 1207 | background: var(--warning-color); 1208 | border-left: 8px solid var(--warning-color-dark); 1209 | color: var(--warning-color-darker); 1210 | } 1211 | 1212 | dl.warning dt, dl.attention dt { 1213 | color: var(--warning-color-dark); 1214 | } 1215 | 1216 | dl.note, dl.remark { 1217 | background: var(--note-color); 1218 | border-left: 8px solid var(--note-color-dark); 1219 | color: var(--note-color-darker); 1220 | } 1221 | 1222 | dl.note dt, dl.remark dt { 1223 | color: var(--note-color-dark); 1224 | } 1225 | 1226 | dl.todo { 1227 | background: var(--todo-color); 1228 | border-left: 8px solid var(--todo-color-dark); 1229 | color: var(--todo-color-darker); 1230 | } 1231 | 1232 | dl.todo dt { 1233 | color: var(--todo-color-dark); 1234 | } 1235 | 1236 | dl.bug dt a { 1237 | color: var(--todo-color-dark) !important; 1238 | } 1239 | 1240 | dl.bug { 1241 | background: var(--bug-color); 1242 | border-left: 8px solid var(--bug-color-dark); 1243 | color: var(--bug-color-darker); 1244 | } 1245 | 1246 | dl.bug dt a { 1247 | color: var(--bug-color-dark) !important; 1248 | } 1249 | 1250 | dl.deprecated { 1251 | background: var(--deprecated-color); 1252 | border-left: 8px solid var(--deprecated-color-dark); 1253 | color: var(--deprecated-color-darker); 1254 | } 1255 | 1256 | dl.deprecated dt a { 1257 | color: var(--deprecated-color-dark) !important; 1258 | } 1259 | 1260 | dl.section dd, dl.bug dd, dl.deprecated dd, dl.todo dd { 1261 | margin-inline-start: 0px; 1262 | } 1263 | 1264 | dl.invariant, dl.pre { 1265 | background: var(--invariant-color); 1266 | border-left: 8px solid var(--invariant-color-dark); 1267 | color: var(--invariant-color-darker); 1268 | } 1269 | 1270 | dl.invariant dt, dl.pre dt { 1271 | color: var(--invariant-color-dark); 1272 | } 1273 | 1274 | /* 1275 | memitem 1276 | */ 1277 | 1278 | div.memdoc, div.memproto, h2.memtitle { 1279 | box-shadow: none; 1280 | background-image: none; 1281 | border: none; 1282 | } 1283 | 1284 | div.memdoc { 1285 | padding: 0 var(--spacing-medium); 1286 | background: var(--page-background-color); 1287 | } 1288 | 1289 | h2.memtitle, div.memitem { 1290 | border: 1px solid var(--separator-color); 1291 | box-shadow: var(--box-shadow); 1292 | } 1293 | 1294 | h2.memtitle { 1295 | box-shadow: 0px var(--spacing-medium) 0 -1px var(--fragment-background), var(--box-shadow); 1296 | } 1297 | 1298 | div.memitem { 1299 | transition: none; 1300 | } 1301 | 1302 | div.memproto, h2.memtitle { 1303 | background: var(--fragment-background); 1304 | text-shadow: none; 1305 | } 1306 | 1307 | h2.memtitle { 1308 | font-weight: 500; 1309 | font-size: var(--memtitle-font-size); 1310 | font-family: var(--font-family-monospace); 1311 | border-bottom: none; 1312 | border-top-left-radius: var(--border-radius-medium); 1313 | border-top-right-radius: var(--border-radius-medium); 1314 | word-break: break-all; 1315 | position: relative; 1316 | } 1317 | 1318 | h2.memtitle:after { 1319 | content: ""; 1320 | display: block; 1321 | background: var(--fragment-background); 1322 | height: var(--spacing-medium); 1323 | bottom: calc(0px - var(--spacing-medium)); 1324 | left: 0; 1325 | right: -14px; 1326 | position: absolute; 1327 | border-top-right-radius: var(--border-radius-medium); 1328 | } 1329 | 1330 | h2.memtitle > span.permalink { 1331 | font-size: inherit; 1332 | } 1333 | 1334 | h2.memtitle > span.permalink > a { 1335 | text-decoration: none; 1336 | padding-left: 3px; 1337 | margin-right: -4px; 1338 | user-select: none; 1339 | display: inline-block; 1340 | margin-top: -6px; 1341 | } 1342 | 1343 | h2.memtitle > span.permalink > a:hover { 1344 | color: var(--primary-dark-color) !important; 1345 | } 1346 | 1347 | a:target + h2.memtitle, a:target + h2.memtitle + div.memitem { 1348 | border-color: var(--primary-light-color); 1349 | } 1350 | 1351 | div.memitem { 1352 | border-top-right-radius: var(--border-radius-medium); 1353 | border-bottom-right-radius: var(--border-radius-medium); 1354 | border-bottom-left-radius: var(--border-radius-medium); 1355 | overflow: hidden; 1356 | display: block !important; 1357 | } 1358 | 1359 | div.memdoc { 1360 | border-radius: 0; 1361 | } 1362 | 1363 | div.memproto { 1364 | border-radius: 0 var(--border-radius-small) 0 0; 1365 | overflow: auto; 1366 | border-bottom: 1px solid var(--separator-color); 1367 | padding: var(--spacing-medium); 1368 | margin-bottom: -1px; 1369 | } 1370 | 1371 | div.memtitle { 1372 | border-top-right-radius: var(--border-radius-medium); 1373 | border-top-left-radius: var(--border-radius-medium); 1374 | } 1375 | 1376 | div.memproto table.memname { 1377 | font-family: var(--font-family-monospace); 1378 | color: var(--page-foreground-color); 1379 | font-size: var(--memname-font-size); 1380 | } 1381 | 1382 | div.memproto div.memtemplate { 1383 | font-family: var(--font-family-monospace); 1384 | color: var(--primary-dark-color); 1385 | font-size: var(--memname-font-size); 1386 | margin-left: 2px; 1387 | } 1388 | 1389 | table.mlabels, table.mlabels > tbody { 1390 | display: block; 1391 | } 1392 | 1393 | td.mlabels-left { 1394 | width: auto; 1395 | } 1396 | 1397 | td.mlabels-right { 1398 | margin-top: 3px; 1399 | position: sticky; 1400 | left: 0; 1401 | } 1402 | 1403 | table.mlabels > tbody > tr:first-child { 1404 | display: flex; 1405 | justify-content: space-between; 1406 | flex-wrap: wrap; 1407 | } 1408 | 1409 | .memname, .memitem span.mlabels { 1410 | margin: 0 1411 | } 1412 | 1413 | /* 1414 | reflist 1415 | */ 1416 | 1417 | dl.reflist { 1418 | box-shadow: var(--box-shadow); 1419 | border-radius: var(--border-radius-medium); 1420 | border: 1px solid var(--separator-color); 1421 | overflow: hidden; 1422 | padding: 0; 1423 | } 1424 | 1425 | 1426 | dl.reflist dt, dl.reflist dd { 1427 | box-shadow: none; 1428 | text-shadow: none; 1429 | background-image: none; 1430 | border: none; 1431 | padding: 12px; 1432 | } 1433 | 1434 | 1435 | dl.reflist dt { 1436 | font-weight: 500; 1437 | border-radius: 0; 1438 | background: var(--code-background); 1439 | border-bottom: 1px solid var(--separator-color); 1440 | color: var(--page-foreground-color) 1441 | } 1442 | 1443 | 1444 | dl.reflist dd { 1445 | background: none; 1446 | } 1447 | 1448 | /* 1449 | Table 1450 | */ 1451 | 1452 | .contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) { 1453 | display: inline-block; 1454 | max-width: 100%; 1455 | } 1456 | 1457 | .contents > table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname):not(.classindex) { 1458 | margin-left: calc(0px - var(--spacing-large)); 1459 | margin-right: calc(0px - var(--spacing-large)); 1460 | max-width: calc(100% + 2 * var(--spacing-large)); 1461 | } 1462 | 1463 | table.markdownTable, table.fieldtable { 1464 | border: none; 1465 | margin: var(--spacing-medium) 0; 1466 | box-shadow: 0 0 0 1px var(--separator-color); 1467 | border-radius: var(--border-radius-small); 1468 | } 1469 | 1470 | table.fieldtable { 1471 | width: 100%; 1472 | } 1473 | 1474 | th.markdownTableHeadLeft, th.markdownTableHeadRight, th.markdownTableHeadCenter, th.markdownTableHeadNone { 1475 | background: var(--tablehead-background); 1476 | color: var(--tablehead-foreground); 1477 | font-weight: 600; 1478 | font-size: var(--page-font-size); 1479 | } 1480 | 1481 | th.markdownTableHeadLeft:first-child, th.markdownTableHeadRight:first-child, th.markdownTableHeadCenter:first-child, th.markdownTableHeadNone:first-child { 1482 | border-top-left-radius: var(--border-radius-small); 1483 | } 1484 | 1485 | th.markdownTableHeadLeft:last-child, th.markdownTableHeadRight:last-child, th.markdownTableHeadCenter:last-child, th.markdownTableHeadNone:last-child { 1486 | border-top-right-radius: var(--border-radius-small); 1487 | } 1488 | 1489 | table.markdownTable td, table.markdownTable th, table.fieldtable dt { 1490 | border: none; 1491 | border-right: 1px solid var(--separator-color); 1492 | padding: var(--spacing-small) var(--spacing-medium); 1493 | } 1494 | 1495 | table.markdownTable td:last-child, table.markdownTable th:last-child, table.fieldtable dt:last-child { 1496 | border: none; 1497 | } 1498 | 1499 | table.markdownTable tr, table.markdownTable tr { 1500 | border-bottom: 1px solid var(--separator-color); 1501 | } 1502 | 1503 | table.markdownTable tr:last-child, table.markdownTable tr:last-child { 1504 | border-bottom: none; 1505 | } 1506 | 1507 | table.fieldtable th { 1508 | font-size: var(--page-font-size); 1509 | font-weight: 600; 1510 | background-image: none; 1511 | background-color: var(--tablehead-background); 1512 | color: var(--tablehead-foreground); 1513 | border-bottom: 1px solid var(--separator-color); 1514 | } 1515 | 1516 | .fieldtable td.fieldtype, .fieldtable td.fieldname { 1517 | border-bottom: 1px solid var(--separator-color); 1518 | border-right: 1px solid var(--separator-color); 1519 | } 1520 | 1521 | .fieldtable td.fielddoc { 1522 | border-bottom: 1px solid var(--separator-color); 1523 | } 1524 | 1525 | .memberdecls td.glow, .fieldtable tr.glow { 1526 | background-color: var(--primary-light-color); 1527 | box-shadow: 0 0 15px var(--primary-light-color); 1528 | } 1529 | 1530 | table.memberdecls { 1531 | display: block; 1532 | } 1533 | 1534 | table.memberdecls tr[class^='memitem'] { 1535 | font-family: var(--font-family-monospace); 1536 | font-size: var(--code-font-size); 1537 | } 1538 | 1539 | table.memberdecls tr[class^='memitem'] .memTemplParams { 1540 | font-family: var(--font-family-monospace); 1541 | font-size: var(--code-font-size); 1542 | color: var(--primary-dark-color); 1543 | } 1544 | 1545 | table.memberdecls .memItemLeft, 1546 | table.memberdecls .memItemRight, 1547 | table.memberdecls .memTemplItemLeft, 1548 | table.memberdecls .memTemplItemRight, 1549 | table.memberdecls .memTemplParams { 1550 | transition: none; 1551 | padding-top: var(--spacing-small); 1552 | padding-bottom: var(--spacing-small); 1553 | border-top: 1px solid var(--separator-color); 1554 | border-bottom: 1px solid var(--separator-color); 1555 | background-color: var(--fragment-background); 1556 | } 1557 | 1558 | table.memberdecls .memTemplItemLeft, 1559 | table.memberdecls .memTemplItemRight { 1560 | padding-top: 2px; 1561 | } 1562 | 1563 | table.memberdecls .memTemplParams { 1564 | border-bottom: 0; 1565 | border-left: 1px solid var(--separator-color); 1566 | border-right: 1px solid var(--separator-color); 1567 | border-radius: var(--border-radius-small) var(--border-radius-small) 0 0; 1568 | padding-bottom: 0; 1569 | } 1570 | 1571 | table.memberdecls .memTemplItemLeft { 1572 | border-radius: 0 0 0 var(--border-radius-small); 1573 | border-left: 1px solid var(--separator-color); 1574 | border-top: 0; 1575 | } 1576 | 1577 | table.memberdecls .memTemplItemRight { 1578 | border-radius: 0 0 var(--border-radius-small) 0; 1579 | border-right: 1px solid var(--separator-color); 1580 | border-top: 0; 1581 | } 1582 | 1583 | table.memberdecls .memItemLeft { 1584 | border-radius: var(--border-radius-small) 0 0 var(--border-radius-small); 1585 | border-left: 1px solid var(--separator-color); 1586 | padding-left: var(--spacing-medium); 1587 | padding-right: 0; 1588 | } 1589 | 1590 | table.memberdecls .memItemRight { 1591 | border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0; 1592 | border-right: 1px solid var(--separator-color); 1593 | padding-right: var(--spacing-medium); 1594 | padding-left: 0; 1595 | 1596 | } 1597 | 1598 | table.memberdecls .mdescLeft, table.memberdecls .mdescRight { 1599 | background: none; 1600 | color: var(--page-foreground-color); 1601 | padding: var(--spacing-small) 0; 1602 | } 1603 | 1604 | table.memberdecls .memSeparator { 1605 | background: var(--page-background-color); 1606 | height: var(--spacing-large); 1607 | border: 0; 1608 | transition: none; 1609 | } 1610 | 1611 | table.memberdecls .groupheader { 1612 | margin-bottom: var(--spacing-large); 1613 | } 1614 | 1615 | table.memberdecls .inherit_header td { 1616 | padding: 0 0 var(--spacing-medium) 0; 1617 | text-indent: -12px; 1618 | line-height: 1.5em; 1619 | color: var(--page-secondary-foreground-color); 1620 | } 1621 | 1622 | @media screen and (max-width: 767px) { 1623 | 1624 | table.memberdecls .memItemLeft, 1625 | table.memberdecls .memItemRight, 1626 | table.memberdecls .mdescLeft, 1627 | table.memberdecls .mdescRight, 1628 | table.memberdecls .memTemplItemLeft, 1629 | table.memberdecls .memTemplItemRight, 1630 | table.memberdecls .memTemplParams { 1631 | display: block; 1632 | text-align: left; 1633 | padding-left: var(--spacing-large); 1634 | margin: 0 calc(0px - var(--spacing-large)) 0 calc(0px - var(--spacing-large)); 1635 | border-right: none; 1636 | border-left: none; 1637 | border-radius: 0; 1638 | } 1639 | 1640 | table.memberdecls .memItemLeft, 1641 | table.memberdecls .mdescLeft, 1642 | table.memberdecls .memTemplItemLeft { 1643 | border-bottom: 0; 1644 | padding-bottom: 0; 1645 | } 1646 | 1647 | table.memberdecls .memTemplItemLeft { 1648 | padding-top: 0; 1649 | } 1650 | 1651 | table.memberdecls .mdescLeft { 1652 | margin-top: calc(0px - var(--page-font-size)); 1653 | } 1654 | 1655 | table.memberdecls .memItemRight, 1656 | table.memberdecls .mdescRight, 1657 | table.memberdecls .memTemplItemRight { 1658 | border-top: 0; 1659 | padding-top: 0; 1660 | padding-right: var(--spacing-large); 1661 | overflow-x: auto; 1662 | } 1663 | 1664 | table.memberdecls tr[class^='memitem']:not(.inherit) { 1665 | display: block; 1666 | width: calc(100vw - 2 * var(--spacing-large)); 1667 | } 1668 | 1669 | table.memberdecls .mdescRight { 1670 | color: var(--page-foreground-color); 1671 | } 1672 | 1673 | table.memberdecls tr.inherit { 1674 | visibility: hidden; 1675 | } 1676 | 1677 | table.memberdecls tr[style="display: table-row;"] { 1678 | display: block !important; 1679 | visibility: visible; 1680 | width: calc(100vw - 2 * var(--spacing-large)); 1681 | animation: fade .5s; 1682 | } 1683 | 1684 | @keyframes fade { 1685 | 0% { 1686 | opacity: 0; 1687 | max-height: 0; 1688 | } 1689 | 1690 | 100% { 1691 | opacity: 1; 1692 | max-height: 200px; 1693 | } 1694 | } 1695 | } 1696 | 1697 | 1698 | /* 1699 | Horizontal Rule 1700 | */ 1701 | 1702 | hr { 1703 | margin-top: var(--spacing-large); 1704 | margin-bottom: var(--spacing-large); 1705 | height: 1px; 1706 | background-color: var(--separator-color); 1707 | border: 0; 1708 | } 1709 | 1710 | .contents hr { 1711 | box-shadow: 100px 0 0 var(--separator-color), 1712 | -100px 0 0 var(--separator-color), 1713 | 500px 0 0 var(--separator-color), 1714 | -500px 0 0 var(--separator-color), 1715 | 1500px 0 0 var(--separator-color), 1716 | -1500px 0 0 var(--separator-color), 1717 | 2000px 0 0 var(--separator-color), 1718 | -2000px 0 0 var(--separator-color); 1719 | } 1720 | 1721 | .contents img, .contents .center, .contents center, .contents div.image object { 1722 | max-width: 100%; 1723 | overflow: auto; 1724 | } 1725 | 1726 | @media screen and (max-width: 767px) { 1727 | .contents .dyncontent > .center, .contents > center { 1728 | margin-left: calc(0px - var(--spacing-large)); 1729 | margin-right: calc(0px - var(--spacing-large)); 1730 | max-width: calc(100% + 2 * var(--spacing-large)); 1731 | } 1732 | } 1733 | 1734 | /* 1735 | Directories 1736 | */ 1737 | div.directory { 1738 | border-top: 1px solid var(--separator-color); 1739 | border-bottom: 1px solid var(--separator-color); 1740 | width: auto; 1741 | } 1742 | 1743 | table.directory { 1744 | font-family: var(--font-family); 1745 | font-size: var(--page-font-size); 1746 | font-weight: normal; 1747 | width: 100%; 1748 | } 1749 | 1750 | table.directory td.entry { 1751 | padding: var(--spacing-small); 1752 | } 1753 | 1754 | table.directory td.desc { 1755 | min-width: 250px; 1756 | } 1757 | 1758 | table.directory tr.even { 1759 | background-color: var(--odd-color); 1760 | } 1761 | 1762 | .icona { 1763 | width: auto; 1764 | height: auto; 1765 | margin: 0 var(--spacing-small); 1766 | } 1767 | 1768 | .icon { 1769 | background: var(--primary-color); 1770 | width: 18px; 1771 | height: 18px; 1772 | line-height: 18px; 1773 | } 1774 | 1775 | .iconfopen, .icondoc, .iconfclosed { 1776 | background-position: center; 1777 | margin-bottom: 0; 1778 | } 1779 | 1780 | .icondoc { 1781 | filter: saturate(0.2); 1782 | } 1783 | 1784 | @media screen and (max-width: 767px) { 1785 | div.directory { 1786 | margin-left: calc(0px - var(--spacing-large)); 1787 | margin-right: calc(0px - var(--spacing-large)); 1788 | } 1789 | } 1790 | 1791 | @media (prefers-color-scheme: dark) { 1792 | html:not(.light-mode) .iconfopen, html:not(.light-mode) .iconfclosed { 1793 | filter: hue-rotate(180deg) invert(); 1794 | } 1795 | } 1796 | 1797 | html.dark-mode .iconfopen, html.dark-mode .iconfclosed { 1798 | filter: hue-rotate(180deg) invert(); 1799 | } 1800 | 1801 | /* 1802 | Class list 1803 | */ 1804 | 1805 | .classindex dl.odd { 1806 | background: var(--odd-color); 1807 | border-radius: var(--border-radius-small); 1808 | } 1809 | 1810 | /* 1811 | Class Index Doxygen 1.8 1812 | */ 1813 | 1814 | table.classindex { 1815 | margin-left: 0; 1816 | margin-right: 0; 1817 | width: 100%; 1818 | } 1819 | 1820 | table.classindex table div.ah { 1821 | background-image: none; 1822 | background-color: initial; 1823 | border-color: var(--separator-color); 1824 | color: var(--page-foreground-color); 1825 | box-shadow: var(--box-shadow); 1826 | border-radius: var(--border-radius-large); 1827 | padding: var(--spacing-small); 1828 | } 1829 | 1830 | div.qindex { 1831 | background-color: var(--odd-color); 1832 | border-radius: var(--border-radius-small); 1833 | border: 1px solid var(--separator-color); 1834 | padding: var(--spacing-small) 0; 1835 | } 1836 | 1837 | /* 1838 | Footer and nav-path 1839 | */ 1840 | 1841 | #nav-path { 1842 | width: 100%; 1843 | } 1844 | 1845 | #nav-path ul { 1846 | background-image: none; 1847 | background: var(--page-background-color); 1848 | border: none; 1849 | border-top: 1px solid var(--separator-color); 1850 | border-bottom: 1px solid var(--separator-color); 1851 | border-bottom: 0; 1852 | box-shadow: 0 0.75px 0 var(--separator-color); 1853 | font-size: var(--navigation-font-size); 1854 | } 1855 | 1856 | img.footer { 1857 | width: 60px; 1858 | } 1859 | 1860 | .navpath li.footer { 1861 | color: var(--page-secondary-foreground-color); 1862 | } 1863 | 1864 | address.footer { 1865 | color: var(--page-secondary-foreground-color); 1866 | margin-bottom: var(--spacing-large); 1867 | } 1868 | 1869 | #nav-path li.navelem { 1870 | background-image: none; 1871 | display: flex; 1872 | align-items: center; 1873 | } 1874 | 1875 | .navpath li.navelem a { 1876 | text-shadow: none; 1877 | display: inline-block; 1878 | color: var(--primary-color) !important; 1879 | } 1880 | 1881 | .navpath li.navelem b { 1882 | color: var(--primary-dark-color); 1883 | font-weight: 500; 1884 | } 1885 | 1886 | li.navelem { 1887 | padding: 0; 1888 | margin-left: -8px; 1889 | } 1890 | 1891 | li.navelem:first-child { 1892 | margin-left: var(--spacing-large); 1893 | } 1894 | 1895 | li.navelem:first-child:before { 1896 | display: none; 1897 | } 1898 | 1899 | #nav-path li.navelem:after { 1900 | content: ''; 1901 | border: 5px solid var(--page-background-color); 1902 | border-bottom-color: transparent; 1903 | border-right-color: transparent; 1904 | border-top-color: transparent; 1905 | transform: translateY(-1px) scaleY(4.2); 1906 | z-index: 10; 1907 | margin-left: 6px; 1908 | } 1909 | 1910 | #nav-path li.navelem:before { 1911 | content: ''; 1912 | border: 5px solid var(--separator-color); 1913 | border-bottom-color: transparent; 1914 | border-right-color: transparent; 1915 | border-top-color: transparent; 1916 | transform: translateY(-1px) scaleY(3.2); 1917 | margin-right: var(--spacing-small); 1918 | } 1919 | 1920 | .navpath li.navelem a:hover { 1921 | color: var(--primary-color); 1922 | } 1923 | 1924 | /* 1925 | Scrollbars for Webkit 1926 | */ 1927 | 1928 | #nav-tree::-webkit-scrollbar, 1929 | div.fragment::-webkit-scrollbar, 1930 | pre.fragment::-webkit-scrollbar, 1931 | div.memproto::-webkit-scrollbar, 1932 | .contents center::-webkit-scrollbar, 1933 | .contents .center::-webkit-scrollbar, 1934 | .contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname)::-webkit-scrollbar { 1935 | width: calc(var(--webkit-scrollbar-size) + var(--webkit-scrollbar-padding) + var(--webkit-scrollbar-padding)); 1936 | height: calc(var(--webkit-scrollbar-size) + var(--webkit-scrollbar-padding) + var(--webkit-scrollbar-padding)); 1937 | } 1938 | 1939 | #nav-tree::-webkit-scrollbar-thumb, 1940 | div.fragment::-webkit-scrollbar-thumb, 1941 | pre.fragment::-webkit-scrollbar-thumb, 1942 | div.memproto::-webkit-scrollbar-thumb, 1943 | .contents center::-webkit-scrollbar-thumb, 1944 | .contents .center::-webkit-scrollbar-thumb, 1945 | .contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname)::-webkit-scrollbar-thumb { 1946 | background-color: transparent; 1947 | border: var(--webkit-scrollbar-padding) solid transparent; 1948 | border-radius: calc(var(--webkit-scrollbar-padding) + var(--webkit-scrollbar-padding)); 1949 | background-clip: padding-box; 1950 | } 1951 | 1952 | #nav-tree:hover::-webkit-scrollbar-thumb, 1953 | div.fragment:hover::-webkit-scrollbar-thumb, 1954 | pre.fragment:hover::-webkit-scrollbar-thumb, 1955 | div.memproto:hover::-webkit-scrollbar-thumb, 1956 | .contents center:hover::-webkit-scrollbar-thumb, 1957 | .contents .center:hover::-webkit-scrollbar-thumb, 1958 | .contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname):hover::-webkit-scrollbar-thumb { 1959 | background-color: var(--webkit-scrollbar-color); 1960 | } 1961 | 1962 | #nav-tree::-webkit-scrollbar-track, 1963 | div.fragment::-webkit-scrollbar-track, 1964 | pre.fragment::-webkit-scrollbar-track, 1965 | div.memproto::-webkit-scrollbar-track, 1966 | .contents center::-webkit-scrollbar-track, 1967 | .contents .center::-webkit-scrollbar-track, 1968 | .contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname)::-webkit-scrollbar-track { 1969 | background: transparent; 1970 | } 1971 | 1972 | #nav-tree::-webkit-scrollbar-corner { 1973 | background-color: var(--side-nav-background); 1974 | } 1975 | 1976 | #nav-tree, 1977 | div.fragment, 1978 | pre.fragment, 1979 | div.memproto, 1980 | .contents center, 1981 | .contents .center, 1982 | .contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) { 1983 | overflow-x: auto; 1984 | overflow-x: overlay; 1985 | } 1986 | 1987 | #nav-tree { 1988 | overflow-x: auto; 1989 | overflow-y: auto; 1990 | overflow-y: overlay; 1991 | } 1992 | 1993 | /* 1994 | Scrollbars for Firefox 1995 | */ 1996 | 1997 | #nav-tree, 1998 | div.fragment, 1999 | pre.fragment, 2000 | div.memproto, 2001 | .contents center, 2002 | .contents .center, 2003 | .contents table:not(.memberdecls):not(.mlabels):not(.fieldtable):not(.memname) { 2004 | scrollbar-width: thin; 2005 | } 2006 | 2007 | /* 2008 | Optional Dark mode toggle button 2009 | */ 2010 | 2011 | doxygen-awesome-dark-mode-toggle { 2012 | display: inline-block; 2013 | margin: 0 0 0 var(--spacing-small); 2014 | padding: 0; 2015 | width: var(--searchbar-height); 2016 | height: var(--searchbar-height); 2017 | background: none; 2018 | border: none; 2019 | border-radius: var(--searchbar-height); 2020 | vertical-align: middle; 2021 | text-align: center; 2022 | line-height: var(--searchbar-height); 2023 | font-size: 22px; 2024 | display: flex; 2025 | align-items: center; 2026 | justify-content: center; 2027 | user-select: none; 2028 | cursor: pointer; 2029 | } 2030 | 2031 | doxygen-awesome-dark-mode-toggle > svg { 2032 | transition: transform .1s ease-in-out; 2033 | } 2034 | 2035 | doxygen-awesome-dark-mode-toggle:active > svg { 2036 | transform: scale(.5); 2037 | } 2038 | 2039 | doxygen-awesome-dark-mode-toggle:hover { 2040 | background-color: rgba(0,0,0,.03); 2041 | } 2042 | 2043 | html.dark-mode doxygen-awesome-dark-mode-toggle:hover { 2044 | background-color: rgba(0,0,0,.18); 2045 | } 2046 | 2047 | /* 2048 | Optional fragment copy button 2049 | */ 2050 | .doxygen-awesome-fragment-wrapper { 2051 | position: relative; 2052 | } 2053 | 2054 | doxygen-awesome-fragment-copy-button { 2055 | opacity: 0; 2056 | background: var(--fragment-background); 2057 | width: 28px; 2058 | height: 28px; 2059 | position: absolute; 2060 | right: calc(var(--spacing-large) - (var(--spacing-large) / 2.5)); 2061 | top: calc(var(--spacing-large) - (var(--spacing-large) / 2.5)); 2062 | border: 1px solid var(--fragment-foreground); 2063 | cursor: pointer; 2064 | border-radius: var(--border-radius-small); 2065 | display: flex; 2066 | justify-content: center; 2067 | align-items: center; 2068 | } 2069 | 2070 | .doxygen-awesome-fragment-wrapper:hover doxygen-awesome-fragment-copy-button, doxygen-awesome-fragment-copy-button.success { 2071 | opacity: .28; 2072 | } 2073 | 2074 | doxygen-awesome-fragment-copy-button:hover, doxygen-awesome-fragment-copy-button.success { 2075 | opacity: 1 !important; 2076 | } 2077 | 2078 | doxygen-awesome-fragment-copy-button:active:not([class~=success]) svg { 2079 | transform: scale(.91); 2080 | } 2081 | 2082 | doxygen-awesome-fragment-copy-button svg { 2083 | fill: var(--fragment-foreground); 2084 | width: 18px; 2085 | height: 18px; 2086 | } 2087 | 2088 | doxygen-awesome-fragment-copy-button.success svg { 2089 | fill: rgb(14, 168, 14); 2090 | } 2091 | 2092 | doxygen-awesome-fragment-copy-button.success { 2093 | border-color: rgb(14, 168, 14); 2094 | } 2095 | 2096 | @media screen and (max-width: 767px) { 2097 | .textblock > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, 2098 | .textblock li > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, 2099 | .memdoc li > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, 2100 | .memdoc > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button, 2101 | dl dd > .doxygen-awesome-fragment-wrapper > doxygen-awesome-fragment-copy-button { 2102 | right: 0; 2103 | } 2104 | } 2105 | 2106 | /* 2107 | Optional paragraph link button 2108 | */ 2109 | 2110 | a.anchorlink { 2111 | font-size: 90%; 2112 | margin-left: var(--spacing-small); 2113 | color: var(--page-foreground-color) !important; 2114 | text-decoration: none; 2115 | opacity: .15; 2116 | display: none; 2117 | transition: opacity .1s ease-in-out, color .1s ease-in-out; 2118 | } 2119 | 2120 | a.anchorlink svg { 2121 | fill: var(--page-foreground-color); 2122 | } 2123 | 2124 | h3 a.anchorlink svg, h4 a.anchorlink svg { 2125 | margin-bottom: -3px; 2126 | margin-top: -4px; 2127 | } 2128 | 2129 | a.anchorlink:hover { 2130 | opacity: .45; 2131 | } 2132 | 2133 | h2:hover a.anchorlink, h1:hover a.anchorlink, h3:hover a.anchorlink, h4:hover a.anchorlink { 2134 | display: inline-block; 2135 | } 2136 | -------------------------------------------------------------------------------- /docs/doxygen-awesome-css/doxygen-custom/custom-alternative.css: -------------------------------------------------------------------------------- 1 | html.alternative { 2 | /* primary theme color. This will affect the entire websites color scheme: links, arrows, labels, ... */ 3 | --primary-color: #AF7FE4; 4 | --primary-dark-color: #9270E4; 5 | --primary-light-color: #7aabd6; 6 | --primary-lighter-color: #cae1f1; 7 | --primary-lightest-color: #e9f1f8; 8 | 9 | /* page base colors */ 10 | --page-background-color: white; 11 | --page-foreground-color: #2c3e50; 12 | --page-secondary-foreground-color: #67727e; 13 | 14 | 15 | --border-radius-large: 22px; 16 | --border-radius-small: 9px; 17 | --border-radius-medium: 14px; 18 | --spacing-small: 8px; 19 | --spacing-medium: 14px; 20 | --spacing-large: 19px; 21 | 22 | --top-height: 125px; 23 | 24 | --side-nav-background: #324067; 25 | --side-nav-foreground: #F1FDFF; 26 | --header-foreground: var(--side-nav-foreground); 27 | --searchbar-background: var(--side-nav-foreground); 28 | --searchbar-border-radius: var(--border-radius-medium); 29 | --header-background: var(--side-nav-background); 30 | --header-foreground: var(--side-nav-foreground); 31 | 32 | --toc-background: rgb(243, 240, 252); 33 | --toc-foreground: var(--page-foreground-color); 34 | } 35 | 36 | html.alternative.dark-mode { 37 | color-scheme: dark; 38 | 39 | --primary-color: #AF7FE4; 40 | --primary-dark-color: #9270E4; 41 | --primary-light-color: #4779ac; 42 | --primary-lighter-color: #191e21; 43 | --primary-lightest-color: #191a1c; 44 | 45 | --page-background-color: #1C1D1F; 46 | --page-foreground-color: #d2dbde; 47 | --page-secondary-foreground-color: #859399; 48 | --separator-color: #3a3246; 49 | --side-nav-background: #171D32; 50 | --side-nav-foreground: #F1FDFF; 51 | --toc-background: #20142C; 52 | --searchbar-background: var(--page-background-color); 53 | 54 | } -------------------------------------------------------------------------------- /docs/doxygen-awesome-css/doxygen-custom/custom.css: -------------------------------------------------------------------------------- 1 | .github-corner svg { 2 | fill: var(--primary-light-color); 3 | color: var(--page-background-color); 4 | width: 72px; 5 | height: 72px; 6 | } 7 | 8 | @media screen and (max-width: 767px) { 9 | .github-corner svg { 10 | width: 50px; 11 | height: 50px; 12 | } 13 | #projectnumber { 14 | margin-right: 22px; 15 | } 16 | } 17 | 18 | .alter-theme-button { 19 | display: inline-block; 20 | cursor: pointer; 21 | background: var(--primary-color); 22 | color: var(--page-background-color) !important; 23 | border-radius: var(--border-radius-medium); 24 | padding: var(--spacing-small) var(--spacing-medium); 25 | text-decoration: none; 26 | } 27 | 28 | .next_section_button { 29 | display: block; 30 | padding: var(--spacing-large) 0 var(--spacing-small) 0; 31 | color: var(--page-background-color); 32 | user-select: none; 33 | } 34 | 35 | .next_section_button::after { 36 | /* clearfix */ 37 | content: ""; 38 | clear: both; 39 | display: table; 40 | } 41 | 42 | .next_section_button a { 43 | overflow: hidden; 44 | float: right; 45 | border: 1px solid var(--separator-color); 46 | padding: var(--spacing-medium) calc(var(--spacing-large) / 2) var(--spacing-medium) var(--spacing-large); 47 | border-radius: var(--border-radius-medium); 48 | color: var(--page-secondary-foreground-color) !important; 49 | text-decoration: none; 50 | background-color: var(--page-background-color); 51 | transition: color .08s ease-in-out, background-color .1s ease-in-out; 52 | } 53 | 54 | .next_section_button a:hover { 55 | color: var(--page-foreground-color) !important; 56 | background-color: var(--odd-color); 57 | } 58 | 59 | .next_section_button a::after { 60 | content: '〉'; 61 | color: var(--page-secondary-foreground-color) !important; 62 | padding-left: var(--spacing-large); 63 | display: inline-block; 64 | transition: color .08s ease-in-out, transform .09s ease-in-out; 65 | } 66 | 67 | .next_section_button a:hover::after { 68 | color: var(--page-foreground-color) !important; 69 | transform: translateX(3px); 70 | } 71 | 72 | .alter-theme-button:hover { 73 | background: var(--primary-dark-color); 74 | } 75 | 76 | html.dark-mode #variants_image img { 77 | filter: brightness(87%) hue-rotate(180deg) invert(); 78 | } -------------------------------------------------------------------------------- /docs/doxygen-awesome-css/doxygen-custom/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | $projectname: $title 23 | $title 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 37 | $treeview 38 | $search 39 | $mathjax 40 | 41 | $extrastylesheet 42 | 43 | 44 | 45 | 46 | 47 | 49 | 50 | 51 |
52 | 53 | 54 |
55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 68 | 69 | 70 | 71 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 |
63 |
$projectname 64 |  $projectnumber 65 |
66 |
$projectbrief
67 |
72 |
$projectbrief
73 |
$searchbox
84 |
85 | 86 | 87 | -------------------------------------------------------------------------------- /docs/doxygen-awesome-css/doxygen-custom/toggle-alternative-theme.js: -------------------------------------------------------------------------------- 1 | 2 | let original_theme_active = true; 3 | 4 | function toggle_alternative_theme() { 5 | if(original_theme_active) { 6 | document.documentElement.classList.add("alternative") 7 | original_theme_active = false; 8 | } else { 9 | document.documentElement.classList.remove("alternative") 10 | original_theme_active = true; 11 | } 12 | } -------------------------------------------------------------------------------- /docs/doxygen-awesome-css/img/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LSPosed/LSPlt/7d609ac3c2d8faa0c830b0904024ef5c81a98e6e/docs/doxygen-awesome-css/img/screenshot.png -------------------------------------------------------------------------------- /docs/doxygen-awesome-css/img/theme-variants.drawio.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 |
11 | 1. Base Theme 12 |
13 |
14 |
15 |
16 | 17 | 1. Base Theme 18 | 19 |
20 |
21 | 22 | 23 | 24 | 25 |
26 |
27 |
28 | 2. Sidebar-Only Theme 29 |
30 |
31 |
32 |
33 | 34 | 2. Sidebar-Only Theme 35 | 36 |
37 |
38 | 39 | 40 | 41 | 42 | 43 |
44 |
45 |
46 | Content 47 |
48 |
49 |
50 |
51 | 52 | Content 53 | 54 |
55 |
56 | 57 | 58 | 59 | 60 |
61 |
62 |
63 | Sidebar 64 |
65 | (Title + Navigation) 66 |
67 |
68 |
69 |
70 | 71 | Sidebar... 72 | 73 |
74 |
75 | 76 | 77 | 78 | 79 |
80 |
81 |
82 | Footer (Breadcrumps) 83 |
84 |
85 |
86 |
87 | 88 | Footer (Breadcrumps) 89 | 90 |
91 |
92 | 93 | 94 | 95 | 96 |
97 |
98 |
99 | Search 100 |
101 |
102 |
103 |
104 | 105 | Search 106 | 107 |
108 |
109 | 110 | 111 | 112 |
113 |
114 |
115 | 116 | Title 117 | 118 |
119 |
120 |
121 |
122 | 123 | Tit... 124 | 125 |
126 |
127 | 128 | 129 | 130 | 131 | 132 |
133 |
134 |
135 | Content 136 |
137 |
138 |
139 |
140 | 141 | Content 142 | 143 |
144 |
145 | 146 | 147 | 148 | 149 |
150 |
151 |
152 | Titlebar (Navigation + Search) 153 |
154 |
155 |
156 |
157 | 158 | Titlebar (Navigation + Search) 159 | 160 |
161 |
162 | 163 | 164 | 165 | 166 |
167 |
168 |
169 | Sidebar (Navigation) 170 |
171 |
172 |
173 |
174 | 175 | Sidebar (Navigation) 176 | 177 |
178 |
179 | 180 | 181 | 182 | 183 |
184 |
185 |
186 | Footer (Breadcrumps) 187 |
188 |
189 |
190 |
191 | 192 | Footer (Breadcrumps) 193 | 194 |
195 |
196 | 197 | 198 | 199 | 200 |
201 |
202 |
203 | Search 204 |
205 |
206 |
207 |
208 | 209 | Search 210 | 211 |
212 |
213 | 214 | 215 | 216 |
217 |
218 |
219 | 220 | Title 221 | 222 |
223 |
224 |
225 |
226 | 227 | Tit... 228 | 229 |
230 |
231 |
232 | 233 | 234 | 235 | 236 | Viewer does not support full SVG 1.1 237 | 238 | 239 | 240 |
-------------------------------------------------------------------------------- /docs/doxygen-awesome-css/include/MyLibrary/example.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace MyLibrary { 5 | 6 | enum Color { red, green, blue }; 7 | 8 | /** 9 | * @brief Example class to demonstrate the features of the custom CSS. 10 | * 11 | * @author jothepro 12 | * 13 | */ 14 | class Example { 15 | public: 16 | /** 17 | * @brief brief summary 18 | * 19 | * doxygen test documentation 20 | * 21 | * @param test this is the only parameter of this test function. It does nothing! 22 | * 23 | * # Supported elements 24 | * 25 | * These elements have been tested with the custom CSS. 26 | * 27 | * ## Tables 28 | * 29 | * The table content is scrollable if the table gets too wide. 30 | * 31 | * | first_column | second_column | third_column | fourth_column | fifth_column | sixth_column | seventh_column | eighth_column | ninth_column | 32 | * |--------------|---------------|--------------|---------------|--------------|--------------|----------------|---------------|--------------| 33 | * | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 34 | * 35 | * 36 | * ## Lists 37 | * 38 | * - element 1 39 | * - element 2 40 | * 41 | * 1. element 1 42 | * ``` 43 | * code in lists 44 | * ``` 45 | * 2. element 2 46 | * 47 | * ## Quotes 48 | * 49 | * > Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt 50 | * > ut labore et dolore magna aliqua. Vitae proin sagittis nisl rhoncus mattis rhoncus urna neque viverra. 51 | * > Velit sed ullamcorper morbi tincidunt ornare. 52 | * > 53 | * > Lorem ipsum dolor sit amet consectetur adipiscing elit duis. 54 | * *- jothepro* 55 | * 56 | * ## Code block 57 | * 58 | * ```cpp 59 | * auto x = "code within md fences (```)"; 60 | * ``` 61 | * 62 | * @code{.cpp} 63 | * // code within @code block 64 | * while(true) { 65 | * auto example = std::make_shared(5); 66 | * example->test("test"); 67 | * } 68 | * 69 | * @endcode 70 | * 71 | * // code within indented code block 72 | * auto test = std::shared_ptr 3 | #include "example.hpp" 4 | #include 5 | 6 | namespace MyLibrary { 7 | 8 | /** 9 | * @brief some subclass 10 | */ 11 | class SubclassExample : public Example { 12 | public: 13 | 14 | /** 15 | * @bug second bug 16 | * @return 17 | */ 18 | int virtualfunc() override; 19 | 20 | /** 21 | * @brief Extra long function with lots of parameters 22 | * @param param1 first parameter 23 | * @param param2 second parameter 24 | * @param parameter3 third parameter 25 | */ 26 | template 27 | std::shared_ptr long_function_with_many_parameters(std::shared_ptr& param1, std::shared_ptr& param2, bool parameter3) { 28 | if(true) { 29 | std::cout << "this even has some code." << std::endl; 30 | } 31 | } 32 | 33 | }; 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /docs/doxygen-awesome-css/logo.drawio.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | android.nonTransitiveRClass=true 2 | android.useAndroidX=true 3 | android.experimental.testOptions.managedDevices.allowOldApiLevelDevices=true 4 | android.library.defaults.buildfeatures.androidresources=false 5 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LSPosed/LSPlt/7d609ac3c2d8faa0c830b0904024ef5c81a98e6e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 1>&2 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 48 | echo. 1>&2 49 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 50 | echo location of your Java installation. 1>&2 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 1>&2 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 62 | echo. 1>&2 63 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 64 | echo location of your Java installation. 1>&2 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /lsplt/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /libs 3 | /obj 4 | /release 5 | 6 | -------------------------------------------------------------------------------- /lsplt/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import java.nio.file.Paths 2 | import org.eclipse.jgit.api.Git 3 | import org.eclipse.jgit.storage.file.FileRepositoryBuilder 4 | 5 | plugins { 6 | id("com.android.library") 7 | id("maven-publish") 8 | id("signing") 9 | } 10 | 11 | buildscript { 12 | repositories { 13 | mavenCentral() 14 | } 15 | dependencies { 16 | classpath("org.eclipse.jgit:org.eclipse.jgit:6.8.0.202311291450-r") 17 | } 18 | } 19 | 20 | val androidTargetSdkVersion: Int by rootProject.extra 21 | val androidMinSdkVersion: Int by rootProject.extra 22 | val androidBuildToolsVersion: String by rootProject.extra 23 | val androidCompileSdkVersion: Int by rootProject.extra 24 | val androidNdkVersion: String by rootProject.extra 25 | val androidCmakeVersion: String by rootProject.extra 26 | 27 | fun findInPath(executable: String): String? { 28 | val pathEnv = System.getenv("PATH") 29 | return pathEnv.split(File.pathSeparator).map { folder -> 30 | Paths.get("${folder}${File.separator}${executable}${if (org.gradle.internal.os.OperatingSystem.current().isWindows) ".exe" else ""}") 31 | .toFile() 32 | }.firstOrNull { path -> 33 | path.exists() 34 | }?.absolutePath 35 | } 36 | 37 | android { 38 | compileSdk = androidCompileSdkVersion 39 | ndkVersion = androidNdkVersion 40 | buildToolsVersion = androidBuildToolsVersion 41 | 42 | buildFeatures { 43 | buildConfig = false 44 | prefabPublishing = true 45 | androidResources = false 46 | prefab = true 47 | } 48 | 49 | packaging { 50 | jniLibs { 51 | excludes += "**.so" 52 | } 53 | } 54 | 55 | prefab { 56 | register("lsplt") { 57 | headers = "src/main/jni/include" 58 | } 59 | } 60 | 61 | defaultConfig { 62 | minSdk = androidMinSdkVersion 63 | targetSdk = androidTargetSdkVersion 64 | } 65 | 66 | compileOptions { 67 | sourceCompatibility = JavaVersion.VERSION_17 68 | targetCompatibility = JavaVersion.VERSION_17 69 | } 70 | 71 | buildTypes { 72 | all { 73 | externalNativeBuild { 74 | cmake { 75 | abiFilters("arm64-v8a", "armeabi-v7a", "x86", "x86_64") 76 | val flags = arrayOf( 77 | "-Wall", 78 | "-Werror", 79 | "-Qunused-arguments", 80 | "-Wno-gnu-string-literal-operator-template", 81 | "-fno-rtti", 82 | "-fvisibility=hidden", 83 | "-fvisibility-inlines-hidden", 84 | "-fno-exceptions", 85 | "-fno-stack-protector", 86 | "-fomit-frame-pointer", 87 | "-Wno-builtin-macro-redefined", 88 | "-ffunction-sections", 89 | "-fdata-sections", 90 | "-Wno-unused-value", 91 | "-D__FILE__=__FILE_NAME__", 92 | "-Wl,--exclude-libs,ALL", 93 | ) 94 | cppFlags("-std=c++20", *flags) 95 | cFlags("-std=c18", *flags) 96 | val configFlags = arrayOf( 97 | "-Oz", 98 | "-DNDEBUG" 99 | ).joinToString(" ") 100 | arguments( 101 | "-DCMAKE_CXX_FLAGS_RELEASE=$configFlags", 102 | "-DCMAKE_C_FLAGS_RELEASE=$configFlags", 103 | "-DDEBUG_SYMBOLS_PATH=${project.buildDir.absolutePath}/symbols/$name", 104 | "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON" 105 | ) 106 | findInPath("ccache")?.let { 107 | println("Using ccache $it") 108 | arguments += "-DANDROID_CCACHE=$it" 109 | } 110 | } 111 | } 112 | } 113 | release { 114 | externalNativeBuild { 115 | val flags = arrayOf( 116 | "-Wl,--gc-sections", 117 | "-flto", 118 | "-fno-unwind-tables", 119 | "-fno-asynchronous-unwind-tables", 120 | ) 121 | cmake { 122 | cppFlags += flags 123 | cFlags += flags 124 | arguments += "-DANDROID_STL=c++_shared" 125 | arguments += "-DCMAKE_BUILD_TYPE=Release" 126 | } 127 | } 128 | } 129 | debug { 130 | externalNativeBuild { 131 | cmake { 132 | arguments += "-DANDROID_STL=c++_shared" 133 | } 134 | } 135 | } 136 | create("standalone") { 137 | initWith(getByName("release")) 138 | externalNativeBuild { 139 | val flags = arrayOf( 140 | "-Wl,--gc-sections", 141 | "-flto", 142 | "-fno-unwind-tables", 143 | "-fno-asynchronous-unwind-tables", 144 | ) 145 | cmake { 146 | cppFlags += flags 147 | cFlags += flags 148 | arguments += "-DANDROID_STL=none" 149 | arguments += "-DCMAKE_BUILD_TYPE=Release" 150 | arguments += "-DLSPLT_STANDALONE=ON" 151 | } 152 | } 153 | } 154 | } 155 | 156 | lint { 157 | abortOnError = true 158 | checkReleaseBuilds = false 159 | } 160 | 161 | externalNativeBuild { 162 | cmake { 163 | path = file("src/main/jni/CMakeLists.txt") 164 | version = androidCmakeVersion 165 | } 166 | } 167 | namespace = "org.lsposed.lsplt" 168 | 169 | publishing { 170 | singleVariant("release") { 171 | withSourcesJar() 172 | withJavadocJar() 173 | } 174 | singleVariant("standalone") { 175 | withSourcesJar() 176 | withJavadocJar() 177 | } 178 | } 179 | } 180 | 181 | val symbolsReleaseTask = tasks.register("generateReleaseSymbolsJar") { 182 | from("${project.buildDir.absolutePath}/symbols/release") 183 | exclude("**/dex_builder") 184 | archiveClassifier.set("symbols") 185 | archiveBaseName.set("release") 186 | } 187 | 188 | val symbolsStandaloneTask = tasks.register("generateStandaloneSymbolsJar") { 189 | from("${project.buildDir.absolutePath}/symbols/standalone") 190 | exclude("**/dex_builder") 191 | archiveClassifier.set("symbols") 192 | archiveBaseName.set("standalone") 193 | } 194 | 195 | val ver = FileRepositoryBuilder().findGitDir(rootProject.file(".git")).runCatching { 196 | build().use { 197 | Git(it).describe().setTags(true).setAbbrev(0).call().removePrefix("v") 198 | } 199 | }.getOrNull() ?: "0.0" 200 | println("${rootProject.name} version: $ver") 201 | 202 | publishing { 203 | publications { 204 | fun MavenPublication.setup() { 205 | group = "org.lsposed.lsplt" 206 | version = ver 207 | pom { 208 | name.set("LSPlt") 209 | description.set("A plt hook framework for Android") 210 | url.set("https://github.com/LSPosed/LSPlt") 211 | licenses { 212 | license { 213 | name.set("GNU Lesser General Public License v3.0") 214 | url.set("https://github.com/LSPosed/LSPlt/blob/master/LICENSE") 215 | } 216 | } 217 | developers { 218 | developer { 219 | name.set("Lsposed") 220 | url.set("https://lsposed.org") 221 | } 222 | } 223 | scm { 224 | connection.set("scm:git:https://github.com/LSPosed/LSPlt.git") 225 | url.set("https://github.com/LSPosed/LSPlt") 226 | } 227 | } 228 | } 229 | register("lsplt") { 230 | artifactId = "lsplt" 231 | afterEvaluate { 232 | from(components.getByName("release")) 233 | artifact(symbolsReleaseTask) 234 | } 235 | setup() 236 | } 237 | register("lspltStandalone") { 238 | artifactId = "lsplt-standalone" 239 | afterEvaluate { 240 | from(components.getByName("standalone")) 241 | artifact(symbolsStandaloneTask) 242 | } 243 | setup() 244 | } 245 | } 246 | repositories { 247 | maven { 248 | name = "ossrh" 249 | url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/") 250 | credentials(PasswordCredentials::class) 251 | } 252 | maven { 253 | name = "GitHubPackages" 254 | url = uri("https://maven.pkg.github.com/LSPosed/LSPlt") 255 | credentials { 256 | username = System.getenv("GITHUB_ACTOR") 257 | password = System.getenv("GITHUB_TOKEN") 258 | } 259 | } 260 | } 261 | dependencies { 262 | "standaloneCompileOnly"("dev.rikka.ndk.thirdparty:cxx:1.2.0") 263 | } 264 | } 265 | 266 | signing { 267 | val signingKey = findProperty("signingKey") as String? 268 | val signingPassword = findProperty("signingPassword") as String? 269 | if (signingKey != null && signingPassword != null) { 270 | useInMemoryPgpKeys(signingKey, signingPassword) 271 | } 272 | sign(publishing.publications) 273 | } 274 | -------------------------------------------------------------------------------- /lsplt/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /lsplt/src/main/jni/.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Google 3 | IndentWidth: 4 4 | UseCRLF: false 5 | UseTab: false 6 | --- 7 | Language: Cpp 8 | DerivePointerAlignment: true 9 | PointerAlignment: Right 10 | ColumnLimit: 100 11 | AlignEscapedNewlines: Right 12 | Cpp11BracedListStyle: true 13 | Standard: Latest 14 | # IndentAccessModifiers: false 15 | IndentCaseLabels: false 16 | BreakStringLiterals: false 17 | IndentExternBlock: false 18 | AccessModifierOffset: -4 19 | # EmptyLineBeforeAccessModifier: true 20 | -------------------------------------------------------------------------------- /lsplt/src/main/jni/.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: > 3 | -*, 4 | bugprone-*, 5 | google-*, 6 | misc-*, 7 | modernize-*, 8 | performance-*, 9 | portability-*, 10 | readability-*, 11 | clang-analyzer-*, 12 | llvm-include-order, 13 | -modernize-use-trailing-return-type, 14 | -readability-implicit-bool-conversion, 15 | -performance-no-int-to-ptr, 16 | 17 | CheckOptions: 18 | - key: readability-identifier-naming.ClassCase 19 | value: CamelCase 20 | - key: readability-identifier-naming.ClassMemberCase 21 | value: lower_case 22 | - key: readability-identifier-naming.EnumCase 23 | value: CamelCase 24 | - key: readability-identifier-naming.EnumConstantCase 25 | value: CamelCase 26 | - key: readability-identifier-naming.EnumConstantPrefix 27 | value: k 28 | - key: readability-identifier-naming.FunctionCase 29 | value: CamelCase 30 | - key: readability-identifier-naming.GlobalConstantCase 31 | value: CamelCase 32 | - key: readability-identifier-naming.GlobalConstantPrefix 33 | value: k 34 | - key: readability-identifier-naming.StaticConstantCase 35 | value: CamelCase 36 | - key: readability-identifier-naming.StaticConstantPrefix 37 | value: k 38 | - key: readability-identifier-naming.StaticVariableCase 39 | value: CamelCase 40 | - key: readability-identifier-naming.StaticVariablePrefix 41 | value: k 42 | - key: readability-identifier-naming.MacroDefinitionCase 43 | value: UPPER_CASE 44 | - key: readability-identifier-naming.MemberCase 45 | value: lower_case 46 | - key: readability-identifier-naming.PrivateMemberSuffix 47 | value: _ 48 | - key: readability-identifier-naming.ProtectedMemberSuffix 49 | value: _ 50 | - key: readability-identifier-naming.NamespaceCase 51 | value: lower_case 52 | - key: readability-identifier-naming.ParameterCase 53 | value: lower_case 54 | - key: readability-identifier-naming.TypeAliasCase 55 | value: CamelCase 56 | - key: readability-identifier-naming.TypedefCase 57 | value: CamelCase 58 | - key: readability-identifier-naming.VariableCase 59 | value: lower_case 60 | - key: readability-identifier-naming.IgnoreMainLikeFunctions 61 | value: 1 62 | - key: readability-braces-around-statements.ShortStatementLines 63 | value: 1 64 | - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic 65 | value: '1' 66 | -------------------------------------------------------------------------------- /lsplt/src/main/jni/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4.1) 2 | project(lsplt) 3 | 4 | find_program(CCACHE ccache) 5 | 6 | if (CCACHE) 7 | set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE}) 8 | set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE}) 9 | endif () 10 | 11 | if (LSPLT_STANDALONE) 12 | find_package(cxx REQUIRED CONFIG) 13 | link_libraries(cxx::cxx) 14 | endif() 15 | 16 | add_definitions(-std=c++20) 17 | 18 | set(SOURCES lsplt.cc elf_util.cc) 19 | 20 | option(LSPLT_BUILD_SHARED "If ON, lsplt will also build shared library" ON) 21 | 22 | if (LSPLT_BUILD_SHARED) 23 | message(STATUS "Building lsplt as a shared library") 24 | add_library(${PROJECT_NAME} SHARED ${SOURCES}) 25 | target_include_directories(${PROJECT_NAME} PUBLIC include) 26 | target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) 27 | target_compile_options(${PROJECT_NAME} PRIVATE -flto) 28 | target_link_options(${PROJECT_NAME} PRIVATE -flto) 29 | add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD 30 | COMMAND ${CMAKE_COMMAND} -E make_directory ${DEBUG_SYMBOLS_PATH}/${ANDROID_ABI} 31 | COMMAND ${CMAKE_OBJCOPY} --only-keep-debug $ 32 | ${DEBUG_SYMBOLS_PATH}/${ANDROID_ABI}/${PROJECT_NAME} 33 | COMMAND ${CMAKE_STRIP} --strip-all $) 34 | 35 | target_link_libraries(${PROJECT_NAME} PUBLIC log) 36 | endif() 37 | 38 | add_library(${PROJECT_NAME}_static STATIC ${SOURCES}) 39 | target_include_directories(${PROJECT_NAME}_static PUBLIC include) 40 | target_include_directories(${PROJECT_NAME}_static PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) 41 | 42 | if (NOT DEFINED DEBUG_SYMBOLS_PATH) 43 | set(DEBUG_SYMBOLS_PATH ${CMAKE_BINARY_DIR}/symbols) 44 | endif() 45 | 46 | target_link_libraries(${PROJECT_NAME}_static PUBLIC log) 47 | -------------------------------------------------------------------------------- /lsplt/src/main/jni/elf_util.cc: -------------------------------------------------------------------------------- 1 | #include "elf_util.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #if defined(__arm__) 9 | #define ELF_R_GENERIC_JUMP_SLOT R_ARM_JUMP_SLOT //.rel.plt 10 | #define ELF_R_GENERIC_GLOB_DAT R_ARM_GLOB_DAT //.rel.dyn 11 | #define ELF_R_GENERIC_ABS R_ARM_ABS32 //.rel.dyn 12 | #elif defined(__aarch64__) 13 | #define ELF_R_GENERIC_JUMP_SLOT R_AARCH64_JUMP_SLOT 14 | #define ELF_R_GENERIC_GLOB_DAT R_AARCH64_GLOB_DAT 15 | #define ELF_R_GENERIC_ABS R_AARCH64_ABS64 16 | #elif defined(__i386__) 17 | #define ELF_R_GENERIC_JUMP_SLOT R_386_JMP_SLOT 18 | #define ELF_R_GENERIC_GLOB_DAT R_386_GLOB_DAT 19 | #define ELF_R_GENERIC_ABS R_386_32 20 | #elif defined(__x86_64__) 21 | #define ELF_R_GENERIC_JUMP_SLOT R_X86_64_JUMP_SLOT 22 | #define ELF_R_GENERIC_GLOB_DAT R_X86_64_GLOB_DAT 23 | #define ELF_R_GENERIC_ABS R_X86_64_64 24 | #elif defined(__riscv) 25 | #define ELF_R_GENERIC_JUMP_SLOT R_RISCV_JUMP_SLOT 26 | #define ELF_R_GENERIC_GLOB_DAT R_RISCV_64 27 | #define ELF_R_GENERIC_ABS R_RISCV_64 28 | #endif 29 | 30 | #if defined(__LP64__) 31 | #define ELF_R_SYM(info) ELF64_R_SYM(info) 32 | #define ELF_R_TYPE(info) ELF64_R_TYPE(info) 33 | #else 34 | #define ELF_R_SYM(info) ELF32_R_SYM(info) 35 | #define ELF_R_TYPE(info) ELF32_R_TYPE(info) 36 | #endif 37 | 38 | namespace { 39 | template 40 | inline constexpr auto OffsetOf(ElfW(Ehdr) * head, ElfW(Off) off) { 41 | return reinterpret_cast, T, T *>>( 42 | reinterpret_cast(head) + off); 43 | } 44 | 45 | template 46 | inline constexpr auto SetByOffset(T &ptr, ElfW(Addr) base, ElfW(Addr) bias, ElfW(Addr) off) { 47 | if (auto val = bias + off; val > base) { 48 | ptr = reinterpret_cast(val); 49 | return true; 50 | } 51 | ptr = 0; 52 | return false; 53 | } 54 | 55 | } // namespace 56 | 57 | Elf::Elf(uintptr_t base_addr) : base_addr_(base_addr) { 58 | header_ = reinterpret_cast(base_addr); 59 | 60 | // check magic 61 | if (0 != memcmp(header_->e_ident, ELFMAG, SELFMAG)) return; 62 | 63 | // check class (64/32) 64 | #if defined(__LP64__) 65 | if (ELFCLASS64 != header_->e_ident[EI_CLASS]) return; 66 | #else 67 | if (ELFCLASS32 != header_->e_ident[EI_CLASS]) return; 68 | #endif 69 | 70 | // check endian (little/big) 71 | if (ELFDATA2LSB != header_->e_ident[EI_DATA]) return; 72 | 73 | // check version 74 | if (EV_CURRENT != header_->e_ident[EI_VERSION]) return; 75 | 76 | // check type 77 | if (ET_EXEC != header_->e_type && ET_DYN != header_->e_type) return; 78 | 79 | // check machine 80 | #if defined(__arm__) 81 | if (EM_ARM != header_->e_machine) return; 82 | #elif defined(__aarch64__) 83 | if (EM_AARCH64 != header_->e_machine) return; 84 | #elif defined(__i386__) 85 | if (EM_386 != header_->e_machine) return; 86 | #elif defined(__x86_64__) 87 | if (EM_X86_64 != header_->e_machine) return; 88 | #elif defined(__riscv) 89 | if (EM_RISCV != header_->e_machine) return; 90 | #else 91 | return; 92 | #endif 93 | 94 | // check version 95 | if (EV_CURRENT != header_->e_version) return; 96 | 97 | program_header_ = OffsetOf(header_, header_->e_phoff); 98 | 99 | auto ph_off = reinterpret_cast(program_header_); 100 | for (int i = 0; i < header_->e_phnum; i++, ph_off += header_->e_phentsize) { 101 | auto *program_header = reinterpret_cast(ph_off); 102 | if (program_header->p_type == PT_LOAD && program_header->p_offset == 0) { 103 | if (base_addr_ >= program_header->p_vaddr) { 104 | bias_addr_ = base_addr_ - program_header->p_vaddr; 105 | } 106 | } else if (program_header->p_type == PT_DYNAMIC) { 107 | dynamic_ = reinterpret_cast(program_header->p_vaddr); 108 | dynamic_size_ = program_header->p_memsz; 109 | } 110 | } 111 | if (!dynamic_ || !bias_addr_) return; 112 | dynamic_ = 113 | reinterpret_cast(bias_addr_ + reinterpret_cast(dynamic_)); 114 | 115 | for (auto *dynamic = dynamic_, *dynamic_end = dynamic_ + (dynamic_size_ / sizeof(dynamic[0])); 116 | dynamic < dynamic_end; ++dynamic) { 117 | switch (dynamic->d_tag) { 118 | case DT_NULL: 119 | // the end of the dynamic-section 120 | dynamic = dynamic_end; 121 | break; 122 | case DT_STRTAB: { 123 | if (!SetByOffset(dyn_str_, base_addr_, bias_addr_, dynamic->d_un.d_ptr)) return; 124 | break; 125 | } 126 | case DT_SYMTAB: { 127 | if (!SetByOffset(dyn_sym_, base_addr_, bias_addr_, dynamic->d_un.d_ptr)) return; 128 | break; 129 | } 130 | case DT_PLTREL: 131 | // use rel or rela? 132 | is_use_rela_ = dynamic->d_un.d_val == DT_RELA; 133 | break; 134 | case DT_JMPREL: { 135 | if (!SetByOffset(rel_plt_, base_addr_, bias_addr_, dynamic->d_un.d_ptr)) return; 136 | break; 137 | } 138 | case DT_PLTRELSZ: 139 | rel_plt_size_ = dynamic->d_un.d_val; 140 | break; 141 | case DT_REL: 142 | case DT_RELA: { 143 | if (!SetByOffset(rel_dyn_, base_addr_, bias_addr_, dynamic->d_un.d_ptr)) return; 144 | break; 145 | } 146 | case DT_RELSZ: 147 | case DT_RELASZ: 148 | rel_dyn_size_ = dynamic->d_un.d_val; 149 | break; 150 | case DT_ANDROID_REL: 151 | case DT_ANDROID_RELA: { 152 | if (!SetByOffset(rel_android_, base_addr_, bias_addr_, dynamic->d_un.d_ptr)) return; 153 | break; 154 | } 155 | case DT_ANDROID_RELSZ: 156 | case DT_ANDROID_RELASZ: 157 | rel_android_size_ = dynamic->d_un.d_val; 158 | break; 159 | case DT_HASH: { 160 | // ignore DT_HASH when ELF contains DT_GNU_HASH hash table 161 | if (bloom_) continue; 162 | auto *raw = reinterpret_cast(bias_addr_ + dynamic->d_un.d_ptr); 163 | bucket_count_ = raw[0]; 164 | bucket_ = raw + 2; 165 | chain_ = bucket_ + bucket_count_; 166 | break; 167 | } 168 | case DT_GNU_HASH: { 169 | auto *raw = reinterpret_cast(bias_addr_ + dynamic->d_un.d_ptr); 170 | bucket_count_ = raw[0]; 171 | sym_offset_ = raw[1]; 172 | bloom_size_ = raw[2]; 173 | bloom_shift_ = raw[3]; 174 | bloom_ = reinterpret_cast(raw + 4); 175 | bucket_ = reinterpret_cast(bloom_ + bloom_size_); 176 | chain_ = bucket_ + bucket_count_ - sym_offset_; 177 | // is_use_gnu_hash_ = true; 178 | break; 179 | } 180 | default: 181 | break; 182 | } 183 | } 184 | 185 | // check android rel/rela 186 | if (0 != rel_android_) { 187 | const auto *rel = reinterpret_cast(rel_android_); 188 | if (rel_android_size_ < 4 || rel[0] != 'A' || rel[1] != 'P' || rel[2] != 'S' || 189 | rel[3] != '2') { 190 | return; 191 | } 192 | 193 | rel_android_ += 4; 194 | rel_android_size_ -= 4; 195 | } 196 | 197 | valid_ = true; 198 | } 199 | 200 | uint32_t Elf::GnuLookup(std::string_view name) const { 201 | static constexpr auto kBloomMaskBits = sizeof(ElfW(Addr)) * 8; 202 | static constexpr uint32_t kInitialHash = 5381; 203 | static constexpr uint32_t kHashShift = 5; 204 | 205 | if (!bucket_ || !bloom_) return 0; 206 | 207 | uint32_t hash = kInitialHash; 208 | for (unsigned char chr : name) { 209 | hash += (hash << kHashShift) + chr; 210 | } 211 | 212 | auto bloom_word = bloom_[(hash / kBloomMaskBits) % bloom_size_]; 213 | uintptr_t mask = 0 | uintptr_t{1} << (hash % kBloomMaskBits) | 214 | uintptr_t{1} << ((hash >> bloom_shift_) % kBloomMaskBits); 215 | if ((mask & bloom_word) == mask) { 216 | auto idx = bucket_[hash % bucket_count_]; 217 | if (idx >= sym_offset_) { 218 | const char *strings = dyn_str_; 219 | do { 220 | auto *sym = dyn_sym_ + idx; 221 | if (((chain_[idx] ^ hash) >> 1) == 0 && name == strings + sym->st_name) { 222 | return idx; 223 | } 224 | } while ((chain_[idx++] & 1) == 0); 225 | } 226 | } 227 | return 0; 228 | } 229 | 230 | uint32_t Elf::ElfLookup(std::string_view name) const { 231 | static constexpr uint32_t kHashMask = 0xf0000000; 232 | static constexpr uint32_t kHashShift = 24; 233 | uint32_t hash = 0; 234 | uint32_t tmp; 235 | 236 | if (!bucket_ || bloom_) return 0; 237 | 238 | for (unsigned char chr : name) { 239 | hash = (hash << 4) + chr; 240 | tmp = hash & kHashMask; 241 | hash ^= tmp; 242 | hash ^= tmp >> kHashShift; 243 | } 244 | const char *strings = dyn_str_; 245 | 246 | for (auto idx = bucket_[hash % bucket_count_]; idx != 0; idx = chain_[idx]) { 247 | auto *sym = dyn_sym_ + idx; 248 | if (name == strings + sym->st_name) { 249 | return idx; 250 | } 251 | } 252 | return 0; 253 | } 254 | 255 | uint32_t Elf::LinearLookup(std::string_view name) const { 256 | if (!dyn_sym_ || !sym_offset_) return 0; 257 | for (uint32_t idx = 0; idx < sym_offset_; idx++) { 258 | auto *sym = dyn_sym_ + idx; 259 | if (name == dyn_str_ + sym->st_name) { 260 | return idx; 261 | } 262 | } 263 | return 0; 264 | } 265 | 266 | std::vector Elf::FindPltAddr(std::string_view name) const { 267 | std::vector res; 268 | 269 | uint32_t idx = GnuLookup(name); 270 | if (!idx) idx = ElfLookup(name); 271 | if (!idx) idx = LinearLookup(name); 272 | if (!idx) return res; 273 | 274 | auto looper = [&](auto begin, auto size, bool is_plt) -> void { 275 | const auto *rel_end = reinterpret_cast(begin + size); 276 | for (const auto *rel = reinterpret_cast(begin); rel < rel_end; ++rel) { 277 | auto r_info = rel->r_info; 278 | auto r_offset = rel->r_offset; 279 | auto r_sym = ELF_R_SYM(r_info); 280 | auto r_type = ELF_R_TYPE(r_info); 281 | if (r_sym != idx) continue; 282 | if (is_plt && r_type != ELF_R_GENERIC_JUMP_SLOT) continue; 283 | if (!is_plt && r_type != ELF_R_GENERIC_ABS && r_type != ELF_R_GENERIC_GLOB_DAT) { 284 | continue; 285 | } 286 | auto addr = bias_addr_ + r_offset; 287 | if (addr > base_addr_) res.emplace_back(addr); 288 | if (is_plt) break; 289 | } 290 | }; 291 | 292 | for (const auto &[rel, rel_size, is_plt] : 293 | {std::make_tuple(rel_plt_, rel_plt_size_, true), 294 | std::make_tuple(rel_dyn_, rel_dyn_size_, false), 295 | std::make_tuple(rel_android_, rel_android_size_, false)}) { 296 | if (!rel) continue; 297 | if (is_use_rela_) { 298 | looper.template operator()(rel, rel_size, is_plt); 299 | } else { 300 | looper.template operator()(rel, rel_size, is_plt); 301 | } 302 | } 303 | 304 | return res; 305 | } 306 | -------------------------------------------------------------------------------- /lsplt/src/main/jni/elf_util.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | class Elf { 8 | ElfW(Addr) base_addr_ = 0; 9 | ElfW(Addr) bias_addr_ = 0; 10 | 11 | ElfW(Ehdr) *header_ = nullptr; 12 | ElfW(Phdr) *program_header_ = nullptr; 13 | 14 | ElfW(Dyn) *dynamic_ = nullptr; //.dynamic 15 | ElfW(Word) dynamic_size_ = 0; 16 | 17 | const char *dyn_str_ = nullptr; //.dynstr (string-table) 18 | ElfW(Sym) *dyn_sym_ = nullptr; //.dynsym (symbol-index to string-table's offset) 19 | 20 | ElfW(Addr) rel_plt_ = 0; //.rel.plt or .rela.plt 21 | ElfW(Word) rel_plt_size_ = 0; 22 | 23 | ElfW(Addr) rel_dyn_ = 0; //.rel.dyn or .rela.dyn 24 | ElfW(Word) rel_dyn_size_ = 0; 25 | 26 | ElfW(Addr) rel_android_ = 0; // android compressed rel or rela 27 | ElfW(Word) rel_android_size_ = 0; 28 | 29 | // for ELF hash 30 | uint32_t *bucket_ = nullptr; 31 | uint32_t bucket_count_ = 0; 32 | uint32_t *chain_ = nullptr; 33 | 34 | // append for GNU hash 35 | uint32_t sym_offset_ = 0; 36 | ElfW(Addr) *bloom_ = nullptr; 37 | uint32_t bloom_size_ = 0; 38 | uint32_t bloom_shift_ = 0; 39 | 40 | bool is_use_rela_ = false; 41 | bool valid_ = false; 42 | 43 | uint32_t GnuLookup(std::string_view name) const; 44 | uint32_t ElfLookup(std::string_view name) const; 45 | uint32_t LinearLookup(std::string_view name) const; 46 | public: 47 | std::vector FindPltAddr(std::string_view name) const; 48 | Elf(uintptr_t base_addr); 49 | bool Valid() const { return valid_; }; 50 | }; 51 | -------------------------------------------------------------------------------- /lsplt/src/main/jni/include/lsplt.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | /// \namespace lsplt 10 | namespace lsplt { 11 | inline namespace v2 { 12 | 13 | /// \struct MapInfo 14 | /// \brief An entry that describes a line in /proc/self/maps. You can obtain a list of these entries 15 | /// by calling #Scan(). 16 | struct MapInfo { 17 | /// \brief The start address of the memory region. 18 | uintptr_t start; 19 | /// \brief The end address of the memory region. 20 | uintptr_t end; 21 | /// \brief The permissions of the memory region. This is a bit mask of the following values: 22 | /// - PROT_READ 23 | /// - PROT_WRITE 24 | /// - PROT_EXEC 25 | uint8_t perms; 26 | /// \brief Whether the memory region is private. 27 | bool is_private; 28 | /// \brief The offset of the memory region. 29 | uintptr_t offset; 30 | /// \brief The device number of the memory region. 31 | /// Major can be obtained by #major() 32 | /// Minor can be obtained by #minor() 33 | dev_t dev; 34 | /// \brief The inode number of the memory region. 35 | ino_t inode; 36 | /// \brief The path of the memory region. 37 | std::string path; 38 | 39 | /// \brief Scans /proc/self/maps and returns a list of \ref MapInfo entries. 40 | /// This is useful to find out the inode of the library to hook. 41 | /// \param[in] pid The process id to scan. This is "self" by default. 42 | /// \return A list of \ref MapInfo entries. 43 | [[maybe_unused, gnu::visibility("default")]] static std::vector Scan(std::string_view pid = "self"); 44 | }; 45 | 46 | /// \brief Register a hook to a function by inode. For so within an archive, you should use 47 | /// #RegisterHook(ino_t, uintptr_t, size_t, std::string_view, void *, void **) instead. 48 | /// \param[in] dev The device number of the memory region. 49 | /// \param[in] inode The inode of the library to hook. You can obtain the inode by #stat() or by finding 50 | /// the library in the list returned by #lsplt::v1::MapInfo::Scan(). 51 | /// \param[in] symbol The function symbol to hook. 52 | /// \param[in] callback The callback function pointer to call when the function is called. 53 | /// \param[out] backup The backup function pointer which can call the original function. This is 54 | /// optional. 55 | /// \return Whether the hook is successfully registered. 56 | /// \note This function is thread-safe. 57 | /// \note \p backup will not be available until #CommitHook() is called. 58 | /// \note \p backup will be nullptr if the hook fails. 59 | /// \note You can unhook the function by calling this function with \p callback set to the backup 60 | /// set by previous call. 61 | /// \note LSPlt will backup the hook memory region and restore it when the 62 | /// hook is restored to its original function pointer so that there won't be dirty pages. LSPlt will 63 | /// do hooks on a copied memory region so that the original memory region will not be modified. You 64 | /// can invalidate this behaviour and hook the original memory region by calling 65 | /// #InvalidateBackup(). 66 | /// \see #CommitHook() 67 | /// \see #InvalidateBackup() 68 | [[maybe_unused, gnu::visibility("default")]] bool RegisterHook(dev_t dev, ino_t inode, std::string_view symbol, 69 | void *callback, void **backup); 70 | 71 | /// \brief Register a hook to a function by inode with offset range. This is useful when hooking 72 | /// a library that is directly loaded from an archive without extraction. 73 | /// \param[in] dev The device number of the memory region. 74 | /// \param[in] inode The inode of the library to hook. You can obtain the inode by #stat() or by finding 75 | /// the library in the list returned by #lsplt::v1::MapInfo::Scan(). 76 | /// \param[in] offset The to the library in the file. 77 | /// \param[in] size The upper bound size to the library in the file. 78 | /// \param[in] symbol The function symbol to hook. 79 | /// \param[in] callback The callback function pointer to call when the function is called. 80 | /// \param[out] backup The backup function pointer which can call the original function. This is 81 | /// optional. 82 | /// \return Whether the hook is successfully registered. 83 | /// \note This function is thread-safe. 84 | /// \note \p backup will not be available until #CommitHook() is called. 85 | /// \note \p backup will be nullptr if the hook fails. 86 | /// \note You can unhook the function by calling this function with \p callback set to the backup 87 | /// set by previous call. 88 | /// \note LSPlt will backup the hook memory region and restore it when the 89 | /// hook is restored to its original function pointer so that there won't be dirty pages. LSPlt will 90 | /// do hooks on a copied memory region so that the original memory region will not be modified. You 91 | /// can invalidate this behaviour and hook the original memory region by calling 92 | /// #InvalidateBackup(). 93 | /// \note You can get the offset range of the library by getting its entry offset and size in the 94 | /// zip file. 95 | /// \note According to the Android linker specification, the \p offset must be page aligned. 96 | /// \note The \p offset must be accurate, otherwise the hook may fail because the ELF header 97 | /// cannot be found. 98 | /// \note The \p size can be inaccurate but should be larger or equal to the library size, 99 | /// otherwise the hook may fail when the hook pointer is beyond the range. 100 | /// \note The behaviour of this function is undefined if \p offset + \p size is larger than the 101 | /// the maximum value of \p size_t. 102 | /// \see #CommitHook() 103 | /// \see #InvalidateBackup() 104 | [[maybe_unused, gnu::visibility("default")]] bool RegisterHook(dev_t dev, ino_t inode, uintptr_t offset, 105 | size_t size, std::string_view symbol, 106 | void *callback, void **backup); 107 | /// \brief Commit all registered hooks. 108 | /// \return Whether all hooks are successfully committed. If any of the hooks fail to commit, 109 | /// the result is false. 110 | /// \note This function is thread-safe. 111 | /// \note The return value indicates whether all hooks are successfully committed. You can 112 | /// determine which hook fails by checking the backup function pointer of #RegisterHook(). 113 | /// \see #RegisterHook() 114 | [[maybe_unused, gnu::visibility("default")]] bool CommitHook(); 115 | 116 | /// \brief Invalidate backup memory regions 117 | /// Normally LSPlt will backup the hooked memory region and do hook on a copied anonymous memory 118 | /// region, and restore the original memory region when the hook is unregistered 119 | /// (when the callback of #RegisterHook() is the original function). This function will restore 120 | /// the backup memory region and do all existing hooks on the original memory region. 121 | /// \return Whether all hooks are successfully invalidated. If any of the hooks fail to invalidate, 122 | /// the result is false. 123 | /// \note This function is thread-safe. 124 | /// \note This will be automatically called when the library is unloaded. 125 | /// \see #RegisterHook() 126 | [[maybe_unused, gnu::visibility("default")]] bool InvalidateBackup(); 127 | } // namespace v2 128 | } // namespace lsplt 129 | -------------------------------------------------------------------------------- /lsplt/src/main/jni/logging.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifndef LOG_TAG 6 | #define LOG_TAG "LSPlt" 7 | #endif 8 | 9 | #ifdef LOG_DISABLED 10 | #define LOGD(...) 0 11 | #define LOGV(...) 0 12 | #define LOGI(...) 0 13 | #define LOGW(...) 0 14 | #define LOGE(...) 0 15 | #else 16 | #ifndef NDEBUG 17 | #define LOGD(fmt, ...) \ 18 | __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, \ 19 | "%s:%d#%s" \ 20 | ": " fmt, \ 21 | __FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(, ) __VA_ARGS__) 22 | #define LOGV(fmt, ...) \ 23 | __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, \ 24 | "%s:%d#%s" \ 25 | ": " fmt, \ 26 | __FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(, ) __VA_ARGS__) 27 | #else 28 | #define LOGD(...) 0 29 | #define LOGV(...) 0 30 | #endif 31 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) 32 | #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) 33 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) 34 | #define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__) 35 | #define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno)) 36 | #endif 37 | -------------------------------------------------------------------------------- /lsplt/src/main/jni/lsplt.cc: -------------------------------------------------------------------------------- 1 | #include "include/lsplt.hpp" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "elf_util.hpp" 14 | #include "logging.hpp" 15 | #include "syscall.hpp" 16 | 17 | namespace { 18 | const uintptr_t kPageSize = getpagesize(); 19 | 20 | inline auto PageStart(uintptr_t addr) { 21 | return reinterpret_cast(addr / kPageSize * kPageSize); 22 | } 23 | 24 | inline auto PageEnd(uintptr_t addr) { 25 | return reinterpret_cast(reinterpret_cast(PageStart(addr)) + kPageSize); 26 | } 27 | 28 | struct RegisterInfo { 29 | dev_t dev; 30 | ino_t inode; 31 | std::pair offset_range; 32 | std::string symbol; 33 | void *callback; 34 | void **backup; 35 | }; 36 | 37 | struct HookInfo : public lsplt::MapInfo { 38 | std::map hooks; 39 | uintptr_t backup; 40 | std::unique_ptr elf; 41 | bool self; 42 | [[nodiscard]] bool Match(const RegisterInfo &info) const { 43 | return info.dev == dev && info.inode == inode && offset >= info.offset_range.first && 44 | offset < info.offset_range.second; 45 | } 46 | }; 47 | 48 | class HookInfos : public std::map> { 49 | public: 50 | static auto ScanHookInfo() { 51 | static ino_t kSelfInode = 0; 52 | static dev_t kSelfDev = 0; 53 | HookInfos info; 54 | auto maps = lsplt::MapInfo::Scan(); 55 | if (kSelfInode == 0) { 56 | auto self = reinterpret_cast(__builtin_return_address(0)); 57 | for (auto &map : maps) { 58 | if (self >= map.start && self < map.end) { 59 | kSelfInode = map.inode; 60 | kSelfDev = map.dev; 61 | LOGV("self inode = %lu", kSelfInode); 62 | break; 63 | } 64 | } 65 | } 66 | for (auto &map : maps) { 67 | // we basically only care about r-?p entry 68 | // and for offset == 0 it's an ELF header 69 | // and for offset != 0 it's what we hook 70 | // both of them should not be xom 71 | if (!map.is_private || !(map.perms & PROT_READ) || map.path.empty() || 72 | map.path[0] == '[') { 73 | continue; 74 | } 75 | auto start = map.start; 76 | const bool self = map.inode == kSelfInode && map.dev == kSelfDev; 77 | info.emplace(start, HookInfo{{std::move(map)}, {}, 0, nullptr, self}); 78 | } 79 | return info; 80 | } 81 | 82 | // filter out ignored 83 | void Filter(const std::list ®ister_info) { 84 | for (auto iter = begin(); iter != end();) { 85 | const auto &info = iter->second; 86 | bool matched = false; 87 | for (const auto ® : register_info) { 88 | if (info.Match(reg)) { 89 | matched = true; 90 | break; 91 | } 92 | } 93 | if (matched) { 94 | LOGV("Match hook info %s:%lu %" PRIxPTR " %" PRIxPTR "-%" PRIxPTR, 95 | iter->second.path.data(), iter->second.inode, iter->second.start, 96 | iter->second.end, iter->second.offset); 97 | ++iter; 98 | } else { 99 | iter = erase(iter); 100 | } 101 | } 102 | } 103 | 104 | void Merge(HookInfos &old) { 105 | // merge with old map info 106 | for (auto &info : old) { 107 | if (info.second.backup) { 108 | erase(info.second.backup); 109 | } 110 | if (auto iter = find(info.first); iter != end()) { 111 | iter->second = std::move(info.second); 112 | } else if (info.second.backup) { 113 | emplace(info.first, std::move(info.second)); 114 | } 115 | } 116 | } 117 | 118 | bool DoHook(uintptr_t addr, uintptr_t callback, uintptr_t *backup) { 119 | LOGV("Hooking %p", reinterpret_cast(addr)); 120 | auto iter = lower_bound(addr); 121 | if (iter == end()) return false; 122 | // iter.first < addr 123 | auto &info = iter->second; 124 | if (info.end <= addr) return false; 125 | const auto len = info.end - info.start; 126 | if (!info.backup && !info.self) { 127 | // let os find a suitable address 128 | auto *backup_addr = sys_mmap(nullptr, len, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0); 129 | LOGD("Backup %p to %p", reinterpret_cast(addr), backup_addr); 130 | if (backup_addr == MAP_FAILED) return false; 131 | if (auto *new_addr = 132 | sys_mremap(reinterpret_cast(info.start), len, len, 133 | MREMAP_FIXED | MREMAP_MAYMOVE | MREMAP_DONTUNMAP, backup_addr); 134 | new_addr == MAP_FAILED || new_addr != backup_addr) { 135 | new_addr = sys_mremap(reinterpret_cast(info.start), len, len, 136 | MREMAP_FIXED | MREMAP_MAYMOVE, backup_addr); 137 | if (new_addr == MAP_FAILED || new_addr != backup_addr) { 138 | return false; 139 | } 140 | LOGD("Backup with MREMAP_DONTUNMAP failed, tried without it"); 141 | } 142 | if (auto *new_addr = sys_mmap(reinterpret_cast(info.start), len, 143 | PROT_READ | PROT_WRITE | info.perms, 144 | MAP_PRIVATE | MAP_FIXED | MAP_ANON, -1, 0); 145 | new_addr == MAP_FAILED) { 146 | return false; 147 | } 148 | for (uintptr_t src = reinterpret_cast(backup_addr), dest = info.start, 149 | end = info.start + len; 150 | dest < end; src += kPageSize, dest += kPageSize) { 151 | memcpy(reinterpret_cast(dest), reinterpret_cast(src), kPageSize); 152 | } 153 | info.backup = reinterpret_cast(backup_addr); 154 | } 155 | if (info.self) { 156 | // self hooking, no need backup since we are always dirty 157 | if (!(info.perms & PROT_WRITE)) { 158 | info.perms |= PROT_WRITE; 159 | mprotect(reinterpret_cast(info.start), len, info.perms); 160 | } 161 | } 162 | auto *the_addr = reinterpret_cast(addr); 163 | auto the_backup = *the_addr; 164 | if (*the_addr != callback) { 165 | *the_addr = callback; 166 | if (backup) *backup = the_backup; 167 | __builtin___clear_cache(PageStart(addr), PageEnd(addr)); 168 | } 169 | if (auto hook_iter = info.hooks.find(addr); hook_iter != info.hooks.end()) { 170 | if (hook_iter->second == callback) info.hooks.erase(hook_iter); 171 | } else { 172 | info.hooks.emplace(addr, the_backup); 173 | } 174 | if (info.hooks.empty() && !info.self) { 175 | LOGD("Restore %p from %p", reinterpret_cast(info.start), 176 | reinterpret_cast(info.backup)); 177 | // Note that we have to always use sys_mremap here, 178 | // see 179 | // https://cs.android.com/android/_/android/platform/bionic/+/4200e260d266fd0c176e71fbd720d0bab04b02db 180 | if (auto *new_addr = 181 | sys_mremap(reinterpret_cast(info.backup), len, len, 182 | MREMAP_FIXED | MREMAP_MAYMOVE, reinterpret_cast(info.start)); 183 | new_addr == MAP_FAILED || reinterpret_cast(new_addr) != info.start) { 184 | return false; 185 | } 186 | info.backup = 0; 187 | } 188 | return true; 189 | } 190 | 191 | bool DoHook(std::list ®ister_info) { 192 | bool res = true; 193 | for (auto info_iter = rbegin(); info_iter != rend(); ++info_iter) { 194 | auto &info = info_iter->second; 195 | for (auto iter = register_info.begin(); iter != register_info.end();) { 196 | const auto ® = *iter; 197 | if (info.offset != iter->offset_range.first || !info.Match(reg)) { 198 | ++iter; 199 | continue; 200 | } 201 | if (!info.elf) info.elf = std::make_unique(info.start); 202 | if (info.elf && info.elf->Valid()) { 203 | LOGD("Hooking %s", iter->symbol.data()); 204 | for (auto addr : info.elf->FindPltAddr(reg.symbol)) { 205 | res = DoHook(addr, reinterpret_cast(reg.callback), 206 | reinterpret_cast(reg.backup)) && 207 | res; 208 | } 209 | } 210 | iter = register_info.erase(iter); 211 | } 212 | } 213 | return res; 214 | } 215 | 216 | bool InvalidateBackup() { 217 | bool res = true; 218 | for (auto &[_, info] : *this) { 219 | if (!info.backup) continue; 220 | for (auto &[addr, backup] : info.hooks) { 221 | // store new address to backup since we don't need backup 222 | backup = *reinterpret_cast(addr); 223 | } 224 | auto len = info.end - info.start; 225 | if (auto *new_addr = 226 | mremap(reinterpret_cast(info.backup), len, len, 227 | MREMAP_FIXED | MREMAP_MAYMOVE, reinterpret_cast(info.start)); 228 | new_addr == MAP_FAILED || reinterpret_cast(new_addr) != info.start) { 229 | res = false; 230 | info.hooks.clear(); 231 | continue; 232 | } 233 | if (!mprotect(PageStart(info.start), len, PROT_WRITE)) { 234 | for (auto &[addr, backup] : info.hooks) { 235 | *reinterpret_cast(addr) = backup; 236 | } 237 | mprotect(PageStart(info.start), len, info.perms); 238 | } 239 | info.hooks.clear(); 240 | info.backup = 0; 241 | } 242 | return res; 243 | } 244 | }; 245 | 246 | std::mutex hook_mutex; 247 | std::list register_info; 248 | HookInfos hook_info; 249 | } // namespace 250 | 251 | namespace lsplt::inline v2 { 252 | [[maybe_unused]] std::vector MapInfo::Scan(std::string_view pid) { 253 | constexpr static auto kPermLength = 5; 254 | constexpr static auto kMapEntry = 7; 255 | std::vector info; 256 | auto path = "/proc/" + std::string{pid} + "/maps"; 257 | auto maps = std::unique_ptr{fopen(path.c_str(), "r"), &fclose}; 258 | if (maps) { 259 | char *line = nullptr; 260 | size_t len = 0; 261 | ssize_t read; 262 | while ((read = getline(&line, &len, maps.get())) > 0) { 263 | line[read - 1] = '\0'; 264 | uintptr_t start = 0; 265 | uintptr_t end = 0; 266 | uintptr_t off = 0; 267 | ino_t inode = 0; 268 | unsigned int dev_major = 0; 269 | unsigned int dev_minor = 0; 270 | std::array perm{'\0'}; 271 | int path_off; 272 | if (sscanf(line, "%" PRIxPTR "-%" PRIxPTR " %4s %" PRIxPTR " %x:%x %lu %n%*s", &start, 273 | &end, perm.data(), &off, &dev_major, &dev_minor, &inode, 274 | &path_off) != kMapEntry) { 275 | continue; 276 | } 277 | while (path_off < read && isspace(line[path_off])) path_off++; 278 | auto &ref = info.emplace_back(start, end, 0, perm[3] == 'p', off, 279 | static_cast(makedev(dev_major, dev_minor)), inode, 280 | line + path_off); 281 | if (perm[0] == 'r') ref.perms |= PROT_READ; 282 | if (perm[1] == 'w') ref.perms |= PROT_WRITE; 283 | if (perm[2] == 'x') ref.perms |= PROT_EXEC; 284 | } 285 | free(line); 286 | } 287 | return info; 288 | } 289 | 290 | [[maybe_unused]] bool RegisterHook(dev_t dev, ino_t inode, std::string_view symbol, void *callback, 291 | void **backup) { 292 | if (dev == 0 || inode == 0 || symbol.empty() || !callback) return false; 293 | 294 | const std::unique_lock lock(hook_mutex); 295 | static_assert(std::numeric_limits::min() == 0); 296 | static_assert(std::numeric_limits::max() == -1); 297 | [[maybe_unused]] const auto &info = register_info.emplace_back( 298 | dev, inode, 299 | std::pair{std::numeric_limits::min(), std::numeric_limits::max()}, 300 | std::string{symbol}, callback, backup); 301 | 302 | LOGV("RegisterHook %lu %s", info.inode, info.symbol.data()); 303 | return true; 304 | } 305 | 306 | [[maybe_unused]] bool RegisterHook(dev_t dev, ino_t inode, uintptr_t offset, size_t size, 307 | std::string_view symbol, void *callback, void **backup) { 308 | if (dev == 0 || inode == 0 || symbol.empty() || !callback) return false; 309 | 310 | const std::unique_lock lock(hook_mutex); 311 | static_assert(std::numeric_limits::min() == 0); 312 | static_assert(std::numeric_limits::max() == -1); 313 | [[maybe_unused]] const auto &info = register_info.emplace_back( 314 | dev, inode, std::pair{offset, offset + size}, std::string{symbol}, callback, backup); 315 | 316 | LOGV("RegisterHook %lu %" PRIxPTR "-%" PRIxPTR " %s", info.inode, info.offset_range.first, 317 | info.offset_range.second, info.symbol.data()); 318 | return true; 319 | } 320 | 321 | [[maybe_unused]] bool CommitHook() { 322 | const std::unique_lock lock(hook_mutex); 323 | if (register_info.empty()) return true; 324 | 325 | auto new_hook_info = HookInfos::ScanHookInfo(); 326 | if (new_hook_info.empty()) return false; 327 | 328 | new_hook_info.Filter(register_info); 329 | 330 | new_hook_info.Merge(hook_info); 331 | // update to new map info 332 | hook_info = std::move(new_hook_info); 333 | 334 | return hook_info.DoHook(register_info); 335 | } 336 | 337 | [[gnu::destructor]] [[maybe_unused]] bool InvalidateBackup() { 338 | const std::unique_lock lock(hook_mutex); 339 | return hook_info.InvalidateBackup(); 340 | } 341 | } // namespace lsplt::inline v2 342 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | plugins { 8 | id("com.android.application") version "8.2.2" 9 | id("com.android.library") version "8.2.2" 10 | } 11 | } 12 | dependencyResolutionManagement { 13 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 14 | repositories { 15 | google() 16 | mavenCentral() 17 | } 18 | } 19 | 20 | rootProject.name = "LSPlt" 21 | include( 22 | ":lsplt", 23 | ":test", 24 | ) 25 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /test/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.android.build.api.dsl.ManagedVirtualDevice 2 | import com.android.build.gradle.internal.tasks.ManagedDeviceInstrumentationTestTask 3 | 4 | plugins { 5 | id("com.android.application") 6 | } 7 | 8 | val androidTargetSdkVersion: Int by rootProject.extra 9 | val androidMinSdkVersion: Int by rootProject.extra 10 | val androidBuildToolsVersion: String by rootProject.extra 11 | val androidCompileSdkVersion: Int by rootProject.extra 12 | val androidNdkVersion: String by rootProject.extra 13 | val androidCmakeVersion: String by rootProject.extra 14 | 15 | android { 16 | namespace = "org.lsposed.lsplt.test" 17 | compileSdk = androidCompileSdkVersion 18 | ndkVersion = androidNdkVersion 19 | buildToolsVersion = androidBuildToolsVersion 20 | 21 | buildFeatures { 22 | buildConfig = false 23 | prefab = true 24 | } 25 | 26 | defaultConfig { 27 | applicationId = "org.lsposed.lsplt" 28 | minSdk = androidMinSdkVersion 29 | targetSdk = androidTargetSdkVersion 30 | versionCode = 1 31 | versionName = "1.0" 32 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 33 | externalNativeBuild { 34 | cmake { 35 | arguments += "-DANDROID_STL=c++_shared" 36 | } 37 | } 38 | } 39 | 40 | externalNativeBuild { 41 | cmake { 42 | path = file("src/main/jni/CMakeLists.txt") 43 | version = androidCmakeVersion 44 | } 45 | } 46 | 47 | compileOptions { 48 | sourceCompatibility = JavaVersion.VERSION_17 49 | targetCompatibility = JavaVersion.VERSION_17 50 | } 51 | 52 | testOptions { 53 | managedDevices { 54 | devices { 55 | fun createDevice(api: Int, is64: Boolean, target: String = "default") = create("""avd-$api-${if(is64) "x86_64" else "x86"}-$target""") { 56 | device = "Pixel 2" 57 | apiLevel = api 58 | systemImageSource = target 59 | require64Bit = is64 60 | } 61 | 62 | createDevice(21, false) 63 | createDevice(21, true) 64 | createDevice(22, false) 65 | createDevice(22, true) 66 | createDevice(23, false) 67 | createDevice(23, true) 68 | createDevice(24, false) 69 | createDevice(24, true) 70 | createDevice(25, false) 71 | createDevice(25, true) 72 | createDevice(26, false) 73 | createDevice(26, true) 74 | createDevice(27, false) 75 | createDevice(27, true) 76 | createDevice(28, false) 77 | createDevice(28, true) 78 | createDevice(29, false) 79 | createDevice(29, true) 80 | createDevice(30, false, "aosp_atd") 81 | createDevice(30, true) 82 | // createDevice(31, false, "android-tv") 83 | createDevice(31, true, "aosp_atd") 84 | createDevice(32, true, "aosp_atd") 85 | createDevice(33, true, "aosp_atd") 86 | createDevice(34, true, "aosp_atd") 87 | } 88 | } 89 | } 90 | } 91 | 92 | dependencies { 93 | implementation(project(":lsplt")) 94 | 95 | androidTestImplementation("androidx.test.ext:junit:1.1.4") 96 | androidTestImplementation("androidx.test:runner:1.5.1") 97 | androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0") 98 | } 99 | 100 | afterEvaluate { 101 | task("testOnAllMVDs") { 102 | dependsOn("assembleAndroidTest") 103 | doLast { 104 | tasks.withType(ManagedDeviceInstrumentationTestTask::class.java) { 105 | println("::group::$this") 106 | exec { 107 | executable = "${rootProject.buildFile.parent}/gradlew" 108 | args = listOf(":${project.name}:$name") 109 | } 110 | exec { 111 | executable = "${rootProject.buildFile.parent}/gradlew" 112 | args = listOf(":${project.name}:cleanManagedDevices") 113 | } 114 | println("::endgroup::") 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /test/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /test/src/main/jni/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.18.1) 2 | project("lsplt_test") 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | 7 | add_library(test SHARED test.cpp) 8 | find_package(lsplt REQUIRED CONFIG) 9 | target_link_libraries(test log lsplt::lsplt) 10 | -------------------------------------------------------------------------------- /test/src/main/jni/logging.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of LSPosed. 3 | * 4 | * LSPosed is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * LSPosed is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with LSPosed. If not, see . 16 | * 17 | * Copyright (C) 2020 EdXposed Contributors 18 | * Copyright (C) 2021 LSPosed Contributors 19 | */ 20 | 21 | #ifndef _LOGGING_H 22 | #define _LOGGING_H 23 | 24 | #include 25 | 26 | #ifndef LOG_TAG 27 | #define LOG_TAG "LSPlt-test" 28 | #endif 29 | 30 | #ifdef LOG_DISABLED 31 | #define LOGD(...) 0 32 | #define LOGV(...) 0 33 | #define LOGI(...) 0 34 | #define LOGW(...) 0 35 | #define LOGE(...) 0 36 | #else 37 | #ifndef NDEBUG 38 | #define LOGD(fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "%s:%d#%s" ": " fmt, __FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(,) __VA_ARGS__) 39 | #define LOGV(fmt, ...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, "%s:%d#%s" ": " fmt, __FILE_NAME__, __LINE__, __PRETTY_FUNCTION__ __VA_OPT__(,) __VA_ARGS__) 40 | #else 41 | #define LOGD(...) 0 42 | #define LOGV(...) 0 43 | #endif 44 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__) 45 | #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__) 46 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) 47 | #define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, __VA_ARGS__) 48 | #define PLOGE(fmt, args...) LOGE(fmt " failed with %d: %s", ##args, errno, strerror(errno)) 49 | #endif 50 | 51 | #endif // _LOGGING_H 52 | -------------------------------------------------------------------------------- /test/src/main/jni/test.cpp: -------------------------------------------------------------------------------- 1 | #include "logging.h" 2 | #include "lsplt.hpp" 3 | #include 4 | 5 | JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { 6 | JNIEnv *env; 7 | if (vm->GetEnv((void **)&env, JNI_VERSION_1_6) != JNI_OK) { 8 | return JNI_ERR; 9 | } 10 | return JNI_VERSION_1_6; 11 | } 12 | --------------------------------------------------------------------------------