├── .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 | 
5 | 
6 | 
7 | 
8 | 
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 | [](https://github.com/jothepro/doxygen-awesome-css/releases/latest)
4 | [](https://github.com/jothepro/doxygen-awesome-css/blob/main/LICENSE)
5 | 
6 |
7 |
8 |
9 | 
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 | 
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 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | $projectname
64 | $projectnumber
65 |
66 | $projectbrief
67 |
68 |
69 |
70 |
71 |
72 | $projectbrief
73 |
74 |
75 |
76 |
77 |
78 | $searchbox
79 |
80 |
81 |
82 |
83 |
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 |
--------------------------------------------------------------------------------