├── .github ├── FUNDING.yml └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── build.rs ├── examples ├── blobs.rs ├── index.html ├── instancing.rs ├── mouse_cursor.rs ├── msaa_render_texture.rs ├── offscreen.rs ├── post_processing.rs ├── quad.rs ├── triangle.rs ├── triangle_color4b.rs └── window_conf.rs ├── java ├── MainActivity.java └── QuadNative.java ├── js └── gl.js ├── shell.nix └── src ├── conf.rs ├── default_icon.rs ├── event.rs ├── fs.rs ├── graphics.rs ├── graphics ├── gl.rs ├── gl │ └── cache.rs └── metal.rs ├── lib.rs ├── log.rs ├── native.rs └── native ├── android.rs ├── android ├── keycodes.rs ├── mod_inject.rs └── ndk_utils.rs ├── apple.rs ├── apple ├── apple_util.rs └── frameworks.rs ├── egl.rs ├── gl.rs ├── ios.rs ├── linux_wayland.rs ├── linux_wayland ├── clipboard.rs ├── decorations.rs ├── drag_n_drop.rs ├── extensions.rs ├── extensions │ ├── cursor.rs │ ├── libdecor.rs │ ├── viewporter.rs │ ├── xdg_decoration.rs │ └── xdg_shell.rs ├── keycodes.rs ├── libwayland_client.rs ├── libwayland_egl.rs ├── libxkbcommon.rs └── shm.rs ├── linux_x11.rs ├── linux_x11 ├── clipboard.rs ├── drag_n_drop.rs ├── glx.rs ├── keycodes.rs ├── libx11.rs ├── libx11_ex.rs ├── rand.rs ├── x.rs ├── x_cursor.rs └── xi_input.rs ├── macos.rs ├── module.rs ├── query_stab.rs ├── wasm.rs ├── wasm ├── fs.rs ├── keycodes.rs └── webgl.rs ├── windows.rs └── windows ├── clipboard.rs ├── keycodes.rs ├── libopengl32.rs └── wgl.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: fedorgames 2 | github: not-fl3 3 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Cross-compile 4 | 5 | jobs: 6 | build: 7 | name: Build 8 | runs-on: ${{ matrix.config.os }} 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | config: 13 | - { os: ubuntu-latest, target: 'aarch64-unknown-linux-gnu' } 14 | - { os: ubuntu-latest, target: 'armv7-unknown-linux-gnueabihf' } 15 | - { os: ubuntu-latest, target: 'x86_64-unknown-linux-gnu' } 16 | - { os: ubuntu-latest, target: 'x86_64-pc-windows-gnu' } 17 | - { os: ubuntu-latest, target: 'wasm32-unknown-unknown' } 18 | - { os: macos-latest, target: 'x86_64-apple-darwin' } 19 | - { os: macos-latest, target: 'aarch64-apple-ios' } 20 | - { os: macos-latest, target: 'x86_64-apple-ios' } 21 | - { os: windows-latest, target: 'x86_64-pc-windows-msvc' } 22 | include: 23 | - os: ubuntu-latest 24 | packages: libx11-dev libxi-dev libgl1-mesa-dev gcc-mingw-w64 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | - name: Install packages (Linux) 29 | if: runner.os == 'Linux' 30 | run: | 31 | sudo apt-get update 32 | sudo apt-get -yq --no-install-suggests --no-install-recommends install ${{ matrix.packages }} 33 | - uses: actions-rs/toolchain@v1 34 | with: 35 | toolchain: stable 36 | target: ${{ matrix.config.target }} 37 | override: true 38 | - name: Workaround MinGW issue # https://github.com/rust-lang/rust/issues/47048 39 | if: runner.os == 'Linux' && matrix.config.target == 'x86_64-pc-windows-gnu' 40 | run: | 41 | sudo cp /usr/x86_64-w64-mingw32/lib/dllcrt2.o ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-pc-windows-gnu/lib/dllcrt2.o 42 | sudo cp /usr/x86_64-w64-mingw32/lib/crt2.o ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/x86_64-pc-windows-gnu/lib/crt2.o 43 | echo "[target.x86_64-pc-windows-gnu]" >> ~/.cargo/config 44 | echo "linker = \"/usr/bin/x86_64-w64-mingw32-gcc\"" >> ~/.cargo/config 45 | - name: Setup aarch64 46 | if: matrix.config.target == 'aarch64-unknown-linux-gnu' 47 | run: | 48 | sudo apt install gcc-aarch64-linux-gnu 49 | echo "[target.aarch64-unknown-linux-gnu]" >> ~/.cargo/config 50 | echo "linker = \"aarch64-linux-gnu-gcc\"" >> ~/.cargo/config 51 | - name: Setup armv7 52 | if: matrix.config.target == 'armv7-unknown-linux-gnueabihf' 53 | run: | 54 | sudo apt install gcc-arm-linux-gnueabihf 55 | echo "[target.armv7-unknown-linux-gnueabihf]" >> ~/.cargo/config 56 | echo "linker = \"arm-linux-gnueabihf-gcc\"" >> ~/.cargo/config 57 | 58 | - uses: actions-rs/cargo@v1 59 | with: 60 | command: build 61 | args: --all-targets --target=${{ matrix.config.target }} 62 | 63 | android: 64 | name: Android 65 | runs-on: ubuntu-latest 66 | container: notfl3/cargo-apk 67 | steps: 68 | - uses: actions/checkout@v2 69 | 70 | - name: cargo quad-apk 71 | run: | 72 | cargo quad-apk build 73 | 74 | msrv: 75 | name: MSRV Check 76 | runs-on: ubuntu-latest 77 | steps: 78 | - uses: actions/checkout@v4 79 | - uses: actions-rs/toolchain@v1 80 | with: 81 | toolchain: '1.70.0' 82 | override: true 83 | - name: Verify MSRV 84 | run: cargo build --all-targets 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "miniquad" 3 | version = "0.4.8" 4 | authors = ["not-fl3 "] 5 | edition = "2018" 6 | rust-version = "1.70" 7 | license = "MIT OR Apache-2.0" 8 | homepage = "https://github.com/not-fl3/miniquad" 9 | repository = "https://github.com/not-fl3/miniquad" 10 | description = """ 11 | Cross-platform window context and rendering library. 12 | """ 13 | readme = "README.md" 14 | exclude = ["examples/"] 15 | keywords = ["graphics", "3D", "opengl", "gamedev", "windowing"] 16 | categories = ["rendering::graphics-api"] 17 | 18 | [features] 19 | 20 | # Optional log-rs like macros implementation 21 | # disabled by default 22 | log-impl = [] 23 | 24 | [target.'cfg(target_os = "linux")'.dependencies] 25 | libc = "0.2" 26 | 27 | [target.'cfg(windows)'.dependencies] 28 | winapi = { version = "0.3", features = [ 29 | "wingdi", 30 | "winuser", 31 | "libloaderapi", 32 | "windef", 33 | "shellscalingapi", 34 | "errhandlingapi", 35 | "windowsx", 36 | "winbase", 37 | "hidusage", 38 | "shellapi", 39 | ] } 40 | 41 | [target.'cfg(target_os = "android")'.dependencies] 42 | libc = "0.2" 43 | ndk-sys = "0.2" 44 | 45 | [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] 46 | objc = { package = "objc-rs", version = "0.2" } 47 | 48 | [dev-dependencies] 49 | glam = { version = "0.24", features = ["scalar-math"] } 50 | quad-rand = "0.1" 51 | 52 | [profile.release] 53 | lto = true 54 | panic = 'abort' 55 | opt-level = "s" 56 | overflow-checks = false 57 | debug-assertions = false 58 | incremental = false 59 | rpath = false 60 | codegen-units = 1 61 | strip = true 62 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT/X Consortium License 2 | 3 | @ 2019-2020 Fedor Logachev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the "Software"), 7 | to deal in the Software without restriction, including without limitation 8 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 9 | and/or sell copies of the Software, and to permit persons to whom the 10 | Software is furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all 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 18 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 21 | DEALINGS IN THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Miniquad 2 | 3 | [![Github Actions](https://github.com/not-fl3/miniquad/workflows/Cross-compile/badge.svg)](https://github.com/not-fl3/miniquad/actions?query=workflow%3A) 4 | [![Docs](https://docs.rs/miniquad/badge.svg?version=0.3.13)](https://docs.rs/miniquad/0.3.13/miniquad/index.html) 5 | [![Crates.io version](https://img.shields.io/crates/v/miniquad.svg)](https://crates.io/crates/miniquad) 6 | [![Discord chat](https://img.shields.io/discord/710177966440579103.svg?label=discord%20chat)](https://discord.gg/WfEp6ut) 7 | [![Matrix](https://img.shields.io/matrix/quad-general:matrix.org?label=matrix%20chat)](https://matrix.to/#/#quad-general:matrix.org) 8 | 9 | Miniquad is a manifestation of a dream in a world where we do not need a deep dependencies tree and thousands lines of code to draw things with a computer. 10 | 11 | Miniquad aims to provide a graphics abstraction that works the same way on any platform with a GPU, being as light weight as possible while covering as many machines as possible. 12 | 13 | ## Supported Platforms 14 | 15 | * Windows, OpenGL 3, OpenGL 2.2; 16 | * Linux, OpenGL 2.2, OpenGL 3, GLES 2, GLES 3; 17 | * macOS, OpenGL 3, Metal; 18 | * iOS, GLES 2, GLES 3, Metal; 19 | * WASM, WebGL 1 - tested on iOS Safari, Firefox, Chrome; 20 | * Android, GLES 2, GLES 3. 21 | 22 | ## Examples 23 | 24 | ![Imgur](https://i.imgur.com/TRI50rk.gif) 25 | 26 | [examples/quad.rs](https://github.com/not-fl3/miniquad/blob/master/examples/quad.rs): [web demo](https://not-fl3.github.io/miniquad-samples/quad.html)
27 | [examples/offscreen.rs](https://github.com/not-fl3/miniquad/blob/master/examples/offscreen.rs): [web demo](https://not-fl3.github.io/miniquad-samples/offscreen.html)
28 | 29 | [PonasKovas/miniquad-mandelbrot](https://github.com/PonasKovas/miniquad-mandelbrot): [web demo](https://ponaskovas.github.io/miniquad-mandelbrot-wasm-demo/) 30 | 31 | # Building examples 32 | 33 | ## Linux 34 | 35 | ```bash 36 | cargo run --example quad 37 | ``` 38 | 39 | On NixOS Linux you can use [`shell.nix`](shell.nix) to start a development 40 | environment where Miniquad can be built and run. 41 | 42 | ## Windows 43 | 44 | ```bash 45 | # both MSVC and GNU target is supported: 46 | rustup target add x86_64-pc-windows-msvc 47 | # or 48 | rustup target add x86_64-pc-windows-gnu 49 | 50 | cargo run --example quad 51 | ``` 52 | 53 | ## WASM 54 | 55 | ```bash 56 | rustup target add wasm32-unknown-unknown 57 | cargo build --example quad --target wasm32-unknown-unknown 58 | ``` 59 | 60 | And then use the following .html to load .wasm: 61 | 62 |
index.html 63 | 64 | ```html 65 | 66 | 67 | 68 | 69 | TITLE 70 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | ``` 95 |
96 | 97 | One of the ways to server static .wasm and .html: 98 | 99 | ```no-run 100 | cargo install basic-http-server 101 | basic-http-server . 102 | ``` 103 | 104 | ## Android 105 | 106 | Recommended way to build for android is using Docker.
107 | miniquad uses slightly modifed version of `cargo-apk` 108 | 109 | ```no-run 110 | docker run --rm -v $(pwd)":/root/src" -w /root/src notfl3/cargo-apk cargo quad-apk build --example quad 111 | ``` 112 | 113 | APK file will be in `target/android-artifacts/(debug|release)/apk` 114 | 115 | With "log-impl" enabled all log calls will be forwarded to adb console. 116 | No code modifications for Android required, everything should just works. 117 | 118 | ## iOS 119 | 120 | To run on the simulator: 121 | 122 | ```no-run 123 | mkdir MyGame.app 124 | cargo build --target x86_64-apple-ios --release 125 | cp target/release/mygame MyGame.app 126 | # only if the game have any assets 127 | cp -r assets MyGame.app 128 | cat > MyGame.app/Info.plist << EOF 129 | 130 | 131 | 132 | 133 | CFBundleExecutable 134 | mygame 135 | CFBundleIdentifier 136 | com.mygame 137 | CFBundleName 138 | mygame 139 | CFBundleVersion 140 | 1 141 | CFBundleShortVersionString 142 | 1.0 143 | 144 | 145 | EOF 146 | 147 | xcrun simctl install booted MyGame.app/ 148 | xcrun simctl launch booted com.mygame 149 | ``` 150 | 151 | For details and instructions on provisioning for real iphone, check [https://macroquad.rs/articles/ios/](https://macroquad.rs/articles/ios/) 152 | 153 | ## Cross Compilation 154 | 155 | ```bash 156 | 157 | # windows target from linux host: 158 | # this is how windows builds are tested from linux machine: 159 | rustup target add x86_64-pc-windows-gnu 160 | cargo run --example quad --target x86_64-pc-windows-gnu 161 | ``` 162 | 163 | # Goals 164 | 165 | * Fast compilation time. Right now it is ~5s from "cargo clean" for both desktop and web. 166 | 167 | * Cross platform. Amount of platform specific user code required should be kept as little as possible. 168 | 169 | * Low-end devices support. 170 | 171 | * Hackability. Working on your own game, highly probable some hardware incompability will be found. Working around that kind of bugs should be easy, implementation details should not be hidden under layers of abstraction. 172 | 173 | * Forkability. Each platform implementation is, usually, just one pure Rust file. And this file is very copy-paste friendly - it doesnt use any miniquad specific abstractions. It is very easy to just copy some part of miniquad's platform implementation and use it standalone. 174 | 175 | # Non-goals 176 | 177 | * Ultimate type safety. Library should be entirely safe in Rust's definition of safe - no UB or memory unsafety. But correct GPU state is not type guaranteed. Feel free to provide safety abstraction in the user code then! 178 | 179 | * High-end API, like Vulkan/DirectX 12. Take a look on [gfx-rs](https://github.com/gfx-rs/gfx) or [vulkano](https://github.com/vulkano-rs/vulkano) instead! 180 | 181 | # Platinum sponsors 182 | 183 | Miniquad is supported by: 184 | 185 | [SourceGear](https://www.sourcegear.com/) 186 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | fn main() { 4 | let target = env::var("TARGET").unwrap_or_else(|e| panic!("{}", e)); 5 | 6 | if target.contains("darwin") || target.contains("ios") { 7 | println!("cargo:rustc-link-lib=framework=MetalKit"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/blobs.rs: -------------------------------------------------------------------------------- 1 | use miniquad::*; 2 | 3 | #[repr(C)] 4 | struct Vec2 { 5 | x: f32, 6 | y: f32, 7 | } 8 | #[repr(C)] 9 | struct Vertex { 10 | pos: Vec2, 11 | uv: Vec2, 12 | } 13 | 14 | struct Stage { 15 | pipeline: Pipeline, 16 | bindings: Bindings, 17 | start_time: f64, 18 | last_frame: f64, 19 | uniforms: shader::Uniforms, 20 | blobs_velocities: [(f32, f32); 32], 21 | ctx: Box, 22 | } 23 | 24 | impl Stage { 25 | pub fn new() -> Stage { 26 | let mut ctx: Box = window::new_rendering_backend(); 27 | 28 | #[rustfmt::skip] 29 | let vertices: [Vertex; 4] = [ 30 | Vertex { pos : Vec2 { x: -1.0, y: -1.0 }, uv: Vec2 { x: 0., y: 0. } }, 31 | Vertex { pos : Vec2 { x: 1.0, y: -1.0 }, uv: Vec2 { x: 1., y: 0. } }, 32 | Vertex { pos : Vec2 { x: 1.0, y: 1.0 }, uv: Vec2 { x: 1., y: 1. } }, 33 | Vertex { pos : Vec2 { x: -1.0, y: 1.0 }, uv: Vec2 { x: 0., y: 1. } }, 34 | ]; 35 | let vertex_buffer = ctx.new_buffer( 36 | BufferType::VertexBuffer, 37 | BufferUsage::Immutable, 38 | BufferSource::slice(&vertices), 39 | ); 40 | 41 | let indices: [u16; 6] = [0, 1, 2, 0, 2, 3]; 42 | let index_buffer = ctx.new_buffer( 43 | BufferType::IndexBuffer, 44 | BufferUsage::Immutable, 45 | BufferSource::slice(&indices), 46 | ); 47 | 48 | let bindings = Bindings { 49 | vertex_buffers: vec![vertex_buffer], 50 | index_buffer: index_buffer, 51 | images: vec![], 52 | }; 53 | 54 | let shader = ctx 55 | .new_shader( 56 | match ctx.info().backend { 57 | Backend::OpenGl => ShaderSource::Glsl { 58 | vertex: shader::VERTEX, 59 | fragment: shader::FRAGMENT, 60 | }, 61 | Backend::Metal => ShaderSource::Msl { 62 | program: shader::METAL, 63 | }, 64 | }, 65 | shader::meta(), 66 | ) 67 | .unwrap(); 68 | 69 | let pipeline = ctx.new_pipeline( 70 | &[BufferLayout::default()], 71 | &[ 72 | VertexAttribute::new("in_pos", VertexFormat::Float2), 73 | VertexAttribute::new("in_uv", VertexFormat::Float2), 74 | ], 75 | shader, 76 | PipelineParams::default(), 77 | ); 78 | 79 | let uniforms = shader::Uniforms { 80 | time: 0., 81 | blobs_count: 1, 82 | blobs_positions: [(0., 0.); 32], 83 | }; 84 | 85 | let time = miniquad::date::now(); 86 | 87 | Stage { 88 | pipeline, 89 | bindings, 90 | start_time: time, 91 | uniforms, 92 | blobs_velocities: [(0., 0.); 32], 93 | last_frame: time, 94 | ctx, 95 | } 96 | } 97 | } 98 | 99 | impl EventHandler for Stage { 100 | fn update(&mut self) { 101 | let time = miniquad::date::now(); 102 | let delta = (time - self.last_frame) as f32; 103 | self.last_frame = time; 104 | 105 | for i in 1..self.uniforms.blobs_count as usize { 106 | self.uniforms.blobs_positions[i].0 += self.blobs_velocities[i].0 * delta * 0.1; 107 | self.uniforms.blobs_positions[i].1 += self.blobs_velocities[i].1 * delta * 0.1; 108 | 109 | if self.uniforms.blobs_positions[i].0 < 0. || self.uniforms.blobs_positions[i].0 > 1. { 110 | self.blobs_velocities[i].0 *= -1.; 111 | } 112 | if self.uniforms.blobs_positions[i].1 < 0. || self.uniforms.blobs_positions[i].1 > 1. { 113 | self.blobs_velocities[i].1 *= -1.; 114 | } 115 | } 116 | } 117 | 118 | fn mouse_motion_event(&mut self, x: f32, y: f32) { 119 | let (w, h) = window::screen_size(); 120 | let (x, y) = (x / w, 1. - y / h); 121 | self.uniforms.blobs_positions[0] = (x, y); 122 | } 123 | 124 | fn mouse_button_down_event(&mut self, _button: MouseButton, x: f32, y: f32) { 125 | if self.uniforms.blobs_count >= 32 { 126 | return; 127 | } 128 | 129 | let (w, h) = window::screen_size(); 130 | let (x, y) = (x / w, 1. - y / h); 131 | let (dx, dy) = (quad_rand::gen_range(-1., 1.), quad_rand::gen_range(-1., 1.)); 132 | 133 | self.uniforms.blobs_positions[self.uniforms.blobs_count as usize] = (x, y); 134 | self.blobs_velocities[self.uniforms.blobs_count as usize] = (dx, dy); 135 | self.uniforms.blobs_count += 1; 136 | } 137 | 138 | fn draw(&mut self) { 139 | self.uniforms.time = (miniquad::date::now() - self.start_time) as f32; 140 | 141 | self.ctx.begin_default_pass(Default::default()); 142 | self.ctx.apply_pipeline(&self.pipeline); 143 | self.ctx.apply_bindings(&self.bindings); 144 | self.ctx 145 | .apply_uniforms(UniformsSource::table(&self.uniforms)); 146 | self.ctx.draw(0, 6, 1); 147 | self.ctx.end_render_pass(); 148 | 149 | self.ctx.commit_frame(); 150 | } 151 | } 152 | 153 | fn main() { 154 | let mut conf = conf::Conf::default(); 155 | let metal = std::env::args().nth(1).as_deref() == Some("metal"); 156 | conf.platform.apple_gfx_api = if metal { 157 | conf::AppleGfxApi::Metal 158 | } else { 159 | conf::AppleGfxApi::OpenGl 160 | }; 161 | 162 | miniquad::start(conf, move || Box::new(Stage::new())); 163 | } 164 | 165 | // based on: https://www.shadertoy.com/view/XsS3DV 166 | mod shader { 167 | use miniquad::*; 168 | 169 | pub const VERTEX: &str = r#"#version 100 170 | attribute vec2 in_pos; 171 | attribute vec2 in_uv; 172 | 173 | varying highp vec2 uv; 174 | 175 | void main() { 176 | gl_Position = vec4(in_pos, 0, 1); 177 | uv = in_uv; 178 | }"#; 179 | 180 | pub const FRAGMENT: &str = r#"#version 100 181 | precision highp float; 182 | 183 | varying vec2 uv; 184 | 185 | uniform float time; 186 | uniform int blobs_count; 187 | uniform vec2 blobs_positions[32]; 188 | 189 | float k = 20.0; 190 | float field = 0.0; 191 | vec2 coord; 192 | 193 | void circle ( float r , vec3 col , vec2 offset) { 194 | vec2 pos = coord.xy; 195 | vec2 c = offset; 196 | float d = distance ( pos , c ); 197 | field += ( k * r ) / ( d*d ); 198 | } 199 | 200 | vec3 band ( float shade, float low, float high, vec3 col1, vec3 col2 ) { 201 | if ( (shade >= low) && (shade <= high) ) { 202 | float delta = (shade - low) / (high - low); 203 | vec3 colDiff = col2 - col1; 204 | return col1 + (delta * colDiff); 205 | } 206 | else 207 | return vec3(0.0,0.0,0.0); 208 | } 209 | 210 | vec3 gradient ( float shade ) { 211 | vec3 colour = vec3( (sin(time/2.0)*0.25)+0.25,0.0,(cos(time/2.0)*0.25)+0.25); 212 | 213 | vec3 col1 = vec3(0.01, 0.0, 1.0-0.01); 214 | vec3 col2 = vec3(1.0-0.01, 0.0, 0.01); 215 | vec3 col3 = vec3(0.02, 1.0-0.02, 0.02); 216 | vec3 col4 = vec3((0.01+0.02)/2.0, (0.01+0.02)/2.0, 1.0 - (0.01+0.02)/2.0); 217 | vec3 col5 = vec3(0.02, 0.02, 0.02); 218 | 219 | colour += band ( shade, 0.0, 0.3, colour, col1 ); 220 | colour += band ( shade, 0.3, 0.6, col1, col2 ); 221 | colour += band ( shade, 0.6, 0.8, col2, col3 ); 222 | colour += band ( shade, 0.8, 0.9, col3, col4 ); 223 | colour += band ( shade, 0.9, 1.0, col4, col5 ); 224 | 225 | return colour; 226 | } 227 | 228 | void main() { 229 | coord = uv; 230 | 231 | for (int i = 0; i < 32; i++) { 232 | if (i >= blobs_count) { break; } // workaround for webgl error: Loop index cannot be compared with non-constant expression 233 | circle(.03 , vec3(0.7 ,0.2, 0.8), blobs_positions[i]); 234 | } 235 | 236 | float shade = min ( 1.0, max ( field/256.0, 0.0 ) ); 237 | 238 | gl_FragColor = vec4( gradient(shade), 1.0 ); 239 | }"#; 240 | 241 | pub const METAL: &str = r#" 242 | #include 243 | 244 | using namespace metal; 245 | 246 | struct Uniforms 247 | { 248 | float time; 249 | int16_t blobs_count; 250 | float2 blobs[32]; 251 | }; 252 | 253 | struct Vertex 254 | { 255 | float2 in_pos [[attribute(0)]]; 256 | float2 in_uv [[attribute(1)]]; 257 | }; 258 | 259 | struct RasterizerData 260 | { 261 | float4 position [[position]]; 262 | float2 uv [[user(locn0)]]; 263 | }; 264 | 265 | vertex RasterizerData vertexShader( 266 | Vertex v [[stage_in]]) 267 | { 268 | RasterizerData out; 269 | 270 | out.position = float4(v.in_pos.xy, 0.0, 1.0); 271 | out.uv = v.in_uv; 272 | 273 | return out; 274 | } 275 | 276 | constant float k = 20.0; 277 | 278 | float circle(float2 coord, float r , float3 col , float2 offset) { 279 | float2 pos = coord.xy; 280 | float2 c = offset; 281 | float d = distance ( pos , c ); 282 | return ( k * r ) / ( d*d ); 283 | } 284 | 285 | float3 band ( float shade, float low, float high, float3 col1, float3 col2 ) { 286 | if ( (shade >= low) && (shade <= high) ) { 287 | float delta = (shade - low) / (high - low); 288 | float3 colDiff = col2 - col1; 289 | return col1 + (delta * colDiff); 290 | } 291 | else 292 | return float3(0.0,0.0,0.0); 293 | } 294 | 295 | float3 gradient (float shade, float time) { 296 | float3 colour = float3( (sin(time/2.0)*0.25)+0.25,0.0,(cos(time/2.0)*0.25)+0.25); 297 | 298 | float3 col1 = float3(0.01, 0.0, 1.0-0.01); 299 | float3 col2 = float3(1.0-0.01, 0.0, 0.01); 300 | float3 col3 = float3(0.02, 1.0-0.02, 0.02); 301 | float3 col4 = float3((0.01+0.02)/2.0, (0.01+0.02)/2.0, 1.0 - (0.01+0.02)/2.0); 302 | float3 col5 = float3(0.02, 0.02, 0.02); 303 | 304 | colour += band ( shade, 0.0, 0.3, colour, col1 ); 305 | colour += band ( shade, 0.3, 0.6, col1, col2 ); 306 | colour += band ( shade, 0.6, 0.8, col2, col3 ); 307 | colour += band ( shade, 0.8, 0.9, col3, col4 ); 308 | colour += band ( shade, 0.9, 1.0, col4, col5 ); 309 | 310 | return colour; 311 | } 312 | 313 | fragment float4 fragmentShader(RasterizerData in [[stage_in]], constant Uniforms& uniforms [[buffer(0)]]) 314 | { 315 | float field = 0.0; 316 | for (int i = 0; i < 32; i++) { 317 | if (i >= uniforms.blobs_count) { break; } // workaround for webgl error: Loop index cannot be compared with non-constant expression 318 | field += circle(in.uv, .03 , float3(0.7 ,0.2, 0.8), uniforms.blobs[i]); 319 | } 320 | 321 | float shade = min ( 1.0, max(field/256.0, 0.0 ) ); 322 | 323 | return float4(gradient(shade, uniforms.time), 1.0 ); 324 | }"#; 325 | 326 | pub fn meta() -> ShaderMeta { 327 | ShaderMeta { 328 | images: vec![], 329 | uniforms: UniformBlockLayout { 330 | uniforms: vec![ 331 | UniformDesc::new("time", UniformType::Float1), 332 | UniformDesc::new("blobs_count", UniformType::Int1), 333 | UniformDesc::new("blobs_positions", UniformType::Float2).array(32), 334 | ], 335 | }, 336 | } 337 | } 338 | 339 | #[repr(C)] 340 | pub struct Uniforms { 341 | pub time: f32, 342 | pub blobs_count: i32, 343 | pub blobs_positions: [(f32, f32); 32], 344 | } 345 | } 346 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | TITLE 7 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/instancing.rs: -------------------------------------------------------------------------------- 1 | use miniquad::*; 2 | 3 | use glam::{vec3, Mat4, Vec3}; 4 | 5 | const MAX_PARTICLES: usize = 512 * 1024; 6 | const NUM_PARTICLES_EMITTED_PER_FRAME: usize = 10; 7 | 8 | struct Stage { 9 | ctx: Box, 10 | 11 | pipeline: Pipeline, 12 | bindings: Bindings, 13 | 14 | pos: Vec, 15 | vel: Vec, 16 | ry: f32, 17 | } 18 | 19 | impl Stage { 20 | pub fn new() -> Stage { 21 | let mut ctx: Box = window::new_rendering_backend(); 22 | 23 | let r = 0.05; 24 | #[rustfmt::skip] 25 | let vertices: &[f32] = &[ 26 | // positions colors 27 | 0.0, -r, 0.0, 1.0, 0.0, 0.0, 1.0, 28 | r, 0.0, r, 0.0, 1.0, 0.0, 1.0, 29 | r, 0.0, -r, 0.0, 0.0, 1.0, 1.0, 30 | -r, 0.0, -r, 1.0, 1.0, 0.0, 1.0, 31 | -r, 0.0, r, 0.0, 1.0, 1.0, 1.0, 32 | 0.0, r, 0.0, 1.0, 0.0, 1.0, 1.0 33 | ]; 34 | // vertex buffer for static geometry 35 | let geometry_vertex_buffer = ctx.new_buffer( 36 | BufferType::VertexBuffer, 37 | BufferUsage::Immutable, 38 | BufferSource::slice(&vertices), 39 | ); 40 | 41 | #[rustfmt::skip] 42 | let indices: &[u16] = &[ 43 | 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 1, 44 | 5, 1, 2, 5, 2, 3, 5, 3, 4, 5, 4, 1 45 | ]; 46 | let index_buffer = ctx.new_buffer( 47 | BufferType::IndexBuffer, 48 | BufferUsage::Immutable, 49 | BufferSource::slice(&indices), 50 | ); 51 | 52 | // empty, dynamic instance data vertex buffer 53 | let positions_vertex_buffer = ctx.new_buffer( 54 | BufferType::VertexBuffer, 55 | BufferUsage::Stream, 56 | BufferSource::empty::(MAX_PARTICLES), 57 | ); 58 | 59 | let bindings = Bindings { 60 | vertex_buffers: vec![geometry_vertex_buffer, positions_vertex_buffer], 61 | index_buffer: index_buffer, 62 | images: vec![], 63 | }; 64 | 65 | let shader = ctx 66 | .new_shader( 67 | match ctx.info().backend { 68 | Backend::OpenGl => ShaderSource::Glsl { 69 | vertex: shader::VERTEX, 70 | fragment: shader::FRAGMENT, 71 | }, 72 | Backend::Metal => ShaderSource::Msl { 73 | program: shader::METAL, 74 | }, 75 | }, 76 | shader::meta(), 77 | ) 78 | .unwrap(); 79 | 80 | let pipeline = ctx.new_pipeline( 81 | &[ 82 | BufferLayout::default(), 83 | BufferLayout { 84 | step_func: VertexStep::PerInstance, 85 | ..Default::default() 86 | }, 87 | ], 88 | &[ 89 | VertexAttribute::with_buffer("in_pos", VertexFormat::Float3, 0), 90 | VertexAttribute::with_buffer("in_color", VertexFormat::Float4, 0), 91 | VertexAttribute::with_buffer("in_inst_pos", VertexFormat::Float3, 1), 92 | ], 93 | shader, 94 | PipelineParams::default(), 95 | ); 96 | 97 | Stage { 98 | ctx, 99 | pipeline, 100 | bindings, 101 | pos: Vec::with_capacity(MAX_PARTICLES), 102 | vel: Vec::with_capacity(MAX_PARTICLES), 103 | ry: 0., 104 | } 105 | } 106 | } 107 | 108 | impl EventHandler for Stage { 109 | fn update(&mut self) { 110 | let frame_time = 1. / 60.; 111 | 112 | // emit new particles 113 | for _ in 0..NUM_PARTICLES_EMITTED_PER_FRAME { 114 | if self.pos.len() < MAX_PARTICLES { 115 | self.pos.push(vec3(0., 0., 0.)); 116 | self.vel.push(vec3( 117 | quad_rand::gen_range(-1., 1.), 118 | quad_rand::gen_range(0., 2.), 119 | quad_rand::gen_range(-1., 1.), 120 | )); 121 | } else { 122 | break; 123 | } 124 | } 125 | 126 | // update particle positions 127 | for i in 0..self.pos.len() { 128 | self.vel[i] -= vec3(0., frame_time, 0.); 129 | self.pos[i] += self.vel[i] * frame_time; 130 | /* bounce back from 'ground' */ 131 | if self.pos[i].y < -2.0 { 132 | self.pos[i].y = -1.8; 133 | self.vel[i] *= vec3(0.8, -0.8, 0.8); 134 | } 135 | } 136 | } 137 | 138 | fn draw(&mut self) { 139 | // by default glam-rs can vec3 as u128 or #[reprc(C)](f32, f32, f32). need to ensure that the second option was used 140 | assert_eq!(std::mem::size_of::(), 12); 141 | 142 | self.ctx.buffer_update( 143 | self.bindings.vertex_buffers[1], 144 | BufferSource::slice(&self.pos[..]), 145 | ); 146 | 147 | // model-view-projection matrix 148 | let (width, height) = window::screen_size(); 149 | 150 | let proj = Mat4::perspective_rh_gl(60.0f32.to_radians(), width / height, 0.01, 50.0); 151 | let view = Mat4::look_at_rh( 152 | vec3(0.0, 1.5, 12.0), 153 | vec3(0.0, 0.0, 0.0), 154 | vec3(0.0, 1.0, 0.0), 155 | ); 156 | let view_proj = proj * view; 157 | 158 | self.ry += 0.01; 159 | let mvp = view_proj * Mat4::from_rotation_y(self.ry); 160 | 161 | self.ctx.begin_default_pass(Default::default()); 162 | 163 | self.ctx.apply_pipeline(&self.pipeline); 164 | self.ctx.apply_bindings(&self.bindings); 165 | self.ctx 166 | .apply_uniforms(UniformsSource::table(&shader::Uniforms { mvp })); 167 | self.ctx.draw(0, 24, self.pos.len() as i32); 168 | self.ctx.end_render_pass(); 169 | 170 | self.ctx.commit_frame(); 171 | } 172 | } 173 | 174 | fn main() { 175 | let mut conf = conf::Conf::default(); 176 | let metal = std::env::args().nth(1).as_deref() == Some("metal"); 177 | conf.platform.apple_gfx_api = if metal { 178 | conf::AppleGfxApi::Metal 179 | } else { 180 | conf::AppleGfxApi::OpenGl 181 | }; 182 | 183 | miniquad::start(conf, move || Box::new(Stage::new())); 184 | } 185 | 186 | mod shader { 187 | use miniquad::*; 188 | 189 | pub const VERTEX: &str = r#"#version 100 190 | attribute vec3 in_pos; 191 | attribute vec4 in_color; 192 | attribute vec3 in_inst_pos; 193 | 194 | varying lowp vec4 color; 195 | 196 | uniform mat4 mvp; 197 | 198 | void main() { 199 | vec4 pos = vec4(in_pos + in_inst_pos, 1.0); 200 | gl_Position = mvp * pos; 201 | color = in_color; 202 | } 203 | "#; 204 | 205 | pub const FRAGMENT: &str = r#"#version 100 206 | varying lowp vec4 color; 207 | 208 | void main() { 209 | gl_FragColor = color; 210 | } 211 | "#; 212 | 213 | pub const METAL: &str = r#" 214 | #include 215 | 216 | using namespace metal; 217 | 218 | struct Uniforms 219 | { 220 | float4x4 mvp; 221 | }; 222 | 223 | struct Vertex 224 | { 225 | float3 in_pos [[attribute(0)]]; 226 | float4 in_color [[attribute(1)]]; 227 | float3 in_inst_pos [[attribute(2)]]; 228 | }; 229 | 230 | struct RasterizerData 231 | { 232 | float4 position [[position]]; 233 | float4 color [[user(locn0)]]; 234 | }; 235 | 236 | vertex RasterizerData vertexShader(Vertex v [[stage_in]], constant Uniforms& uniforms [[buffer(0)]]) 237 | { 238 | RasterizerData out; 239 | 240 | out.position = uniforms.mvp * float4(v.in_pos + v.in_inst_pos, 1.0); 241 | out.color = v.in_color; 242 | 243 | return out; 244 | } 245 | 246 | fragment float4 fragmentShader(RasterizerData in [[stage_in]]) 247 | { 248 | return in.color; 249 | }"#; 250 | 251 | pub fn meta() -> ShaderMeta { 252 | ShaderMeta { 253 | images: vec![], 254 | uniforms: UniformBlockLayout { 255 | uniforms: vec![UniformDesc::new("mvp", UniformType::Mat4)], 256 | }, 257 | } 258 | } 259 | 260 | #[repr(C)] 261 | pub struct Uniforms { 262 | pub mvp: glam::Mat4, 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /examples/mouse_cursor.rs: -------------------------------------------------------------------------------- 1 | use miniquad::*; 2 | 3 | struct Stage {} 4 | 5 | impl EventHandler for Stage { 6 | fn update(&mut self) {} 7 | 8 | fn draw(&mut self) {} 9 | 10 | fn char_event(&mut self, character: char, _: KeyMods, _: bool) { 11 | match character { 12 | 'z' => window::show_mouse(false), 13 | 'x' => window::show_mouse(true), 14 | _ => (), 15 | } 16 | 17 | let icon = match character { 18 | '1' => CursorIcon::Default, 19 | '2' => CursorIcon::Help, 20 | '3' => CursorIcon::Pointer, 21 | '4' => CursorIcon::Wait, 22 | '5' => CursorIcon::Crosshair, 23 | '6' => CursorIcon::Text, 24 | '7' => CursorIcon::Move, 25 | '8' => CursorIcon::NotAllowed, 26 | '9' => CursorIcon::EWResize, 27 | '0' => CursorIcon::NSResize, 28 | 'q' => CursorIcon::NESWResize, 29 | 'w' => CursorIcon::NWSEResize, 30 | _ => return, 31 | }; 32 | window::set_mouse_cursor(icon); 33 | } 34 | } 35 | 36 | fn main() { 37 | miniquad::start(conf::Conf::default(), || Box::new(Stage {})); 38 | } 39 | -------------------------------------------------------------------------------- /examples/quad.rs: -------------------------------------------------------------------------------- 1 | use miniquad::*; 2 | 3 | #[repr(C)] 4 | struct Vec2 { 5 | x: f32, 6 | y: f32, 7 | } 8 | #[repr(C)] 9 | struct Vertex { 10 | pos: Vec2, 11 | uv: Vec2, 12 | } 13 | 14 | struct Stage { 15 | ctx: Box, 16 | 17 | pipeline: Pipeline, 18 | bindings: Bindings, 19 | } 20 | 21 | impl Stage { 22 | pub fn new() -> Stage { 23 | let mut ctx: Box = window::new_rendering_backend(); 24 | 25 | #[rustfmt::skip] 26 | let vertices: [Vertex; 4] = [ 27 | Vertex { pos : Vec2 { x: -0.5, y: -0.5 }, uv: Vec2 { x: 0., y: 0. } }, 28 | Vertex { pos : Vec2 { x: 0.5, y: -0.5 }, uv: Vec2 { x: 1., y: 0. } }, 29 | Vertex { pos : Vec2 { x: 0.5, y: 0.5 }, uv: Vec2 { x: 1., y: 1. } }, 30 | Vertex { pos : Vec2 { x: -0.5, y: 0.5 }, uv: Vec2 { x: 0., y: 1. } }, 31 | ]; 32 | let vertex_buffer = ctx.new_buffer( 33 | BufferType::VertexBuffer, 34 | BufferUsage::Immutable, 35 | BufferSource::slice(&vertices), 36 | ); 37 | 38 | let indices: [u16; 6] = [0, 1, 2, 0, 2, 3]; 39 | let index_buffer = ctx.new_buffer( 40 | BufferType::IndexBuffer, 41 | BufferUsage::Immutable, 42 | BufferSource::slice(&indices), 43 | ); 44 | 45 | let pixels: [u8; 4 * 4 * 4] = [ 46 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 47 | 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 48 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 49 | 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 50 | 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 51 | ]; 52 | let texture = ctx.new_texture_from_rgba8(4, 4, &pixels); 53 | 54 | let bindings = Bindings { 55 | vertex_buffers: vec![vertex_buffer], 56 | index_buffer: index_buffer, 57 | images: vec![texture], 58 | }; 59 | 60 | let shader = ctx 61 | .new_shader( 62 | match ctx.info().backend { 63 | Backend::OpenGl => ShaderSource::Glsl { 64 | vertex: shader::VERTEX, 65 | fragment: shader::FRAGMENT, 66 | }, 67 | Backend::Metal => ShaderSource::Msl { 68 | program: shader::METAL, 69 | }, 70 | }, 71 | shader::meta(), 72 | ) 73 | .unwrap(); 74 | 75 | let pipeline = ctx.new_pipeline( 76 | &[BufferLayout::default()], 77 | &[ 78 | VertexAttribute::new("in_pos", VertexFormat::Float2), 79 | VertexAttribute::new("in_uv", VertexFormat::Float2), 80 | ], 81 | shader, 82 | PipelineParams::default(), 83 | ); 84 | 85 | Stage { 86 | pipeline, 87 | bindings, 88 | ctx, 89 | } 90 | } 91 | } 92 | 93 | impl EventHandler for Stage { 94 | fn update(&mut self) {} 95 | 96 | fn draw(&mut self) { 97 | let t = date::now(); 98 | 99 | self.ctx.begin_default_pass(Default::default()); 100 | 101 | self.ctx.apply_pipeline(&self.pipeline); 102 | self.ctx.apply_bindings(&self.bindings); 103 | for i in 0..10 { 104 | let t = t + i as f64 * 0.3; 105 | 106 | self.ctx 107 | .apply_uniforms(UniformsSource::table(&shader::Uniforms { 108 | offset: (t.sin() as f32 * 0.5, (t * 3.).cos() as f32 * 0.5), 109 | })); 110 | self.ctx.draw(0, 6, 1); 111 | } 112 | self.ctx.end_render_pass(); 113 | 114 | self.ctx.commit_frame(); 115 | } 116 | } 117 | 118 | fn main() { 119 | let mut conf = conf::Conf::default(); 120 | let metal = std::env::args().nth(1).as_deref() == Some("metal"); 121 | conf.platform.apple_gfx_api = if metal { 122 | conf::AppleGfxApi::Metal 123 | } else { 124 | conf::AppleGfxApi::OpenGl 125 | }; 126 | 127 | miniquad::start(conf, move || Box::new(Stage::new())); 128 | } 129 | 130 | mod shader { 131 | use miniquad::*; 132 | 133 | pub const VERTEX: &str = r#"#version 100 134 | attribute vec2 in_pos; 135 | attribute vec2 in_uv; 136 | 137 | uniform vec2 offset; 138 | 139 | varying lowp vec2 texcoord; 140 | 141 | void main() { 142 | gl_Position = vec4(in_pos + offset, 0, 1); 143 | texcoord = in_uv; 144 | }"#; 145 | 146 | pub const FRAGMENT: &str = r#"#version 100 147 | varying lowp vec2 texcoord; 148 | 149 | uniform sampler2D tex; 150 | 151 | void main() { 152 | gl_FragColor = texture2D(tex, texcoord); 153 | }"#; 154 | 155 | pub const METAL: &str = r#" 156 | #include 157 | 158 | using namespace metal; 159 | 160 | struct Uniforms 161 | { 162 | float2 offset; 163 | }; 164 | 165 | struct Vertex 166 | { 167 | float2 in_pos [[attribute(0)]]; 168 | float2 in_uv [[attribute(1)]]; 169 | }; 170 | 171 | struct RasterizerData 172 | { 173 | float4 position [[position]]; 174 | float2 uv [[user(locn0)]]; 175 | }; 176 | 177 | vertex RasterizerData vertexShader( 178 | Vertex v [[stage_in]], 179 | constant Uniforms& uniforms [[buffer(0)]]) 180 | { 181 | RasterizerData out; 182 | 183 | out.position = float4(v.in_pos.xy + uniforms.offset, 0.0, 1.0); 184 | out.uv = v.in_uv; 185 | 186 | return out; 187 | } 188 | 189 | fragment float4 fragmentShader(RasterizerData in [[stage_in]], texture2d tex [[texture(0)]], sampler texSmplr [[sampler(0)]]) 190 | { 191 | return tex.sample(texSmplr, in.uv); 192 | }"#; 193 | 194 | pub fn meta() -> ShaderMeta { 195 | ShaderMeta { 196 | images: vec!["tex".to_string()], 197 | uniforms: UniformBlockLayout { 198 | uniforms: vec![UniformDesc::new("offset", UniformType::Float2)], 199 | }, 200 | } 201 | } 202 | 203 | #[repr(C)] 204 | pub struct Uniforms { 205 | pub offset: (f32, f32), 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /examples/triangle.rs: -------------------------------------------------------------------------------- 1 | use miniquad::*; 2 | 3 | #[repr(C)] 4 | struct Vertex { 5 | pos: [f32; 2], 6 | color: [f32; 4], 7 | } 8 | 9 | struct Stage { 10 | pipeline: Pipeline, 11 | bindings: Bindings, 12 | ctx: Box, 13 | } 14 | 15 | impl Stage { 16 | pub fn new() -> Stage { 17 | let mut ctx: Box = window::new_rendering_backend(); 18 | 19 | #[rustfmt::skip] 20 | let vertices: [Vertex; 3] = [ 21 | Vertex { pos : [ -0.5, -0.5 ], color: [1., 0., 0., 1.] }, 22 | Vertex { pos : [ 0.5, -0.5 ], color: [0., 1., 0., 1.] }, 23 | Vertex { pos : [ 0.0, 0.5 ], color: [0., 0., 1., 1.] }, 24 | ]; 25 | let vertex_buffer = ctx.new_buffer( 26 | BufferType::VertexBuffer, 27 | BufferUsage::Immutable, 28 | BufferSource::slice(&vertices), 29 | ); 30 | 31 | let indices: [u16; 3] = [0, 1, 2]; 32 | let index_buffer = ctx.new_buffer( 33 | BufferType::IndexBuffer, 34 | BufferUsage::Immutable, 35 | BufferSource::slice(&indices), 36 | ); 37 | 38 | let bindings = Bindings { 39 | vertex_buffers: vec![vertex_buffer], 40 | index_buffer: index_buffer, 41 | images: vec![], 42 | }; 43 | 44 | let shader = ctx 45 | .new_shader( 46 | match ctx.info().backend { 47 | Backend::OpenGl => ShaderSource::Glsl { 48 | vertex: shader::VERTEX, 49 | fragment: shader::FRAGMENT, 50 | }, 51 | Backend::Metal => ShaderSource::Msl { 52 | program: shader::METAL, 53 | }, 54 | }, 55 | shader::meta(), 56 | ) 57 | .unwrap(); 58 | 59 | let pipeline = ctx.new_pipeline( 60 | &[BufferLayout::default()], 61 | &[ 62 | VertexAttribute::new("in_pos", VertexFormat::Float2), 63 | VertexAttribute::new("in_color", VertexFormat::Float4), 64 | ], 65 | shader, 66 | PipelineParams::default(), 67 | ); 68 | 69 | Stage { 70 | pipeline, 71 | bindings, 72 | ctx, 73 | } 74 | } 75 | } 76 | 77 | impl EventHandler for Stage { 78 | fn update(&mut self) {} 79 | 80 | fn draw(&mut self) { 81 | self.ctx.begin_default_pass(Default::default()); 82 | 83 | self.ctx.apply_pipeline(&self.pipeline); 84 | self.ctx.apply_bindings(&self.bindings); 85 | self.ctx.draw(0, 3, 1); 86 | self.ctx.end_render_pass(); 87 | 88 | self.ctx.commit_frame(); 89 | } 90 | } 91 | 92 | fn main() { 93 | let mut conf = conf::Conf::default(); 94 | let metal = std::env::args().nth(1).as_deref() == Some("metal"); 95 | conf.platform.apple_gfx_api = if metal { 96 | conf::AppleGfxApi::Metal 97 | } else { 98 | conf::AppleGfxApi::OpenGl 99 | }; 100 | 101 | miniquad::start(conf, move || Box::new(Stage::new())); 102 | } 103 | 104 | mod shader { 105 | use miniquad::*; 106 | 107 | pub const VERTEX: &str = r#"#version 100 108 | attribute vec2 in_pos; 109 | attribute vec4 in_color; 110 | 111 | varying lowp vec4 color; 112 | 113 | void main() { 114 | gl_Position = vec4(in_pos, 0, 1); 115 | color = in_color; 116 | }"#; 117 | 118 | pub const FRAGMENT: &str = r#"#version 100 119 | varying lowp vec4 color; 120 | 121 | void main() { 122 | gl_FragColor = color; 123 | }"#; 124 | 125 | pub const METAL: &str = r#" 126 | #include 127 | 128 | using namespace metal; 129 | 130 | struct Vertex 131 | { 132 | float2 in_pos [[attribute(0)]]; 133 | float4 in_color [[attribute(1)]]; 134 | }; 135 | 136 | struct RasterizerData 137 | { 138 | float4 position [[position]]; 139 | float4 color [[user(locn0)]]; 140 | }; 141 | 142 | vertex RasterizerData vertexShader(Vertex v [[stage_in]]) 143 | { 144 | RasterizerData out; 145 | 146 | out.position = float4(v.in_pos.xy, 0.0, 1.0); 147 | out.color = v.in_color; 148 | 149 | return out; 150 | } 151 | 152 | fragment float4 fragmentShader(RasterizerData in [[stage_in]]) 153 | { 154 | return in.color; 155 | }"#; 156 | 157 | pub fn meta() -> ShaderMeta { 158 | ShaderMeta { 159 | images: vec![], 160 | uniforms: UniformBlockLayout { uniforms: vec![] }, 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /examples/triangle_color4b.rs: -------------------------------------------------------------------------------- 1 | use miniquad::*; 2 | 3 | #[repr(C)] 4 | struct Vertex { 5 | pos: [f32; 2], 6 | color: [u8; 4], 7 | } 8 | 9 | struct Stage { 10 | pipeline: Pipeline, 11 | bindings: Bindings, 12 | ctx: Box, 13 | } 14 | 15 | impl Stage { 16 | pub fn new() -> Stage { 17 | let mut ctx: Box = window::new_rendering_backend(); 18 | 19 | #[rustfmt::skip] 20 | let vertices: [Vertex; 3] = [ 21 | Vertex { pos : [ -0.5, -0.5 ], color: [0xFF, 0, 0, 0xFF] }, 22 | Vertex { pos : [ 0.5, -0.5 ], color: [0, 0xFF, 0, 0xFF] }, 23 | Vertex { pos : [ 0.0, 0.5 ], color: [0, 0, 0xFF, 0xFF] }, 24 | ]; 25 | let vertex_buffer = ctx.new_buffer( 26 | BufferType::VertexBuffer, 27 | BufferUsage::Immutable, 28 | BufferSource::slice(&vertices), 29 | ); 30 | 31 | let indices: [u16; 3] = [0, 1, 2]; 32 | let index_buffer = ctx.new_buffer( 33 | BufferType::IndexBuffer, 34 | BufferUsage::Immutable, 35 | BufferSource::slice(&indices), 36 | ); 37 | 38 | let bindings = Bindings { 39 | vertex_buffers: vec![vertex_buffer], 40 | index_buffer: index_buffer, 41 | images: vec![], 42 | }; 43 | 44 | let shader = ctx 45 | .new_shader( 46 | match ctx.info().backend { 47 | Backend::OpenGl => ShaderSource::Glsl { 48 | vertex: shader::VERTEX, 49 | fragment: shader::FRAGMENT, 50 | }, 51 | Backend::Metal => ShaderSource::Msl { 52 | program: shader::METAL, 53 | }, 54 | }, 55 | shader::meta(), 56 | ) 57 | .unwrap(); 58 | 59 | let pipeline = ctx.new_pipeline( 60 | &[BufferLayout::default()], 61 | &[ 62 | VertexAttribute::new("in_pos", VertexFormat::Float2), 63 | VertexAttribute { 64 | gl_pass_as_float: false, 65 | ..VertexAttribute::new("in_color", VertexFormat::Byte4) 66 | }, 67 | ], 68 | shader, 69 | PipelineParams::default(), 70 | ); 71 | 72 | Stage { 73 | pipeline, 74 | bindings, 75 | ctx, 76 | } 77 | } 78 | } 79 | 80 | impl EventHandler for Stage { 81 | fn update(&mut self) {} 82 | 83 | fn draw(&mut self) { 84 | self.ctx.begin_default_pass(Default::default()); 85 | 86 | self.ctx.apply_pipeline(&self.pipeline); 87 | self.ctx.apply_bindings(&self.bindings); 88 | self.ctx.draw(0, 3, 1); 89 | self.ctx.end_render_pass(); 90 | 91 | self.ctx.commit_frame(); 92 | } 93 | } 94 | 95 | fn main() { 96 | let mut conf = conf::Conf::default(); 97 | let metal = std::env::args().nth(1).as_deref() == Some("metal"); 98 | conf.platform.apple_gfx_api = if metal { 99 | conf::AppleGfxApi::Metal 100 | } else { 101 | conf::AppleGfxApi::OpenGl 102 | }; 103 | miniquad::start(conf, move || Box::new(Stage::new())); 104 | } 105 | 106 | mod shader { 107 | use miniquad::*; 108 | 109 | pub const VERTEX: &str = r#"#version 150 110 | in vec2 in_pos; 111 | in lowp uvec4 in_color; 112 | 113 | out lowp vec4 color; 114 | 115 | void main() { 116 | gl_Position = vec4(in_pos, 0, 1); 117 | color = vec4(in_color) / 255.0; 118 | }"#; 119 | 120 | pub const FRAGMENT: &str = r#"#version 150 121 | in lowp vec4 color; 122 | out vec4 frag_color; 123 | 124 | void main() { 125 | frag_color = color; 126 | }"#; 127 | 128 | pub const METAL: &str = r#" 129 | #include 130 | 131 | using namespace metal; 132 | 133 | struct Vertex 134 | { 135 | float2 in_pos [[attribute(0)]]; 136 | uint4 in_color [[attribute(1)]]; 137 | }; 138 | 139 | struct RasterizerData 140 | { 141 | float4 position [[position]]; 142 | float4 color [[user(locn0)]]; 143 | }; 144 | 145 | vertex RasterizerData vertexShader(Vertex v [[stage_in]]) 146 | { 147 | RasterizerData out; 148 | 149 | out.position = float4(v.in_pos.xy, 0.0, 1.0); 150 | out.color = float4(v.in_color) / 255.0; 151 | 152 | return out; 153 | } 154 | 155 | fragment float4 fragmentShader(RasterizerData in [[stage_in]]) 156 | { 157 | return in.color; 158 | }"#; 159 | 160 | pub fn meta() -> ShaderMeta { 161 | ShaderMeta { 162 | images: vec![], 163 | uniforms: UniformBlockLayout { uniforms: vec![] }, 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /examples/window_conf.rs: -------------------------------------------------------------------------------- 1 | use miniquad::*; 2 | 3 | struct Stage { 4 | ctx: GlContext, 5 | } 6 | impl EventHandler for Stage { 7 | fn update(&mut self) {} 8 | 9 | fn draw(&mut self) { 10 | self.ctx.clear(Some((0., 1., 0., 1.)), None, None); 11 | } 12 | } 13 | 14 | fn main() { 15 | miniquad::start( 16 | conf::Conf { 17 | window_title: "Miniquad".to_string(), 18 | window_width: 1024, 19 | window_height: 768, 20 | fullscreen: true, 21 | ..Default::default() 22 | }, 23 | || { 24 | Box::new(Stage { 25 | ctx: GlContext::new(), 26 | }) 27 | }, 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /java/QuadNative.java: -------------------------------------------------------------------------------- 1 | package quad_native; 2 | 3 | import android.view.Surface; 4 | 5 | // Java force the MainActivity class to belong to a specially named package 6 | // this package name is forced to be different for each app 7 | // and Java do not have any way to specify a native symbol lookup name.. 8 | // To workaround this - all native callbakcks lives in QuadNative class 9 | public class QuadNative { 10 | // belongs to MainActivity class 11 | public native static void activityOnCreate(Object activity); 12 | public native static void activityOnResume(); 13 | public native static void activityOnPause(); 14 | public native static void activityOnDestroy(); 15 | 16 | // belongs to QuadSurface class 17 | public native static void surfaceOnSurfaceCreated(Surface surface); 18 | public native static void surfaceOnSurfaceDestroyed(Surface surface); 19 | public native static void surfaceOnTouch(int id, int phase, float x, float y); 20 | public native static void surfaceOnSurfaceChanged(Surface surface, int width, int height); 21 | public native static void surfaceOnKeyDown(int keycode); 22 | public native static void surfaceOnKeyUp(int keycode); 23 | public native static void surfaceOnCharacter(int character); 24 | } 25 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | let pkgs = import { }; 2 | in pkgs.mkShell { 3 | buildInputs = with pkgs; [ 4 | rustc 5 | cargo 6 | 7 | libGL 8 | xorg.libX11 9 | xorg.libXi 10 | libxkbcommon 11 | ]; 12 | 13 | shellHook = '' 14 | export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${ 15 | with pkgs; 16 | pkgs.lib.makeLibraryPath [ libGL xorg.libX11 xorg.libXi libxkbcommon ] 17 | }" 18 | ''; 19 | } 20 | -------------------------------------------------------------------------------- /src/conf.rs: -------------------------------------------------------------------------------- 1 | //! Context creation configuration 2 | //! 3 | //! A [`Conf`] struct is used to describe a hardware and platform specific setup, 4 | //! mostly video display settings. 5 | //! 6 | //! ## High DPI rendering 7 | //! 8 | //! You can set the [`Conf::high_dpi`] flag during initialization to request 9 | //! a full-resolution framebuffer on HighDPI displays. The default behaviour 10 | //! is `high_dpi = false`, this means that the application will 11 | //! render to a lower-resolution framebuffer on HighDPI displays and the 12 | //! rendered content will be upscaled by the window system composer. 13 | //! In a HighDPI scenario, you still request the same window size during 14 | //! [`miniquad::start`][super::start], but the framebuffer sizes returned by 15 | //! [`screen_size`] will be scaled up according to the DPI scaling ratio. 16 | //! You can also get a DPI scaling factor with the function [`dpi_scale`]. 17 | //! 18 | //! Here's an example on a Mac with Retina display: 19 | //! ```ignore 20 | //! Conf { 21 | //! width = 640, 22 | //! height = 480, 23 | //! high_dpi = true, 24 | //! .. Default::default() 25 | //! }; 26 | //! ``` 27 | //! 28 | //! The functions [`screen_size`] and [`dpi_scale`] will return the following values: 29 | //! ```bash 30 | //! screen_size -> (1280, 960) 31 | //! dpi_scale -> 2.0 32 | //! ``` 33 | //! 34 | //! If the `high_dpi` flag is false, or you're not running on a Retina display, 35 | //! the values would be: 36 | //! ```bash 37 | //! screen_size -> (640, 480) 38 | //! dpi_scale -> 1.0 39 | //! ``` 40 | //! 41 | //! [`dpi_scale`]: super::window::dpi_scale 42 | //! [`screen_size`]: super::window::screen_size 43 | 44 | /// Specifies how to load an OpenGL context on X11 in Linux. 45 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] 46 | pub enum LinuxX11Gl { 47 | /// Use `libGLX.so` (or `libGLX.so.0`) exclusively. Panics if unavailable. 48 | GLXOnly, 49 | /// Use `libEGL.so` (or `libEGL.so.0`) exclusively. Panics if unavailable. 50 | EGLOnly, 51 | /// Prefer `libGLX`; fall back to `libEGL` if `libGLX` is unavailable. 52 | /// This is the default choice. 53 | #[default] 54 | GLXWithEGLFallback, 55 | /// Prefer `libEGL`; fall back to `libGLX` if `libEGL` is unavailable. 56 | EGLWithGLXFallback, 57 | } 58 | 59 | /// On Linux, the backend used for windowing and event handling. 60 | /// 61 | /// Defaults to `X11Only`. The Wayland implementation is currently unstable 62 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] 63 | pub enum LinuxBackend { 64 | /// Use only the X11 backend. Panics if unavailable. This is the default choice. 65 | #[default] 66 | X11Only, 67 | /// Use only the Wayland backend. Panics if unavailable. 68 | WaylandOnly, 69 | /// Prefer X11, fall back to Wayland if X11 is unavailable. 70 | X11WithWaylandFallback, 71 | /// Prefer Wayland, fall back to X11 if Wayland is unavailable. 72 | WaylandWithX11Fallback, 73 | } 74 | 75 | /// On Apple platforms, choose the rendering API for creating contexts. 76 | /// 77 | /// Miniquad always links to Metal.framework (assuming it's present), 78 | /// and links to OpenGL dynamically only if required. 79 | /// 80 | /// Defaults to AppleGfxApi::GL for legacy reasons. 81 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] 82 | pub enum AppleGfxApi { 83 | /// Use OpenGL for Apple platforms. This is the default choice. 84 | #[default] 85 | OpenGl, 86 | /// Use Metal for Apple platforms. 87 | Metal, 88 | } 89 | 90 | /// On the Web, specify which WebGL version to use. 91 | /// 92 | /// While miniquad itself only uses WebGL 1 features, a WebGL 2 context allows to: 93 | /// - Use GLES3 shaders. 94 | /// - Do raw WebGL2 OpenGL calls. 95 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] 96 | pub enum WebGLVersion { 97 | /// Use WebGL 1.0. This is the default choice. 98 | #[default] 99 | WebGL1, 100 | /// Use WebGL 2.0. 101 | WebGL2, 102 | } 103 | 104 | /// On Wayland, specify how to draw client-side decoration (CSD) if server-side decoration (SSD) is 105 | /// not supported (e.g., on GNOME). 106 | /// 107 | /// Defaults to ServerWithLibDecorFallback 108 | #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] 109 | pub enum WaylandDecorations { 110 | /// If SSD is not supported, will try to load `libdecor` to draw CSD. This is the default 111 | /// choice. 112 | #[default] 113 | ServerWithLibDecorFallback, 114 | /// If SSD is not supported, draw a light gray border. 115 | ServerWithMiniquadFallback, 116 | /// If SSD is not supported, no CSD will be drawn. 117 | ServerOnly, 118 | } 119 | 120 | /// Platform-specific settings. 121 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 122 | pub struct Platform { 123 | /// Determines how to load an OpenGL context on X11 (via GLX or EGL). 124 | pub linux_x11_gl: LinuxX11Gl, 125 | 126 | /// Specifies which Linux window system (X11 or Wayland) is preferred or used. 127 | pub linux_backend: LinuxBackend, 128 | 129 | /// Specifies which WebGL version to use on the Web (1.0. or 2.0). 130 | pub webgl_version: WebGLVersion, 131 | 132 | /// Defines which rendering API to use on Apple platforms (Metal or OpenGL). 133 | pub apple_gfx_api: AppleGfxApi, 134 | 135 | /// Optional swap interval (vertical sync). 136 | /// 137 | /// Note that this is highly platform- and driver-dependent. 138 | /// There is no guarantee the FPS will match the specified `swap_interval`. 139 | /// In other words, `swap_interval` is only a hint to the GPU driver and 140 | /// not a reliable way to limit the game's FPS. 141 | pub swap_interval: Option, 142 | 143 | /// If `true`, the event loop will block until [`schedule_update`] is called. 144 | /// 145 | /// This can reduce CPU usage to nearly zero while waiting for events. 146 | /// 147 | /// It is recommended to call `schedule_update` at the end of `resize_event` 148 | /// or any relevant mouse/keyboard input. 149 | /// 150 | /// `schedule_update` may be used from other threads to "wake up" the window. 151 | /// 152 | /// [`schedule_update`]: super::window::schedule_update 153 | pub blocking_event_loop: bool, 154 | 155 | /// If `true`, the framebuffer includes an alpha channel. 156 | /// Currently supported only on Android. 157 | /// 158 | /// - TODO: Make it works on web, on web it should make a transparent HTML5 canvas 159 | /// - TODO: Document(and check) what does it actually mean on android. Transparent window? 160 | pub framebuffer_alpha: bool, 161 | 162 | /// On Wayland, specifies how to draw client-side decoration (CSD) if server-side decoration (SSD) is 163 | /// not supported (e.g., on GNOME). 164 | pub wayland_decorations: WaylandDecorations, 165 | 166 | /// Set the `WM_CLASS` window property on X11 and the `app_id` on Wayland. This is used 167 | /// by gnome to determine the window icon (together with an external `.desktop` file). 168 | // in fact `WM_CLASS` contains two strings "instance name" and "class name" 169 | // for most purposes they are the same so we just use class name for simplicity 170 | // https://unix.stackexchange.com/questions/494169/ 171 | pub linux_wm_class: &'static str, 172 | } 173 | 174 | impl Default for Platform { 175 | fn default() -> Platform { 176 | Platform { 177 | linux_x11_gl: LinuxX11Gl::default(), 178 | linux_backend: LinuxBackend::default(), 179 | apple_gfx_api: AppleGfxApi::default(), 180 | webgl_version: WebGLVersion::default(), 181 | blocking_event_loop: false, 182 | swap_interval: None, 183 | framebuffer_alpha: false, 184 | wayland_decorations: WaylandDecorations::default(), 185 | linux_wm_class: "miniquad-application", 186 | } 187 | } 188 | } 189 | 190 | /// Describes a hardware and platform-specific setup. 191 | #[derive(Debug)] 192 | pub struct Conf { 193 | /// Window title. Defaults to an empty string. 194 | pub window_title: String, 195 | 196 | /// Preferred window width (ignored on WASM/Android). 197 | /// Defaults to `800`. 198 | pub window_width: i32, 199 | 200 | /// Preferred window height (ignored on WASM/Android). 201 | /// Defaults to `600`. 202 | pub window_height: i32, 203 | 204 | /// If `true`, the rendering canvas is scaled for HighDPI displays. 205 | /// Defaults to `false`. 206 | pub high_dpi: bool, 207 | 208 | /// If `true`, create the window in fullscreen mode (ignored on WASM/Android). 209 | /// Defaults to `false`. 210 | pub fullscreen: bool, 211 | 212 | /// MSAA sample count. 213 | /// Defaults to `1`. 214 | pub sample_count: i32, 215 | 216 | /// If `true`, the user can resize the window. 217 | pub window_resizable: bool, 218 | 219 | /// Optional icon data used by the OS where applicable: 220 | /// - On Windows, taskbar/title bar icon 221 | /// - On macOS, Dock/title bar icon 222 | /// - TODO: Favicon on HTML5 223 | /// - TODO: Taskbar/title bar icon on Linux (depends on WM) 224 | /// - Note: on gnome, icon is determined using `WM_CLASS` (can be set under [`Platform`]) and 225 | /// an external `.desktop` file 226 | pub icon: Option, 227 | 228 | /// Platform-specific hints (e.g., context creation, driver settings). 229 | pub platform: Platform, 230 | } 231 | 232 | /// Icon image in three levels of detail. 233 | #[derive(Clone)] 234 | pub struct Icon { 235 | /// 16 * 16 image of RGBA pixels (each 4 * u8) in row-major order. 236 | pub small: [u8; 16 * 16 * 4], 237 | /// 32 x 32 image of RGBA pixels (each 4 * u8) in row-major order. 238 | pub medium: [u8; 32 * 32 * 4], 239 | /// 64 x 64 image of RGBA pixels (each 4 * u8) in row-major order. 240 | pub big: [u8; 64 * 64 * 4], 241 | } 242 | 243 | impl Icon { 244 | pub fn miniquad_logo() -> Icon { 245 | Icon { 246 | small: crate::default_icon::SMALL, 247 | medium: crate::default_icon::MEDIUM, 248 | big: crate::default_icon::BIG, 249 | } 250 | } 251 | } 252 | // Printing 64x64 array with a default formatter is not meaningfull, 253 | // so debug will skip the data fields of an Icon 254 | impl std::fmt::Debug for Icon { 255 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 256 | f.debug_struct("Icon").finish() 257 | } 258 | } 259 | 260 | // reasonable defaults for PC and mobiles are slightly different 261 | #[cfg(not(target_os = "android"))] 262 | impl Default for Conf { 263 | fn default() -> Conf { 264 | Conf { 265 | window_title: "".to_owned(), 266 | window_width: 800, 267 | window_height: 600, 268 | high_dpi: false, 269 | fullscreen: false, 270 | sample_count: 1, 271 | window_resizable: true, 272 | icon: Some(Icon::miniquad_logo()), 273 | platform: Default::default(), 274 | } 275 | } 276 | } 277 | 278 | #[cfg(target_os = "android")] 279 | impl Default for Conf { 280 | fn default() -> Conf { 281 | Conf { 282 | window_title: "".to_owned(), 283 | window_width: 800, 284 | window_height: 600, 285 | high_dpi: true, 286 | fullscreen: true, // 287 | sample_count: 1, 288 | window_resizable: false, // 289 | icon: Some(Icon::miniquad_logo()), 290 | platform: Default::default(), 291 | } 292 | } 293 | } 294 | -------------------------------------------------------------------------------- /src/event.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)] 2 | #[repr(u8)] 3 | pub enum MouseButton { 4 | Left = 0, 5 | Middle = 1, 6 | Right = 2, 7 | Unknown = 255, 8 | } 9 | 10 | #[derive(Debug, Copy, Clone)] 11 | pub struct Touch { 12 | pub id: u32, 13 | pub x: f32, 14 | pub y: f32, 15 | } 16 | 17 | #[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)] 18 | #[repr(u16)] 19 | /// These keycode values are based off of X11's `keysymdef.h`. 20 | /// Missing keycodes from that list are given the prefix 0x01. 21 | pub enum KeyCode { 22 | Space = 0x0020, 23 | Apostrophe = 0x0027, 24 | Comma = 0x002c, 25 | Minus = 0x002d, 26 | Period = 0x002e, 27 | Slash = 0x002f, 28 | Key0 = 0x0030, 29 | Key1 = 0x0031, 30 | Key2 = 0x0032, 31 | Key3 = 0x0033, 32 | Key4 = 0x0034, 33 | Key5 = 0x0035, 34 | Key6 = 0x0036, 35 | Key7 = 0x0037, 36 | Key8 = 0x0038, 37 | Key9 = 0x0039, 38 | Semicolon = 0x003b, 39 | Equal = 0x003d, 40 | A = 0x0041, 41 | B = 0x0042, 42 | C = 0x0043, 43 | D = 0x0044, 44 | E = 0x0045, 45 | F = 0x0046, 46 | G = 0x0047, 47 | H = 0x0048, 48 | I = 0x0049, 49 | J = 0x004a, 50 | K = 0x004b, 51 | L = 0x004c, 52 | M = 0x004d, 53 | N = 0x004e, 54 | O = 0x004f, 55 | P = 0x0050, 56 | Q = 0x0051, 57 | R = 0x0052, 58 | S = 0x0053, 59 | T = 0x0054, 60 | U = 0x0055, 61 | V = 0x0056, 62 | W = 0x0057, 63 | X = 0x0058, 64 | Y = 0x0059, 65 | Z = 0x005a, 66 | LeftBracket = 0x005b, 67 | Backslash = 0x005c, 68 | RightBracket = 0x005d, 69 | GraveAccent = 0x0060, 70 | World1 = 0x0100, 71 | World2 = 0x0101, 72 | Escape = 0xff1b, 73 | Enter = 0xff0d, 74 | Tab = 0xff09, 75 | Backspace = 0xff08, 76 | Insert = 0xff63, 77 | Delete = 0xffff, 78 | Right = 0xff53, 79 | Left = 0xff51, 80 | Down = 0xff54, 81 | Up = 0xff52, 82 | PageUp = 0xff55, 83 | PageDown = 0xff56, 84 | Home = 0xff50, 85 | End = 0xff57, 86 | CapsLock = 0xffe5, 87 | ScrollLock = 0xff14, 88 | NumLock = 0xff7f, 89 | PrintScreen = 0xfd1d, 90 | Pause = 0xff13, 91 | F1 = 0xffbe, 92 | F2 = 0xffbf, 93 | F3 = 0xffc0, 94 | F4 = 0xffc1, 95 | F5 = 0xffc2, 96 | F6 = 0xffc3, 97 | F7 = 0xffc4, 98 | F8 = 0xffc5, 99 | F9 = 0xffc6, 100 | F10 = 0xffc7, 101 | F11 = 0xffc8, 102 | F12 = 0xffc9, 103 | F13 = 0xffca, 104 | F14 = 0xffcb, 105 | F15 = 0xffcc, 106 | F16 = 0xffcd, 107 | F17 = 0xffce, 108 | F18 = 0xffcf, 109 | F19 = 0xffd0, 110 | F20 = 0xffd1, 111 | F21 = 0xffd2, 112 | F22 = 0xffd3, 113 | F23 = 0xffd4, 114 | F24 = 0xffd5, 115 | F25 = 0xffd6, 116 | Kp0 = 0xffb0, 117 | Kp1 = 0xffb1, 118 | Kp2 = 0xffb2, 119 | Kp3 = 0xffb3, 120 | Kp4 = 0xffb4, 121 | Kp5 = 0xffb5, 122 | Kp6 = 0xffb6, 123 | Kp7 = 0xffb7, 124 | Kp8 = 0xffb8, 125 | Kp9 = 0xffb9, 126 | KpDecimal = 0xffae, 127 | KpDivide = 0xffaf, 128 | KpMultiply = 0xffaa, 129 | KpSubtract = 0xffad, 130 | KpAdd = 0xffab, 131 | KpEnter = 0xff8d, 132 | KpEqual = 0xffbd, 133 | LeftShift = 0xffe1, 134 | LeftControl = 0xffe3, 135 | LeftAlt = 0xffe9, 136 | LeftSuper = 0xffeb, 137 | RightShift = 0xffe2, 138 | RightControl = 0xffe4, 139 | RightAlt = 0xffea, 140 | RightSuper = 0xffec, 141 | Menu = 0xff67, 142 | // Android back button 143 | Back = 0xff04, 144 | Unknown = 0x01ff, 145 | } 146 | 147 | #[derive(Debug, Copy, Clone, PartialEq, Default)] 148 | pub struct KeyMods { 149 | pub shift: bool, 150 | pub ctrl: bool, 151 | pub alt: bool, 152 | pub logo: bool, 153 | } 154 | 155 | #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] 156 | pub enum TouchPhase { 157 | Started, 158 | Moved, 159 | Ended, 160 | Cancelled, 161 | } 162 | 163 | /// A trait defining event callbacks. 164 | pub trait EventHandler { 165 | /// On most platforms update() and draw() are called each frame, sequentially, 166 | /// draw right after update. 167 | /// But on Android (and maybe some other platforms in the future) update might 168 | /// be called without draw. 169 | /// When the app is in background, Android destroys the rendering surface, 170 | /// while app is still alive and can do some usefull calculations. 171 | /// Note that in this case drawing from update may lead to crashes. 172 | fn update(&mut self); 173 | fn draw(&mut self); 174 | fn resize_event(&mut self, _width: f32, _height: f32) {} 175 | fn mouse_motion_event(&mut self, _x: f32, _y: f32) {} 176 | fn mouse_wheel_event(&mut self, _x: f32, _y: f32) {} 177 | fn mouse_button_down_event(&mut self, _button: MouseButton, _x: f32, _y: f32) {} 178 | fn mouse_button_up_event(&mut self, _button: MouseButton, _x: f32, _y: f32) {} 179 | 180 | fn char_event(&mut self, _character: char, _keymods: KeyMods, _repeat: bool) {} 181 | 182 | fn key_down_event(&mut self, _keycode: KeyCode, _keymods: KeyMods, _repeat: bool) {} 183 | 184 | /// Note: you are not always guaranteed to receive a key_up event. For example on 185 | /// Wayland when leaving the focused window, the key_up event will not be caught. 186 | fn key_up_event(&mut self, _keycode: KeyCode, _keymods: KeyMods) {} 187 | 188 | /// Default implementation emulates mouse clicks 189 | fn touch_event(&mut self, phase: TouchPhase, _id: u64, x: f32, y: f32) { 190 | if phase == TouchPhase::Started { 191 | self.mouse_button_down_event(MouseButton::Left, x, y); 192 | } 193 | 194 | if phase == TouchPhase::Ended { 195 | self.mouse_button_up_event(MouseButton::Left, x, y); 196 | } 197 | 198 | if phase == TouchPhase::Moved { 199 | self.mouse_motion_event(x, y); 200 | } 201 | } 202 | 203 | /// Represents raw hardware mouse motion event 204 | /// Note that these events are delivered regardless of input focus and not in pixels, but in 205 | /// hardware units instead. And those units may be different from pixels depending on the target platform 206 | fn raw_mouse_motion(&mut self, _dx: f32, _dy: f32) {} 207 | 208 | /// Window has been minimized 209 | /// Right now is only implemented on Android, Windows, OSX, X11 and wasm, 210 | /// On Andoid window_minimized_event is called on a Pause ndk callback 211 | /// On X11, OSX, Windows and wasm it will be called on focus change events. 212 | fn window_minimized_event(&mut self) {} 213 | 214 | /// Window has been restored 215 | /// Right now is only implemented on Android, X11 and wasm, 216 | /// On Andoid window_minimized_event is called on a Pause ndk callback 217 | /// On X11 and wasm it will be called on focus change events. 218 | fn window_restored_event(&mut self) {} 219 | 220 | /// This event is sent when the userclicks the window's close button 221 | /// or application code calls the ctx.request_quit() function. The event 222 | /// handler callback code can handle this event by calling 223 | /// ctx.cancel_quit() to cancel the quit. 224 | /// If the event is ignored, the application will quit as usual. 225 | fn quit_requested_event(&mut self) {} 226 | 227 | /// A file has been dropped over the application. 228 | /// Applications can request the number of dropped files with 229 | /// `ctx.dropped_file_count()`, path of an individual file with 230 | /// `ctx.dropped_file_path()`, and for wasm targets the file bytes 231 | /// can be requested with `ctx.dropped_file_bytes()`. 232 | fn files_dropped_event(&mut self) {} 233 | } 234 | -------------------------------------------------------------------------------- /src/fs.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "ios")] 2 | use crate::native::ios; 3 | 4 | #[derive(Debug)] 5 | pub enum Error { 6 | IOError(std::io::Error), 7 | DownloadFailed, 8 | AndroidAssetLoadingError, 9 | /// MainBundle pathForResource returned null 10 | IOSAssetNoSuchFile, 11 | /// NSData dataWithContentsOfFile or data.bytes are null 12 | IOSAssetNoData, 13 | } 14 | 15 | impl From for Error { 16 | fn from(e: std::io::Error) -> Error { 17 | Error::IOError(e) 18 | } 19 | } 20 | 21 | impl std::fmt::Display for Error { 22 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 23 | match self { 24 | Self::IOError(e) => write!(f, "I/O error: {e}"), 25 | Self::DownloadFailed => write!(f, "Download failed"), 26 | Self::AndroidAssetLoadingError => write!(f, "[android] Failed to load asset"), 27 | Self::IOSAssetNoSuchFile => write!(f, "[ios] No such asset file"), 28 | Self::IOSAssetNoData => write!(f, "[ios] No data in asset file"), 29 | } 30 | } 31 | } 32 | 33 | impl std::error::Error for Error {} 34 | 35 | pub type Response = Result, Error>; 36 | 37 | /// Filesystem path on desktops or HTTP URL in WASM 38 | pub fn load_file(path: &str, on_loaded: F) { 39 | #[cfg(target_arch = "wasm32")] 40 | wasm::load_file(path, on_loaded); 41 | 42 | #[cfg(target_os = "android")] 43 | load_file_android(path, on_loaded); 44 | 45 | #[cfg(target_os = "ios")] 46 | ios::load_file(path, on_loaded); 47 | 48 | #[cfg(not(any(target_arch = "wasm32", target_os = "android", target_os = "ios")))] 49 | load_file_desktop(path, on_loaded); 50 | } 51 | 52 | #[cfg(target_os = "android")] 53 | fn load_file_android(path: &str, on_loaded: F) { 54 | fn load_file_sync(path: &str) -> Response { 55 | use crate::native; 56 | 57 | let filename = std::ffi::CString::new(path).unwrap(); 58 | 59 | let mut data: native::android_asset = unsafe { std::mem::zeroed() }; 60 | 61 | unsafe { native::android::load_asset(filename.as_ptr(), &mut data as _) }; 62 | 63 | if data.content.is_null() == false { 64 | let slice = 65 | unsafe { std::slice::from_raw_parts(data.content, data.content_length as _) }; 66 | let response = slice.iter().map(|c| *c as _).collect::>(); 67 | Ok(response) 68 | } else { 69 | Err(Error::AndroidAssetLoadingError) 70 | } 71 | } 72 | 73 | let response = load_file_sync(path); 74 | 75 | on_loaded(response); 76 | } 77 | 78 | #[cfg(target_arch = "wasm32")] 79 | mod wasm { 80 | use super::Response; 81 | use crate::native; 82 | 83 | use std::{cell::RefCell, collections::HashMap, thread_local}; 84 | 85 | thread_local! { 86 | #[allow(clippy::type_complexity)] 87 | static FILES: RefCell>> = RefCell::new(HashMap::new()); 88 | } 89 | 90 | #[no_mangle] 91 | pub extern "C" fn file_loaded(file_id: u32) { 92 | use super::Error; 93 | use native::wasm::fs; 94 | 95 | FILES.with(|files| { 96 | let mut files = files.borrow_mut(); 97 | let callback = files 98 | .remove(&file_id) 99 | .unwrap_or_else(|| panic!("Unknown file loaded!")); 100 | let file_len = unsafe { fs::fs_get_buffer_size(file_id) }; 101 | if file_len == -1 { 102 | callback(Err(Error::DownloadFailed)); 103 | } else { 104 | let mut buffer = vec![0; file_len as usize]; 105 | unsafe { fs::fs_take_buffer(file_id, buffer.as_mut_ptr(), file_len as u32) }; 106 | 107 | callback(Ok(buffer)); 108 | } 109 | }) 110 | } 111 | 112 | pub fn load_file(path: &str, on_loaded: F) { 113 | use native::wasm::fs; 114 | use std::ffi::CString; 115 | 116 | let url = CString::new(path).unwrap(); 117 | let file_id = unsafe { fs::fs_load_file(url.as_ptr(), url.as_bytes().len() as u32) }; 118 | FILES.with(|files| { 119 | let mut files = files.borrow_mut(); 120 | files.insert(file_id, Box::new(on_loaded)); 121 | }); 122 | } 123 | } 124 | 125 | #[cfg(not(any(target_arch = "wasm32", target_os = "android", target_os = "ios")))] 126 | fn load_file_desktop(path: &str, on_loaded: F) { 127 | fn load_file_sync(path: &str) -> Response { 128 | use std::fs::File; 129 | use std::io::Read; 130 | 131 | let mut response = vec![]; 132 | let mut file = File::open(path)?; 133 | file.read_to_end(&mut response)?; 134 | Ok(response) 135 | } 136 | 137 | let response = load_file_sync(path); 138 | 139 | on_loaded(response); 140 | } 141 | -------------------------------------------------------------------------------- /src/graphics/gl/cache.rs: -------------------------------------------------------------------------------- 1 | use crate::graphics::*; 2 | 3 | #[derive(Clone, Copy, Debug, PartialEq, Default)] 4 | pub struct VertexAttributeInternal { 5 | pub attr_loc: GLuint, 6 | pub size: i32, 7 | pub type_: GLuint, 8 | pub offset: i64, 9 | pub stride: i32, 10 | pub buffer_index: usize, 11 | pub divisor: i32, 12 | pub gl_pass_as_float: bool, 13 | } 14 | 15 | #[derive(Default, Copy, Clone)] 16 | pub struct CachedAttribute { 17 | pub attribute: VertexAttributeInternal, 18 | pub gl_vbuf: GLuint, 19 | } 20 | 21 | #[derive(Clone, Copy)] 22 | pub struct CachedTexture { 23 | // GL_TEXTURE_2D or GL_TEXTURE_CUBEMAP 24 | pub target: GLuint, 25 | pub texture: GLuint, 26 | } 27 | 28 | pub struct GlCache { 29 | pub stored_index_buffer: GLuint, 30 | pub stored_index_type: Option, 31 | pub stored_vertex_buffer: GLuint, 32 | pub stored_target: GLuint, 33 | pub stored_texture: GLuint, 34 | pub index_buffer: GLuint, 35 | pub index_type: Option, 36 | pub vertex_buffer: GLuint, 37 | pub textures: [CachedTexture; MAX_SHADERSTAGE_IMAGES], 38 | pub cur_pipeline: Option, 39 | pub cur_pass: Option, 40 | pub color_blend: Option, 41 | pub alpha_blend: Option, 42 | pub stencil: Option, 43 | pub color_write: ColorMask, 44 | pub cull_face: CullFace, 45 | pub attributes: [Option; MAX_VERTEX_ATTRIBUTES], 46 | } 47 | 48 | impl GlCache { 49 | pub fn bind_buffer(&mut self, target: GLenum, buffer: GLuint, index_type: Option) { 50 | if target == GL_ARRAY_BUFFER { 51 | if self.vertex_buffer != buffer { 52 | self.vertex_buffer = buffer; 53 | unsafe { 54 | glBindBuffer(target, buffer); 55 | } 56 | } 57 | } else { 58 | if self.index_buffer != buffer { 59 | self.index_buffer = buffer; 60 | unsafe { 61 | glBindBuffer(target, buffer); 62 | } 63 | } 64 | self.index_type = index_type; 65 | } 66 | } 67 | 68 | pub fn store_buffer_binding(&mut self, target: GLenum) { 69 | if target == GL_ARRAY_BUFFER { 70 | self.stored_vertex_buffer = self.vertex_buffer; 71 | } else { 72 | self.stored_index_buffer = self.index_buffer; 73 | self.stored_index_type = self.index_type; 74 | } 75 | } 76 | 77 | pub fn restore_buffer_binding(&mut self, target: GLenum) { 78 | if target == GL_ARRAY_BUFFER { 79 | if self.stored_vertex_buffer != 0 { 80 | self.bind_buffer(target, self.stored_vertex_buffer, None); 81 | self.stored_vertex_buffer = 0; 82 | } 83 | } else if self.stored_index_buffer != 0 { 84 | self.bind_buffer(target, self.stored_index_buffer, self.stored_index_type); 85 | self.stored_index_buffer = 0; 86 | } 87 | } 88 | 89 | pub fn bind_texture(&mut self, slot_index: usize, target: GLuint, texture: GLuint) { 90 | unsafe { 91 | glActiveTexture(GL_TEXTURE0 + slot_index as GLuint); 92 | if self.textures[slot_index].target != target 93 | || self.textures[slot_index].texture != texture 94 | { 95 | let target = if target == 0 { GL_TEXTURE_2D } else { target }; 96 | glBindTexture(target, texture); 97 | self.textures[slot_index] = CachedTexture { target, texture }; 98 | } 99 | } 100 | } 101 | 102 | pub fn store_texture_binding(&mut self, slot_index: usize) { 103 | self.stored_target = self.textures[slot_index].target; 104 | self.stored_texture = self.textures[slot_index].texture; 105 | } 106 | 107 | pub fn restore_texture_binding(&mut self, slot_index: usize) { 108 | self.bind_texture(slot_index, self.stored_target, self.stored_texture); 109 | } 110 | 111 | pub fn clear_buffer_bindings(&mut self) { 112 | self.bind_buffer(GL_ARRAY_BUFFER, 0, None); 113 | self.vertex_buffer = 0; 114 | 115 | self.bind_buffer(GL_ELEMENT_ARRAY_BUFFER, 0, None); 116 | self.index_buffer = 0; 117 | } 118 | 119 | pub fn clear_texture_bindings(&mut self) { 120 | for ix in 0..MAX_SHADERSTAGE_IMAGES { 121 | if self.textures[ix].texture != 0 { 122 | self.bind_texture(ix, self.textures[ix].target, 0); 123 | self.textures[ix] = CachedTexture { 124 | target: 0, 125 | texture: 0, 126 | }; 127 | } 128 | } 129 | } 130 | 131 | pub fn clear_vertex_attributes(&mut self) { 132 | for attr_index in 0..MAX_VERTEX_ATTRIBUTES { 133 | let cached_attr = &mut self.attributes[attr_index]; 134 | 135 | if cached_attr.is_some() { 136 | unsafe { glDisableVertexAttribArray(attr_index as GLuint) }; 137 | } 138 | *cached_attr = None; 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/log.rs: -------------------------------------------------------------------------------- 1 | /// This module is disabled by default 2 | /// 3 | /// Most of the code gleaned from log-rs crate 4 | /// 5 | /// Will send log calls like debug!(), warn!() and error!() to appropriate console_* call on wasm 6 | /// and just println! on PC. 7 | /// If you need better control of log messages - just dont use "log-impl" feature and use appropriate loggers from log-rs 8 | use std::cmp; 9 | 10 | #[repr(usize)] 11 | #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)] 12 | pub enum Level { 13 | /// The "error" level. 14 | /// 15 | /// Designates very serious errors. 16 | Error = 1, // This way these line up with the discriminants for LevelFilter below 17 | /// The "warn" level. 18 | /// 19 | /// Designates hazardous situations. 20 | Warn, 21 | /// The "info" level. 22 | /// 23 | /// Designates useful information. 24 | Info, 25 | /// The "debug" level. 26 | /// 27 | /// Designates lower priority information. 28 | Debug, 29 | /// The "trace" level. 30 | /// 31 | /// Designates very low priority, often extremely verbose, information. 32 | Trace, 33 | } 34 | 35 | impl PartialOrd for Level { 36 | #[inline] 37 | fn partial_cmp(&self, other: &Level) -> Option { 38 | Some(self.cmp(other)) 39 | } 40 | 41 | #[inline] 42 | fn lt(&self, other: &Level) -> bool { 43 | (*self as usize) < *other as usize 44 | } 45 | 46 | #[inline] 47 | fn le(&self, other: &Level) -> bool { 48 | *self as usize <= *other as usize 49 | } 50 | 51 | #[inline] 52 | fn gt(&self, other: &Level) -> bool { 53 | *self as usize > *other as usize 54 | } 55 | 56 | #[inline] 57 | fn ge(&self, other: &Level) -> bool { 58 | *self as usize >= *other as usize 59 | } 60 | } 61 | 62 | impl Ord for Level { 63 | #[inline] 64 | fn cmp(&self, other: &Level) -> cmp::Ordering { 65 | (*self as usize).cmp(&(*other as usize)) 66 | } 67 | } 68 | 69 | #[macro_export(local_inner_macros)] 70 | macro_rules! log { 71 | (target: $target:expr, $lvl:expr, $message:expr) => ({ 72 | let lvl = $lvl; 73 | //if lvl <= $crate::STATIC_MAX_LEVEL && lvl <= $crate::max_level() { 74 | // ensure that $message is a valid format string literal 75 | let _ = __log_format_args!($message); 76 | $crate::log::__private_api_log_lit( 77 | $message, 78 | lvl, 79 | &($target, __log_module_path!(), __log_file!(), __log_line!()), 80 | ); 81 | //} 82 | }); 83 | (target: $target:expr, $lvl:expr, $($arg:tt)+) => ({ 84 | let lvl = $lvl; 85 | //if lvl <= $crate::STATIC_MAX_LEVEL && lvl <= $crate::max_level() { 86 | $crate::log::__private_api_log_lit( 87 | &__log_format_args!($($arg)+), 88 | lvl, 89 | &($target, __log_module_path!(), __log_file!(), __log_line!()), 90 | ); 91 | //} 92 | }); 93 | ($lvl:expr, $($arg:tt)+) => (log!(target: __log_module_path!(), $lvl, $($arg)+)) 94 | } 95 | 96 | #[macro_export(local_inner_macros)] 97 | macro_rules! error { 98 | (target: $target:expr, $($arg:tt)+) => ( 99 | log!(target: $target, $crate::Level::Error, $($arg)+); 100 | ); 101 | ($($arg:tt)+) => ( 102 | log!($crate::log::Level::Error, $($arg)+); 103 | ) 104 | } 105 | 106 | #[macro_export(local_inner_macros)] 107 | macro_rules! warn { 108 | (target: $target:expr, $($arg:tt)+) => ( 109 | log!(target: $target, $crate::Level::Warn, $($arg)+); 110 | ); 111 | ($($arg:tt)+) => ( 112 | log!($crate::log::Level::Warn, $($arg)+); 113 | ) 114 | } 115 | 116 | #[macro_export(local_inner_macros)] 117 | macro_rules! info { 118 | (target: $target:expr, $($arg:tt)+) => ( 119 | log!(target: $target, $crate::Level::Info, $($arg)+); 120 | ); 121 | ($($arg:tt)+) => ( 122 | log!($crate::log::Level::Info, $($arg)+); 123 | ) 124 | } 125 | 126 | #[macro_export(local_inner_macros)] 127 | macro_rules! debug { 128 | (target: $target:expr, $($arg:tt)+) => ( 129 | log!(target: $target, $crate::Level::Debug, $($arg)+); 130 | ); 131 | ($($arg:tt)+) => ( 132 | log!($crate::log::Level::Debug, $($arg)+); 133 | ) 134 | } 135 | 136 | #[macro_export(local_inner_macros)] 137 | macro_rules! trace { 138 | (target: $target:expr, $($arg:tt)+) => ( 139 | log!(target: $target, $crate::Level::Trace, $($arg)+); 140 | ); 141 | ($($arg:tt)+) => ( 142 | log!($crate::log::Level::Trace, $($arg)+); 143 | ) 144 | } 145 | 146 | /// log-rs used `macro_export(local_inner_macros)` instead of $crate::log! to support older rustc version 147 | /// but actually there is an other difference - $crate::log does not support macros reexport :( 148 | /// so even miniquad is fine with 1.31+ rustc version, we need to use local_inner_macros as well 149 | #[doc(hidden)] 150 | #[macro_export] 151 | macro_rules! __log_format_args { 152 | ($($args:tt)*) => { 153 | format!($($args)*) 154 | }; 155 | } 156 | 157 | #[doc(hidden)] 158 | #[macro_export] 159 | macro_rules! __log_module_path { 160 | () => { 161 | module_path!() 162 | }; 163 | } 164 | 165 | #[doc(hidden)] 166 | #[macro_export] 167 | macro_rules! __log_file { 168 | () => { 169 | file!() 170 | }; 171 | } 172 | 173 | #[doc(hidden)] 174 | #[macro_export] 175 | macro_rules! __log_line { 176 | () => { 177 | line!() 178 | }; 179 | } 180 | 181 | #[cfg(not(any(target_arch = "wasm32", target_os = "android", target_os = "ios")))] 182 | pub fn __private_api_log_lit( 183 | message: &str, 184 | _level: Level, 185 | &(_target, _module_path, _file, _line): &(&str, &'static str, &'static str, u32), 186 | ) { 187 | eprintln!("{}", message); 188 | } 189 | 190 | #[cfg(target_arch = "wasm32")] 191 | pub fn __private_api_log_lit( 192 | message: &str, 193 | level: Level, 194 | &(_target, _module_path, _file, _line): &(&str, &'static str, &'static str, u32), 195 | ) { 196 | use crate::native::wasm; 197 | use std::ffi::CString; 198 | 199 | let log_fn = match level { 200 | Level::Debug => wasm::console_debug, 201 | Level::Warn => wasm::console_warn, 202 | Level::Info => wasm::console_info, 203 | Level::Trace => wasm::console_debug, 204 | Level::Error => wasm::console_error, 205 | }; 206 | let msg = CString::new(message).unwrap_or_else(|_| panic!()); 207 | 208 | unsafe { log_fn(msg.as_ptr()) }; 209 | } 210 | 211 | #[cfg(target_os = "android")] 212 | pub fn __private_api_log_lit( 213 | message: &str, 214 | level: Level, 215 | &(_target, _module_path, _file, _line): &(&str, &'static str, &'static str, u32), 216 | ) { 217 | use std::ffi::CString; 218 | 219 | let log_fn = match level { 220 | Level::Debug => crate::native::android::console_debug, 221 | Level::Warn => crate::native::android::console_warn, 222 | Level::Info => crate::native::android::console_info, 223 | Level::Trace => crate::native::android::console_debug, 224 | Level::Error => crate::native::android::console_error, 225 | }; 226 | let msg = CString::new(message).unwrap_or_else(|_| panic!()); 227 | 228 | unsafe { log_fn(msg.as_ptr()) }; 229 | } 230 | 231 | #[cfg(target_os = "ios")] 232 | pub fn __private_api_log_lit( 233 | message: &str, 234 | _level: Level, 235 | &(_target, _module_path, _file, _line): &(&str, &'static str, &'static str, u32), 236 | ) { 237 | crate::native::ios::log(message); 238 | } 239 | 240 | #[test] 241 | fn test_logs() { 242 | trace!("info"); 243 | trace!("info: {}", 1); 244 | 245 | debug!("info"); 246 | debug!("info: {}", 1); 247 | 248 | info!("info"); 249 | info!("info: {}", 1); 250 | 251 | warn!("info"); 252 | warn!("info: {}", 1); 253 | 254 | error!("info"); 255 | error!("info: {}", 1); 256 | } 257 | -------------------------------------------------------------------------------- /src/native.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::sync::mpsc; 4 | 5 | #[derive(Default)] 6 | pub(crate) struct DroppedFiles { 7 | pub paths: Vec, 8 | pub bytes: Vec>, 9 | } 10 | pub(crate) struct NativeDisplayData { 11 | pub screen_width: i32, 12 | pub screen_height: i32, 13 | pub screen_position: (u32, u32), 14 | pub dpi_scale: f32, 15 | pub high_dpi: bool, 16 | pub quit_requested: bool, 17 | pub quit_ordered: bool, 18 | pub native_requests: mpsc::Sender, 19 | pub clipboard: Box, 20 | pub dropped_files: DroppedFiles, 21 | pub blocking_event_loop: bool, 22 | 23 | #[cfg(target_vendor = "apple")] 24 | pub view: crate::native::apple::frameworks::ObjcId, 25 | #[cfg(target_os = "ios")] 26 | pub view_ctrl: crate::native::apple::frameworks::ObjcId, 27 | #[cfg(target_vendor = "apple")] 28 | pub gfx_api: crate::conf::AppleGfxApi, 29 | } 30 | #[cfg(target_vendor = "apple")] 31 | unsafe impl Send for NativeDisplayData {} 32 | #[cfg(target_vendor = "apple")] 33 | unsafe impl Sync for NativeDisplayData {} 34 | 35 | impl NativeDisplayData { 36 | pub fn new( 37 | screen_width: i32, 38 | screen_height: i32, 39 | native_requests: mpsc::Sender, 40 | clipboard: Box, 41 | ) -> NativeDisplayData { 42 | NativeDisplayData { 43 | screen_width, 44 | screen_height, 45 | screen_position: (0, 0), 46 | dpi_scale: 1., 47 | high_dpi: false, 48 | quit_requested: false, 49 | quit_ordered: false, 50 | native_requests, 51 | clipboard, 52 | dropped_files: Default::default(), 53 | blocking_event_loop: false, 54 | #[cfg(target_vendor = "apple")] 55 | gfx_api: crate::conf::AppleGfxApi::OpenGl, 56 | #[cfg(target_vendor = "apple")] 57 | view: std::ptr::null_mut(), 58 | #[cfg(target_os = "ios")] 59 | view_ctrl: std::ptr::null_mut(), 60 | } 61 | } 62 | } 63 | 64 | #[derive(Debug)] 65 | pub(crate) enum Request { 66 | ScheduleUpdate, 67 | SetCursorGrab(bool), 68 | ShowMouse(bool), 69 | SetMouseCursor(crate::CursorIcon), 70 | SetWindowSize { new_width: u32, new_height: u32 }, 71 | SetWindowPosition { new_x: u32, new_y: u32 }, 72 | SetFullscreen(bool), 73 | ShowKeyboard(bool), 74 | } 75 | 76 | pub trait Clipboard: Send + Sync { 77 | fn get(&mut self) -> Option; 78 | fn set(&mut self, string: &str); 79 | } 80 | 81 | pub mod module; 82 | 83 | #[cfg(target_os = "linux")] 84 | pub mod linux_x11; 85 | 86 | #[cfg(target_os = "linux")] 87 | pub mod linux_wayland; 88 | 89 | #[cfg(target_os = "android")] 90 | pub mod android; 91 | 92 | #[cfg(target_os = "windows")] 93 | pub mod windows; 94 | 95 | #[cfg(target_os = "android")] 96 | pub use android::*; 97 | 98 | #[cfg(target_arch = "wasm32")] 99 | pub mod wasm; 100 | 101 | #[cfg(any(target_os = "macos", target_os = "ios"))] 102 | pub mod apple; 103 | 104 | #[cfg(target_os = "macos")] 105 | pub mod macos; 106 | 107 | #[cfg(target_os = "ios")] 108 | pub mod ios; 109 | 110 | #[cfg(any(target_os = "android", target_os = "linux"))] 111 | pub mod egl; 112 | 113 | // there is no glGetProcAddr on webgl, so its impossible to make "gl" module work 114 | // on macos.. well, there is, but way easier to just statically link to gl 115 | #[cfg(not(target_arch = "wasm32"))] 116 | pub mod gl; 117 | 118 | #[cfg(target_arch = "wasm32")] 119 | pub use wasm::webgl as gl; 120 | 121 | pub mod query_stab; 122 | -------------------------------------------------------------------------------- /src/native/android/keycodes.rs: -------------------------------------------------------------------------------- 1 | use crate::event::KeyCode; 2 | 3 | pub fn translate_keycode(keycode: u32) -> KeyCode { 4 | // same as GLFW 5 | match keycode { 6 | 0x01 => KeyCode::Left, 7 | 0x02 => KeyCode::Right, 8 | 0x03 => KeyCode::Home, 9 | 0x04 => KeyCode::Back, 10 | 0x07 => KeyCode::Key0, 11 | 0x08 => KeyCode::Key1, 12 | 0x09 => KeyCode::Key2, 13 | 0x0a => KeyCode::Key3, 14 | 0x0b => KeyCode::Key4, 15 | 0x0c => KeyCode::Key5, 16 | 0x0d => KeyCode::Key6, 17 | 0x0e => KeyCode::Key7, 18 | 0x0f => KeyCode::Key8, 19 | 0x10 => KeyCode::Key9, 20 | 0x13 => KeyCode::Up, 21 | 0x14 => KeyCode::Down, 22 | 0x15 => KeyCode::Left, 23 | 0x16 => KeyCode::Right, 24 | 0x17 => KeyCode::Enter, 25 | 0x1d => KeyCode::A, 26 | 0x1e => KeyCode::B, 27 | 0x1f => KeyCode::C, 28 | 0x20 => KeyCode::D, 29 | 0x21 => KeyCode::E, 30 | 0x22 => KeyCode::F, 31 | 0x23 => KeyCode::G, 32 | 0x24 => KeyCode::H, 33 | 0x25 => KeyCode::I, 34 | 0x26 => KeyCode::J, 35 | 0x27 => KeyCode::K, 36 | 0x28 => KeyCode::L, 37 | 0x29 => KeyCode::M, 38 | 0x2a => KeyCode::N, 39 | 0x2b => KeyCode::O, 40 | 0x2c => KeyCode::P, 41 | 0x2d => KeyCode::Q, 42 | 0x2e => KeyCode::R, 43 | 0x2f => KeyCode::S, 44 | 0x30 => KeyCode::T, 45 | 0x31 => KeyCode::U, 46 | 0x32 => KeyCode::V, 47 | 0x33 => KeyCode::W, 48 | 0x34 => KeyCode::X, 49 | 0x35 => KeyCode::Y, 50 | 0x36 => KeyCode::Z, 51 | 0x37 => KeyCode::Comma, 52 | 0x38 => KeyCode::Period, 53 | 0x39 => KeyCode::LeftAlt, 54 | 0x3a => KeyCode::RightAlt, 55 | 0x3b => KeyCode::LeftShift, 56 | 0x3c => KeyCode::RightShift, 57 | 0x3d => KeyCode::Tab, 58 | 0x3e => KeyCode::Space, 59 | 0x42 => KeyCode::Enter, 60 | // android calls it Delete, but it has an icon of the Backspace and 61 | // expected behavior of the Backspace 62 | 0x43 => KeyCode::Backspace, 63 | _ => KeyCode::Unknown, 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/native/android/mod_inject.rs: -------------------------------------------------------------------------------- 1 | // Imagine crate A depending on miniquad. 2 | // On android A is being compiled to .so and this .so is being loaded 3 | // by "System.loadLibrary("")" java call. 4 | // extern "C" functions from miniquad are right there, in the .so. But somehow 5 | // they are invisible for JNI unless they are declared in the A itself, 6 | // not in the A's dependencies. 7 | // 8 | // Why? I do not know. Would be nice to find some tracking issue. 9 | // 10 | // But we really need to be able to make miniquad's functions visible to java. 11 | // 12 | // Contents of this file is being copied right to the "main.rs" of the main crate 13 | // by cargo quad. And therefore functions like JAVA_CLASS_PATH_QuadSurface_nativeOnSurfaceCreated are well visible for the JNI 14 | // and they just forward the call to the real implementation inside miniquad 15 | // Note that because it is being injected - we might not have neither miniquad 16 | // or ndk_sys as a crate dependency.. so we cant use anything from them. 17 | 18 | 19 | #[no_mangle] 20 | pub extern "C" fn quad_main() { 21 | let _ = super::main(); 22 | } 23 | -------------------------------------------------------------------------------- /src/native/android/ndk_utils.rs: -------------------------------------------------------------------------------- 1 | //! Little helpers to writing JNI code. 2 | //! Aimed to reduce amount of (**env).Function.unwrap() calls in the code. 3 | //! This belongs to a separate crate! 4 | 5 | #[macro_export] 6 | /// Find an method with given signature 7 | /// on $obj class 8 | /// and call a NewObject jni function with given extra arguments 9 | macro_rules! new_object { 10 | ($env:expr, $class:expr, $sig:expr $(, $args:expr)*) => {{ 11 | let find_class = (**$env).FindClass.unwrap(); 12 | let get_method_id = (**$env).GetMethodID.unwrap(); 13 | let new_object = (**$env).NewObject.unwrap(); 14 | 15 | let class = std::ffi::CString::new($class).unwrap(); 16 | let sig = std::ffi::CString::new($sig).unwrap(); 17 | let class = find_class($env, class.as_ptr() as _); 18 | 19 | let constructor = get_method_id($env, class, b"\0".as_ptr() as _, sig.as_ptr() as _); 20 | 21 | new_object($env, class, constructor, $($args,)*) 22 | }}; 23 | } 24 | 25 | #[macro_export] 26 | macro_rules! call_method { 27 | ($fn:tt, $env:expr, $obj:expr, $method:expr, $sig:expr $(, $args:expr)*) => {{ 28 | let get_object_class = (**$env).GetObjectClass.unwrap(); 29 | let get_method_id = (**$env).GetMethodID.unwrap(); 30 | let call_object_method = (**$env).$fn.unwrap(); 31 | 32 | let method = std::ffi::CString::new($method).unwrap(); 33 | let sig = std::ffi::CString::new($sig).unwrap(); 34 | let class = get_object_class($env, $obj); 35 | 36 | assert!(!class.is_null()); 37 | 38 | let method = get_method_id($env, class, method.as_ptr() as _, sig.as_ptr() as _); 39 | assert!(!method.is_null()); 40 | 41 | call_object_method($env, $obj, method, $($args,)*) 42 | }}; 43 | } 44 | 45 | #[macro_export] 46 | macro_rules! call_object_method { 47 | ($env:expr, $obj:expr, $method:expr, $sig:expr $(, $args:expr)*) => {{ 48 | $crate::call_method!(CallObjectMethod, $env, $obj, $method, $sig $(, $args)*) 49 | }}; 50 | } 51 | 52 | #[macro_export] 53 | macro_rules! call_int_method { 54 | ($env:expr, $obj:expr, $method:expr, $sig:expr $(, $args:expr)*) => {{ 55 | $crate::call_method!(CallIntMethod, $env, $obj, $method, $sig $(, $args)*) 56 | }}; 57 | } 58 | 59 | #[macro_export] 60 | macro_rules! call_void_method { 61 | ($env:expr, $obj:expr, $method:expr, $sig:expr $(, $args:expr)*) => {{ 62 | $crate::call_method!(CallVoidMethod, $env, $obj, $method, $sig $(, $args)*) 63 | }}; 64 | } 65 | 66 | #[macro_export] 67 | macro_rules! call_bool_method { 68 | ($env:expr, $obj:expr, $method:expr, $sig:expr $(, $args:expr)*) => {{ 69 | $crate::call_method!(CallBooleanMethod, $env, $obj, $method, $sig $(, $args)*) 70 | }}; 71 | } 72 | 73 | #[macro_export] 74 | macro_rules! get_utf_str { 75 | ($env:expr, $obj:expr) => {{ 76 | let cstr_dat = (**$env).GetStringUTFChars.unwrap()($env, $obj, std::ptr::null_mut()); 77 | let string = std::ffi::CStr::from_ptr(cstr_dat) 78 | .to_str() 79 | .unwrap() 80 | .to_string(); 81 | (**$env).ReleaseStringUTFChars.unwrap()($env, $obj, cstr_dat); 82 | string 83 | }}; 84 | } 85 | 86 | #[macro_export] 87 | macro_rules! new_global_ref { 88 | ($env:expr, $obj:expr) => {{ 89 | (**$env).NewGlobalRef.unwrap()($env, $obj) 90 | }}; 91 | } 92 | 93 | #[macro_export] 94 | macro_rules! new_local_ref { 95 | ($env:expr, $obj:expr) => {{ 96 | (**$env).NewLocalRef.unwrap()($env, $obj) 97 | }}; 98 | } 99 | 100 | pub use { 101 | call_bool_method, call_int_method, call_method, call_object_method, call_void_method, 102 | get_utf_str, new_global_ref, new_local_ref, new_object, 103 | }; 104 | -------------------------------------------------------------------------------- /src/native/apple.rs: -------------------------------------------------------------------------------- 1 | pub mod apple_util; 2 | pub mod frameworks; 3 | -------------------------------------------------------------------------------- /src/native/egl.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types, non_snake_case, dead_code)] 2 | 3 | #[cfg(target_os = "linux")] 4 | pub type EGLNativeDisplayType = *mut crate::native::linux_x11::libx11::Display; 5 | #[cfg(target_os = "linux")] 6 | pub type EGLNativePixmapType = crate::native::linux_x11::libx11::Pixmap; 7 | #[cfg(target_os = "linux")] 8 | pub type EGLNativeWindowType = crate::native::linux_x11::libx11::Window; 9 | 10 | #[cfg(target_os = "android")] 11 | pub type EGLNativeDisplayType = *mut (); 12 | #[cfg(target_os = "android")] 13 | pub type EGLNativePixmapType = ::core::ffi::c_ulong; 14 | #[cfg(target_os = "android")] 15 | pub type EGLNativeWindowType = ::core::ffi::c_ulong; 16 | 17 | pub use core::ptr::null_mut; 18 | use std::fmt::Display; 19 | 20 | pub const EGL_SUCCESS: u32 = 12288; 21 | 22 | pub const EGL_WINDOW_BIT: u32 = 4; 23 | 24 | pub const EGL_ALPHA_SIZE: u32 = 12321; 25 | pub const EGL_BLUE_SIZE: u32 = 12322; 26 | pub const EGL_GREEN_SIZE: u32 = 12323; 27 | pub const EGL_RED_SIZE: u32 = 12324; 28 | pub const EGL_DEPTH_SIZE: u32 = 12325; 29 | pub const EGL_STENCIL_SIZE: u32 = 12326; 30 | pub const EGL_SAMPLES: u32 = 12337; 31 | pub const EGL_NATIVE_VISUAL_ID: u32 = 12334; 32 | pub const EGL_WIDTH: u32 = 12375; 33 | pub const EGL_HEIGHT: u32 = 12374; 34 | pub const EGL_SURFACE_TYPE: u32 = 12339; 35 | pub const EGL_NONE: u32 = 12344; 36 | pub const EGL_CONTEXT_CLIENT_VERSION: u32 = 12440; 37 | 38 | pub type NativeDisplayType = EGLNativeDisplayType; 39 | pub type NativePixmapType = EGLNativePixmapType; 40 | pub type NativeWindowType = EGLNativeWindowType; 41 | pub type EGLint = i32; 42 | pub type EGLBoolean = ::core::ffi::c_uint; 43 | pub type EGLDisplay = *mut ::core::ffi::c_void; 44 | pub type EGLConfig = *mut ::core::ffi::c_void; 45 | pub type EGLSurface = *mut ::core::ffi::c_void; 46 | pub type EGLContext = *mut ::core::ffi::c_void; 47 | pub type __eglMustCastToProperFunctionPointerType = ::std::option::Option; 48 | 49 | crate::declare_module! { 50 | LibEgl, 51 | "libEGL.so", 52 | "libEGL.so.1", 53 | ... 54 | ... 55 | pub fn eglChooseConfig( 56 | EGLDisplay, 57 | *const EGLint, 58 | *mut EGLConfig, 59 | EGLint, 60 | *mut EGLint, 61 | ) -> EGLBoolean, 62 | pub fn eglCopyBuffers( 63 | EGLDisplay, 64 | EGLSurface, 65 | EGLNativePixmapType, 66 | ) -> EGLBoolean, 67 | pub fn eglCreateContext( 68 | EGLDisplay, 69 | EGLConfig, 70 | EGLContext, 71 | *const EGLint, 72 | ) -> EGLContext, 73 | pub fn eglCreatePbufferSurface( 74 | EGLDisplay, 75 | EGLConfig, 76 | *const EGLint, 77 | ) -> EGLSurface, 78 | pub fn eglCreatePixmapSurface( 79 | EGLDisplay, 80 | EGLConfig, 81 | EGLNativePixmapType, 82 | *const EGLint, 83 | ) -> EGLSurface, 84 | pub fn eglCreateWindowSurface( 85 | EGLDisplay, 86 | EGLConfig, 87 | EGLNativeWindowType, 88 | *const EGLint, 89 | ) -> EGLSurface, 90 | pub fn eglDestroyContext(EGLDisplay, EGLContext) -> EGLBoolean, 91 | pub fn eglDestroySurface(EGLDisplay, EGLSurface) -> EGLBoolean, 92 | pub fn eglGetConfigAttrib( 93 | EGLDisplay, 94 | EGLConfig, 95 | EGLint, 96 | *mut EGLint, 97 | ) -> EGLBoolean, 98 | pub fn eglGetConfigs( 99 | EGLDisplay, 100 | *mut EGLConfig, 101 | EGLint, 102 | *mut EGLint, 103 | ) -> EGLBoolean, 104 | pub fn eglGetCurrentDisplay() -> EGLDisplay, 105 | pub fn eglGetCurrentSurface(EGLint) -> EGLSurface, 106 | pub fn eglGetDisplay(EGLNativeDisplayType) -> EGLDisplay, 107 | pub fn eglGetError() -> EGLint, 108 | pub fn eglGetProcAddress( 109 | *const ::core::ffi::c_char, 110 | ) -> __eglMustCastToProperFunctionPointerType, 111 | pub fn eglInitialize(EGLDisplay, *mut EGLint, *mut EGLint) -> EGLBoolean, 112 | pub fn eglMakeCurrent( 113 | EGLDisplay, 114 | EGLSurface, 115 | EGLSurface, 116 | EGLContext, 117 | ) -> EGLBoolean, 118 | pub fn eglQueryContext( 119 | EGLDisplay, 120 | EGLContext, 121 | EGLint, 122 | *mut EGLint, 123 | ) -> EGLBoolean, 124 | pub fn eglQueryString(EGLDisplay, EGLint) -> *const ::core::ffi::c_char, 125 | pub fn eglQuerySurface( 126 | EGLDisplay, 127 | EGLSurface, 128 | EGLint, 129 | *mut EGLint, 130 | ) -> EGLBoolean, 131 | pub fn eglSwapBuffers(EGLDisplay, EGLSurface) -> EGLBoolean, 132 | pub fn eglTerminate(EGLDisplay) -> EGLBoolean, 133 | pub fn eglWaitGL() -> EGLBoolean, 134 | pub fn eglWaitNative(EGLint) -> EGLBoolean, 135 | pub fn eglBindTexImage(EGLDisplay, EGLSurface, EGLint) -> EGLBoolean, 136 | pub fn eglReleaseTexImage(EGLDisplay, EGLSurface, EGLint) -> EGLBoolean, 137 | pub fn eglSurfaceAttrib( 138 | EGLDisplay, 139 | EGLSurface, 140 | EGLint, 141 | EGLint, 142 | ) -> EGLBoolean, 143 | pub fn eglSwapInterval(EGLDisplay, EGLint) -> EGLBoolean, 144 | ... 145 | ... 146 | } 147 | 148 | #[derive(Debug)] 149 | pub enum EglError { 150 | NoDisplay, 151 | InitializeFailed, 152 | CreateContextFailed, 153 | } 154 | 155 | impl Display for EglError { 156 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 157 | match self { 158 | Self::NoDisplay => write!(f, "No display"), 159 | Self::InitializeFailed => write!(f, "Failed to initialize context"), 160 | Self::CreateContextFailed => write!(f, "Faild to create context"), 161 | } 162 | } 163 | } 164 | 165 | impl std::error::Error for EglError {} 166 | 167 | pub struct Egl {} 168 | 169 | pub unsafe fn create_egl_context( 170 | egl: &mut LibEgl, 171 | display: *mut std::ffi::c_void, 172 | alpha: bool, 173 | sample_count: i32, 174 | ) -> Result<(EGLContext, EGLConfig, EGLDisplay), EglError> { 175 | let display = (egl.eglGetDisplay)(display as _); 176 | if display.is_null() { 177 | // == EGL_NO_DISPLAY 178 | return Err(EglError::NoDisplay); 179 | } 180 | 181 | if (egl.eglInitialize)(display, null_mut(), null_mut()) == 0 { 182 | return Err(EglError::InitializeFailed); 183 | } 184 | 185 | let alpha_size = if alpha { 8 } else { 0 }; 186 | #[rustfmt::skip] 187 | let cfg_attributes = [ 188 | EGL_SURFACE_TYPE, EGL_WINDOW_BIT, 189 | EGL_RED_SIZE, 8, 190 | EGL_GREEN_SIZE, 8, 191 | EGL_BLUE_SIZE, 8, 192 | EGL_ALPHA_SIZE, alpha_size, 193 | EGL_DEPTH_SIZE, 16, 194 | EGL_STENCIL_SIZE, 0, 195 | EGL_SAMPLES, sample_count as u32, 196 | EGL_NONE, 197 | ]; 198 | let mut available_cfgs: Vec = vec![null_mut(); 32]; 199 | let mut cfg_count = 0; 200 | 201 | (egl.eglChooseConfig)( 202 | display, 203 | cfg_attributes.as_ptr() as _, 204 | available_cfgs.as_ptr() as _, 205 | 32, 206 | &mut cfg_count as *mut _ as *mut _, 207 | ); 208 | assert!(cfg_count > 0); 209 | assert!(cfg_count <= 32); 210 | 211 | // find config with 8-bit rgb buffer if available, ndk sample does not trust egl spec 212 | let mut config: EGLConfig = null_mut(); 213 | let mut exact_cfg_found = false; 214 | for c in &mut available_cfgs[0..cfg_count] { 215 | let mut r: i32 = 0; 216 | let mut g: i32 = 0; 217 | let mut b: i32 = 0; 218 | let mut a: i32 = 0; 219 | let mut d: i32 = 0; 220 | if (egl.eglGetConfigAttrib)(display, *c, EGL_RED_SIZE as _, &mut r) == 1 221 | && (egl.eglGetConfigAttrib)(display, *c, EGL_GREEN_SIZE as _, &mut g) == 1 222 | && (egl.eglGetConfigAttrib)(display, *c, EGL_BLUE_SIZE as _, &mut b) == 1 223 | && (egl.eglGetConfigAttrib)(display, *c, EGL_ALPHA_SIZE as _, &mut a) == 1 224 | && (egl.eglGetConfigAttrib)(display, *c, EGL_DEPTH_SIZE as _, &mut d) == 1 225 | && r == 8 226 | && g == 8 227 | && b == 8 228 | && (alpha_size == 0 || a == alpha_size as _) 229 | && d == 16 230 | { 231 | exact_cfg_found = true; 232 | config = *c; 233 | break; 234 | } 235 | } 236 | if !exact_cfg_found { 237 | config = available_cfgs[0]; 238 | } 239 | let ctx_attributes = [EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE]; 240 | let context = (egl.eglCreateContext)( 241 | display, 242 | config, 243 | /* EGL_NO_CONTEXT */ null_mut(), 244 | ctx_attributes.as_ptr() as _, 245 | ); 246 | if context.is_null() { 247 | return Err(EglError::CreateContextFailed); 248 | } 249 | 250 | Ok((context, config, display)) 251 | } 252 | -------------------------------------------------------------------------------- /src/native/linux_wayland/clipboard.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::{wl_request, wl_request_constructor}; 3 | 4 | use core::ffi::{c_char, c_int, c_void}; 5 | 6 | // new clipboard content is available 7 | // this could be fired at any time, so we just store the `data_offer` for later use 8 | pub(super) unsafe extern "C" fn data_device_handle_selection( 9 | data: *mut c_void, 10 | data_device: *mut wl_data_device, 11 | data_offer: *mut wl_data_offer, 12 | ) { 13 | let display: &mut WaylandPayload = &mut *(data as *mut _); 14 | assert_eq!(data_device, display.data_device); 15 | CLIPBOARD.get_mut().unwrap().data_offer = (!data_offer.is_null()).then_some(data_offer); 16 | } 17 | 18 | use std::sync::OnceLock; 19 | static mut CLIPBOARD: OnceLock = OnceLock::new(); 20 | 21 | // Contains the owned clipboard and the `data_source` (object in Wayland that indicates clipboard 22 | // ownership). There is a static instance `CLIPBOARD` available globally, initialized when creating 23 | // the `WaylandClipboard`. 24 | #[derive(Debug)] 25 | struct ClipboardContext { 26 | display: *mut WaylandPayload, 27 | content: String, 28 | data_source: Option<*mut wl_data_source>, 29 | data_offer: Option<*mut wl_data_offer>, 30 | } 31 | 32 | impl ClipboardContext { 33 | unsafe fn get_clipboard(&mut self, mime_type: &str) -> Option> { 34 | self.data_offer.map(|data_offer| { 35 | let display: &mut WaylandPayload = &mut *self.display; 36 | let mime_type = std::ffi::CString::new(mime_type).unwrap(); 37 | display 38 | .client 39 | .data_offer_receive(display.display, data_offer, mime_type.as_ptr()) 40 | })? 41 | } 42 | 43 | unsafe fn set(&mut self, data: &str) { 44 | self.content.clear(); 45 | self.content.push_str(data); 46 | let display: &mut WaylandPayload = &mut *self.display; 47 | // Wayland requires that only the window with focus can set the clipboard 48 | if let Some(serial) = display.keyboard_context.enter_serial { 49 | let data_source = self.new_data_source(); 50 | // only support copying utf8 strings 51 | let mime_type = std::ffi::CString::new("UTF8_STRING").unwrap(); 52 | wl_request!( 53 | display.client, 54 | data_source, 55 | WL_DATA_SOURCE_OFFER, 56 | mime_type.as_ptr() 57 | ); 58 | wl_request!( 59 | display.client, 60 | display.data_device, 61 | WL_DATA_DEVICE_SET_SELECTION, 62 | data_source, 63 | serial 64 | ); 65 | } 66 | } 67 | 68 | unsafe fn respond_to_clipboard_request(&mut self, mime_type: &str, fd: c_int) { 69 | #![allow(clippy::single_match)] 70 | match mime_type { 71 | "UTF8_STRING" => { 72 | libc::write(fd, self.content.as_ptr() as _, self.content.len()); 73 | } 74 | _ => {} 75 | } 76 | libc::close(fd); 77 | } 78 | 79 | unsafe fn destroy_data_source(&mut self) { 80 | // since the data_source is constructed by us, we need to dispose of it properly 81 | if let Some(data_source) = self.data_source { 82 | let display: &mut WaylandPayload = &mut *self.display; 83 | wl_request!(display.client, data_source, WL_DATA_SOURCE_DESTROY); 84 | (display.client.wl_proxy_destroy)(data_source as _); 85 | self.data_source = None; 86 | } 87 | } 88 | 89 | unsafe fn new_data_source(&mut self) -> *mut wl_data_source { 90 | self.destroy_data_source(); 91 | let display: &mut WaylandPayload = &mut *self.display; 92 | let data_source: *mut wl_data_source = wl_request_constructor!( 93 | display.client, 94 | display.data_device_manager, 95 | WL_DATA_DEVICE_MANAGER_CREATE_DATA_SOURCE, 96 | display.client.wl_data_source_interface, 97 | ); 98 | assert!(!data_source.is_null()); 99 | DATA_SOURCE_LISTENER.send = data_source_handle_send; 100 | DATA_SOURCE_LISTENER.cancelled = data_source_handle_cancelled; 101 | (display.client.wl_proxy_add_listener)( 102 | data_source as _, 103 | &DATA_SOURCE_LISTENER as *const _ as _, 104 | self.display as *const _ as _, 105 | ); 106 | self.data_source = Some(data_source); 107 | data_source 108 | } 109 | } 110 | 111 | static mut DATA_SOURCE_LISTENER: wl_data_source_listener = wl_data_source_listener::dummy(); 112 | 113 | // some app (could be ourself) is requesting the owned clipboard 114 | unsafe extern "C" fn data_source_handle_send( 115 | _data: *mut c_void, 116 | data_source: *mut wl_data_source, 117 | mime_type: *const c_char, 118 | fd: c_int, 119 | ) { 120 | let mime_type = core::ffi::CStr::from_ptr(mime_type).to_str().unwrap(); 121 | let ctx = CLIPBOARD.get_mut().unwrap(); 122 | assert!(ctx.data_source == Some(data_source)); 123 | ctx.respond_to_clipboard_request(mime_type, fd); 124 | } 125 | 126 | // the owned clipboard has been replaced by some other app 127 | unsafe extern "C" fn data_source_handle_cancelled( 128 | _data: *mut c_void, 129 | data_source: *mut wl_data_source, 130 | ) { 131 | let ctx = CLIPBOARD.get_mut().unwrap(); 132 | assert!(ctx.data_source == Some(data_source)); 133 | ctx.destroy_data_source(); 134 | } 135 | 136 | pub struct WaylandClipboard {} 137 | unsafe impl Send for WaylandClipboard {} 138 | unsafe impl Sync for WaylandClipboard {} 139 | 140 | impl WaylandClipboard { 141 | pub(super) fn new(display: *mut WaylandPayload) -> Self { 142 | // initialize the global context 143 | unsafe { 144 | CLIPBOARD 145 | .set(ClipboardContext { 146 | display, 147 | content: String::new(), 148 | data_source: None, 149 | data_offer: None, 150 | }) 151 | .unwrap(); 152 | } 153 | WaylandClipboard {} 154 | } 155 | } 156 | 157 | impl crate::native::Clipboard for WaylandClipboard { 158 | fn get(&mut self) -> Option { 159 | let bytes = unsafe { CLIPBOARD.get_mut().unwrap().get_clipboard("UTF8_STRING")? }; 160 | Some(std::str::from_utf8(&bytes).ok()?.to_string()) 161 | } 162 | fn set(&mut self, data: &str) { 163 | unsafe { 164 | CLIPBOARD.get_mut().unwrap().set(data); 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/native/linux_wayland/drag_n_drop.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::wl_request; 3 | 4 | #[derive(Default)] 5 | pub struct WaylandDnD { 6 | data_offer: Option<*mut wl_data_offer>, 7 | enter_serial: Option, 8 | } 9 | 10 | pub(super) unsafe extern "C" fn data_offer_handle_source_actions( 11 | data: *mut ::core::ffi::c_void, 12 | data_offer: *mut wl_data_offer, 13 | actions: ::core::ffi::c_uint, 14 | ) { 15 | if actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY == 1 { 16 | let display: &mut WaylandPayload = &mut *(data as *mut _); 17 | wl_request!( 18 | display.client, 19 | data_offer, 20 | WL_DATA_OFFER_SET_ACTIONS, 21 | WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY, 22 | WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY 23 | ); 24 | } 25 | } 26 | 27 | pub(super) unsafe extern "C" fn data_device_handle_enter( 28 | data: *mut ::core::ffi::c_void, 29 | data_device: *mut wl_data_device, 30 | serial: core::ffi::c_uint, 31 | _surface: *mut wl_surface, 32 | _surface_x: i32, 33 | _surface_y: i32, 34 | data_offer: *mut wl_data_offer, 35 | ) { 36 | let display: &mut WaylandPayload = &mut *(data as *mut _); 37 | assert_eq!(data_device, display.data_device); 38 | display.drag_n_drop.enter_serial = Some(serial); 39 | display.drag_n_drop.data_offer = Some(data_offer); 40 | // only accept utf8 strings 41 | let mime_type = std::ffi::CString::new("UTF8_STRING").unwrap(); 42 | wl_request!( 43 | display.client, 44 | data_offer, 45 | WL_DATA_OFFER_ACCEPT, 46 | serial, 47 | mime_type.as_ptr() 48 | ); 49 | } 50 | 51 | pub(super) unsafe extern "C" fn data_device_handle_leave( 52 | data: *mut ::core::ffi::c_void, 53 | data_device: *mut wl_data_device, 54 | ) { 55 | let display: &mut WaylandPayload = &mut *(data as *mut _); 56 | assert_eq!(data_device, display.data_device); 57 | display.drag_n_drop.enter_serial = None; 58 | display.drag_n_drop.data_offer = None; 59 | } 60 | 61 | pub(super) unsafe extern "C" fn data_device_handle_drop( 62 | data: *mut ::core::ffi::c_void, 63 | data_device: *mut wl_data_device, 64 | ) { 65 | let display: &mut WaylandPayload = &mut *(data as *mut _); 66 | assert_eq!(data_device, display.data_device); 67 | if let Some(data_offer) = display.drag_n_drop.data_offer { 68 | let mime_type = std::ffi::CString::new("UTF8_STRING").unwrap(); 69 | if let Some(bytes) = 70 | display 71 | .client 72 | .data_offer_receive(display.display, data_offer, mime_type.as_ptr()) 73 | { 74 | // Doing `data_offer.finish` here sometimes causes "premature finish error" 75 | // No idea why so we just delete the data_offer directly 76 | // wl_request!(display.client, data_offer, WL_DATA_OFFER_FINISH); 77 | wl_request!(display.client, data_offer, WL_DATA_OFFER_DESTROY); 78 | (display.client.wl_proxy_destroy)(data_offer as _); 79 | display.drag_n_drop.data_offer = None; 80 | if let Ok(filenames) = String::from_utf8(bytes) { 81 | display.events.push(WaylandEvent::FilesDropped(filenames)); 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/native/linux_wayland/extensions.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_variables, dead_code, non_upper_case_globals, static_mut_refs)] 2 | 3 | pub mod cursor; 4 | pub mod libdecor; 5 | pub mod viewporter; 6 | pub mod xdg_decoration; 7 | pub mod xdg_shell; 8 | 9 | #[macro_export] 10 | macro_rules! count { 11 | () => (0usize); 12 | ( $x:tt $($xs:tt)* ) => (1usize + $crate::count!($($xs)*)); 13 | } 14 | 15 | #[macro_export] 16 | macro_rules! method_consts { 17 | ($x:expr, ()) => { 18 | }; 19 | ($x:expr, ($next:ident, $($rest:ident,)*)) => { 20 | pub const $next: u32 = $x; 21 | $crate::method_consts!(($x + 1), ($($rest,)*)); 22 | }; 23 | } 24 | 25 | #[macro_export] 26 | macro_rules! wayland_interface { 27 | ($name:ident, $struct_name:ident, $version:expr, 28 | [ 29 | $( 30 | ($method_name:ident, $method_sign:expr, ($($method_argument_name:expr),*)) 31 | ),* 32 | ], 33 | [ 34 | $( 35 | ($event_name:expr, $event_sign:expr) 36 | ),* 37 | ]) => { 38 | mod $name { 39 | use super::*; 40 | 41 | $( 42 | mod $method_name { 43 | use super::*; 44 | 45 | pub static mut METHOD_ARGUMENTS_TYPES: 46 | [*const wl_interface; $crate::count!($($method_argument_name)*)] = 47 | [$(unsafe { &$method_argument_name as *const _},)*]; 48 | 49 | } 50 | )* 51 | 52 | static mut requests: [wl_message; $crate::count!($($method_name)*)] = [$(wl_message { 53 | name: concat!(stringify!($method_name), '\0').as_ptr() as _, 54 | signature: concat!($method_sign, '\0').as_ptr() as _, 55 | types: unsafe { $method_name::METHOD_ARGUMENTS_TYPES.as_ptr() as _ } 56 | }), *]; 57 | 58 | static mut events: [wl_message; $crate::count!($($event_name)*)] = [$(wl_message { 59 | name: concat!($event_name, '\0').as_ptr() as _, 60 | signature: concat!($event_sign, '\0').as_ptr() as _, 61 | types: std::ptr::null_mut() 62 | }),*]; 63 | 64 | pub static mut $name: wl_interface = wl_interface { 65 | name: concat!(stringify!($struct_name), '\0').as_ptr() as *const _, 66 | version: $version, 67 | method_count: $crate::count!($($method_name)*) as i32, 68 | methods: unsafe { requests.as_ptr() }, 69 | event_count: $crate::count!($($event_name)*) as i32, 70 | events: unsafe { events.as_ptr() }, 71 | }; 72 | } 73 | 74 | #[repr(C)] 75 | #[derive(Debug, Copy, Clone)] 76 | pub struct $struct_name { 77 | _unused: [u8; 0], 78 | } 79 | 80 | impl $struct_name { 81 | $crate::method_consts!(0, ($($method_name,)*)); 82 | } 83 | pub use $name::$name; 84 | }; 85 | } 86 | 87 | /// Redifinition some interfaces from wayland-protocol.c, to have them available 88 | /// in compile time, to allow other interfaces use them as their arguments. 89 | pub mod wayland_protocol { 90 | use super::super::{wl_interface, wl_message}; 91 | 92 | wayland_interface!( 93 | wl_output_interface, 94 | wl_output, 95 | 3, 96 | [(release, "3", ())], 97 | [ 98 | ("geometry", "iiiiissi"), 99 | ("mode", "uiii"), 100 | ("done", "2"), 101 | ("scale", "2i") 102 | ] 103 | ); 104 | 105 | wayland_interface!( 106 | wl_seat_interface, 107 | wl_seat, 108 | 7, 109 | [ 110 | (get_pointer, "n", ()), 111 | (get_keyboard, "n", ()), 112 | (get_touch, "n", ()), 113 | (release, "5", ()) 114 | ], 115 | [("capabilities", "u"), ("name", "2s")] 116 | ); 117 | } 118 | -------------------------------------------------------------------------------- /src/native/linux_wayland/extensions/cursor.rs: -------------------------------------------------------------------------------- 1 | use super::super::libwayland_client::{wl_fixed_t, wl_interface, wl_message}; 2 | use crate::wayland_interface; 3 | 4 | pub const CURSOR_SHAPE_MANAGER_GET_POINTER: u32 = 1; 5 | pub const CURSOR_SHAPE_DEVICE_SET_SHAPE: u32 = 1; 6 | pub const RELATIVE_POINTER_MANAGER_GET_RELATIVE_POINTER: u32 = 1; 7 | pub const POINTER_CONSTRAINTS_LOCK_POINTER: u32 = 1; 8 | pub const zwp_pointer_constraints_v1_lifetime_ONESHOT: u32 = 1; 9 | pub const zwp_pointer_constraints_v1_lifetime_PERSISTENT: u32 = 2; 10 | 11 | #[rustfmt::skip] 12 | wayland_interface!( 13 | wp_cursor_shape_manager_v1_interface, 14 | wp_cursor_shape_manager_v1, 15 | 1, 16 | [ 17 | (destroy, "", ()), 18 | (get_pointer, "no", (wp_cursor_shape_device_v1_interface)), 19 | (get_tablet_tool_v2, "no", (wp_cursor_shape_device_v1_interface)) 20 | ], 21 | [] 22 | ); 23 | 24 | #[rustfmt::skip] 25 | wayland_interface!( 26 | wp_cursor_shape_device_v1_interface, 27 | wp_cursor_shape_device_v1, 28 | 1, 29 | [ 30 | (destroy, "", ()), 31 | (set_shape, "uu", ()) 32 | ], 33 | [] 34 | ); 35 | 36 | #[rustfmt::skip] 37 | wayland_interface!( 38 | zwp_relative_pointer_manager_v1_interface, 39 | zwp_relative_pointer_manager_v1, 40 | 1, 41 | [ 42 | (destroy, "", ()), 43 | (get_relative_pointer, "no", (zwp_relative_pointer_v1_interface)) 44 | ], 45 | [] 46 | ); 47 | 48 | #[rustfmt::skip] 49 | wayland_interface!( 50 | zwp_pointer_constraints_v1_interface, 51 | zwp_pointer_constraints_v1, 52 | 1, 53 | [ 54 | (destroy, "", ()), 55 | (lock_pointer, "noo?ou", (zwp_locked_pointer_v1_interface)), 56 | (confine_pointer, "noo?ou", (zwp_confined_pointer_v1_interface)) 57 | ], 58 | [] 59 | ); 60 | 61 | #[rustfmt::skip] 62 | wayland_interface!( 63 | zwp_locked_pointer_v1_interface, 64 | zwp_locked_pointer_v1, 65 | 1, 66 | [ 67 | (destroy, "", ()), 68 | (set_cursor_position_hint, "ff", ()), 69 | (set_region, "?o", ()) 70 | ], 71 | [("locked", ""), ("unlocked", "")] 72 | ); 73 | 74 | #[rustfmt::skip] 75 | wayland_interface!( 76 | zwp_confined_pointer_v1_interface, 77 | zwp_confined_pointer_v1, 78 | 1, 79 | [ 80 | (destroy, "", ()), 81 | (set_region, "?o", ()) 82 | ], 83 | [("confined", ""), ("unconfined", "")] 84 | ); 85 | 86 | #[rustfmt::skip] 87 | wayland_interface!( 88 | zwp_relative_pointer_v1_interface, 89 | zwp_relative_pointer_v1, 90 | 1, 91 | [ 92 | (destroy, "", ()) 93 | ], 94 | [("relative_motion", "uuffff")] 95 | ); 96 | 97 | crate::wl_listener!( 98 | zwp_relative_pointer_v1_listener, 99 | zwp_relative_pointer_v1, 100 | zwp_relative_pointer_v1_dummy, 101 | fn relative_motion( 102 | utime_hi: core::ffi::c_uint, 103 | utime_lo: core::ffi::c_uint, 104 | dx: wl_fixed_t, 105 | dy: wl_fixed_t, 106 | dx_unaccel: wl_fixed_t, 107 | dy_unaccel: wl_fixed_t, 108 | ), 109 | ); 110 | 111 | pub fn translate_cursor(icon: crate::CursorIcon) -> core::ffi::c_uint { 112 | // https://wayland.app/protocols/cursor-shape-v1#wp_cursor_shape_device_v1:enum:shape 113 | match icon { 114 | crate::CursorIcon::Default => 1, 115 | crate::CursorIcon::Help => 3, 116 | crate::CursorIcon::Pointer => 4, 117 | crate::CursorIcon::Wait => 6, 118 | crate::CursorIcon::Crosshair => 8, 119 | crate::CursorIcon::Text => 9, 120 | crate::CursorIcon::Move => 13, 121 | crate::CursorIcon::NotAllowed => 15, 122 | crate::CursorIcon::EWResize => 26, 123 | crate::CursorIcon::NSResize => 27, 124 | crate::CursorIcon::NESWResize => 28, 125 | crate::CursorIcon::NWSEResize => 29, 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/native/linux_wayland/extensions/libdecor.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types, dead_code)] 2 | 3 | use super::super::libwayland_client::*; 4 | use super::xdg_shell::*; 5 | use crate::declare_module; 6 | 7 | #[repr(C)] 8 | #[derive(Debug, Copy, Clone)] 9 | pub struct libdecor { 10 | _unused: [u8; 0], 11 | } 12 | 13 | #[repr(C)] 14 | #[derive(Debug, Copy, Clone)] 15 | pub struct libdecor_frame { 16 | _unused: [u8; 0], 17 | } 18 | 19 | #[repr(C)] 20 | #[derive(Debug, Copy, Clone)] 21 | pub struct libdecor_configuration { 22 | _unused: [u8; 0], 23 | } 24 | 25 | #[repr(C)] 26 | #[derive(Debug, Copy, Clone)] 27 | pub struct libdecor_state { 28 | _unused: [u8; 0], 29 | } 30 | 31 | #[repr(C)] 32 | #[derive(Debug, Copy, Clone)] 33 | pub struct libdecor_error { 34 | _unused: [u8; 0], 35 | } 36 | 37 | #[repr(C)] 38 | #[derive(Debug, Copy, Clone)] 39 | pub struct libdecor_interface { 40 | pub error: unsafe extern "C" fn(*mut libdecor, *mut libdecor_error, *const c_char), 41 | } 42 | 43 | #[repr(C)] 44 | #[derive(Debug, Copy, Clone)] 45 | pub struct libdecor_frame_interface { 46 | pub configure: 47 | unsafe extern "C" fn(*mut libdecor_frame, *mut libdecor_configuration, *mut c_void), 48 | pub close: unsafe extern "C" fn(*mut libdecor_frame, *mut c_void), 49 | pub commit: unsafe extern "C" fn(*mut libdecor_frame, *mut c_void), 50 | } 51 | 52 | use core::ffi::{c_char, c_int, c_void}; 53 | 54 | declare_module! { 55 | LibDecor, 56 | "libdecor-0.so", 57 | "libdecor-0.so.0", 58 | ... 59 | ... 60 | pub fn libdecor_new(*mut wl_display, *mut libdecor_interface) -> *mut libdecor, 61 | pub fn libdecor_decorate( 62 | *mut libdecor, 63 | *mut wl_surface, 64 | *mut libdecor_frame_interface, 65 | *mut c_void 66 | ) -> *mut libdecor_frame, 67 | pub fn libdecor_frame_set_app_id(*mut libdecor_frame, *const c_char), 68 | pub fn libdecor_frame_set_title(*mut libdecor_frame, *const c_char), 69 | pub fn libdecor_frame_map(*mut libdecor_frame), 70 | pub fn libdecor_state_new(c_int, c_int) -> *mut libdecor_state, 71 | pub fn libdecor_frame_commit( 72 | *mut libdecor_frame, 73 | *mut libdecor_state, 74 | *mut libdecor_configuration, 75 | ), 76 | pub fn libdecor_state_free(*mut libdecor_state), 77 | pub fn libdecor_configuration_get_content_size( 78 | *mut libdecor_configuration, 79 | *mut libdecor_frame, 80 | *mut c_int, 81 | *mut c_int, 82 | ) -> c_int, 83 | pub fn libdecor_frame_get_xdg_surface(*mut libdecor_frame) -> *mut xdg_surface, 84 | pub fn libdecor_frame_get_xdg_toplevel(*mut libdecor_frame) -> *mut xdg_toplevel, 85 | ... 86 | ... 87 | } 88 | -------------------------------------------------------------------------------- /src/native/linux_wayland/extensions/viewporter.rs: -------------------------------------------------------------------------------- 1 | // viewporter.xml 2 | 3 | use super::super::libwayland_client::{wl_interface, wl_message}; 4 | use crate::wayland_interface; 5 | 6 | wayland_interface!( 7 | wp_viewporter_interface, 8 | wp_viewporter, 9 | 3, 10 | [ 11 | (destroy, "", ()), 12 | (get_viewport, "no", (wp_viewport_interface)) 13 | ], 14 | [] 15 | ); 16 | 17 | wayland_interface!( 18 | wp_viewport_interface, 19 | wp_viewport, 20 | 3, 21 | [ 22 | (destroy, "", ()), 23 | (set_source, "ffff", ()), 24 | (set_destination, "ii", ()) 25 | ], 26 | [] 27 | ); 28 | -------------------------------------------------------------------------------- /src/native/linux_wayland/extensions/xdg_decoration.rs: -------------------------------------------------------------------------------- 1 | // xdg-decoration-unstable-v1.xml 2 | 3 | use crate::wayland_interface; 4 | 5 | use super::{ 6 | super::libwayland_client::{wl_interface, wl_message}, 7 | xdg_shell::xdg_toplevel_interface, 8 | }; 9 | 10 | pub const ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE: u32 = 1; 11 | pub const ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE: u32 = 2; 12 | 13 | #[rustfmt::skip] 14 | wayland_interface!( 15 | zxdg_decoration_manager_v1_interface, 16 | zxdg_decoration_manager_v1, 17 | 3, 18 | [ 19 | (destroy, "", ()), 20 | (get_toplevel_decoration, "no", (zxdg_toplevel_decoration_v1_interface, xdg_toplevel_interface)) 21 | ], 22 | [] 23 | ); 24 | 25 | #[rustfmt::skip] 26 | wayland_interface!( 27 | zxdg_toplevel_decoration_v1_interface, 28 | zxdg_toplevel_decoration_v1, 29 | 3, 30 | [ 31 | (destroy, "", ()), 32 | (set_mode, "u", ()), 33 | (unset_mode, "", ())], 34 | [("configure", "u")] 35 | ); 36 | -------------------------------------------------------------------------------- /src/native/linux_wayland/extensions/xdg_shell.rs: -------------------------------------------------------------------------------- 1 | // xdg-shell.xml 2 | 3 | use super::{ 4 | super::libwayland_client::{wl_array, wl_interface, wl_message}, 5 | wayland_protocol::{wl_output_interface, wl_seat_interface}, 6 | }; 7 | use crate::wayland_interface; 8 | 9 | wayland_interface!( 10 | xdg_wm_base_interface, 11 | xdg_wm_base, 12 | 3, 13 | [ 14 | (destroy, "", ()), 15 | (create_positioner, "n", (xdg_positioner_interface)), 16 | (get_xdg_surface, "no", (xdg_surface_interface)), 17 | (pong, "u", ()) 18 | ], 19 | [("ping", "u")] 20 | ); 21 | 22 | wayland_interface!( 23 | xdg_surface_interface, 24 | xdg_surface, 25 | 3, 26 | [ 27 | (destroy, "", ()), 28 | (get_toplevel, "n", (xdg_toplevel_interface)), 29 | (get_popup, "n?oo", ()), 30 | (set_window_geometry, "iiii", ()), 31 | (ack_configure, "u", ()) 32 | ], 33 | [("configure", "u")] 34 | ); 35 | 36 | wayland_interface!( 37 | xdg_toplevel_interface, 38 | xdg_toplevel, 39 | 3, 40 | [ 41 | (destroy, "", ()), 42 | (set_parent, "?o", (xdg_toplevel_interface)), 43 | (set_title, "s", ()), 44 | (set_app_id, "s", ()), 45 | (show_window_menu, "ouii", (xdg_popup_interface)), 46 | (r#move, "ou", (wl_seat_interface)), 47 | (resize, "ouu", (wl_seat_interface)), 48 | (set_max_size, "ii", ()), 49 | (set_min_size, "ii", ()), 50 | (set_maximized, "", ()), 51 | (unset_maximized, "", ()), 52 | (set_fullscreen, "?o", (wl_output_interface)), 53 | (unset_fullscreen, "", ()), 54 | (set_minimized, "", ()) 55 | ], 56 | [("configure", "iia"), ("close", "")] 57 | ); 58 | 59 | wayland_interface!( 60 | xdg_positioner_interface, 61 | xdg_positioner, 62 | 3, 63 | [ 64 | (destroy, "", ()), 65 | (set_size, "ii", ()), 66 | (set_anchor_rect, "iiii", ()), 67 | (set_anchor, "u", ()), 68 | (set_gravity, "u", ()), 69 | (set_constraint_adjustment, "u", ()), 70 | (set_offset, "ii", ()), 71 | (set_reactive, "3", ()), 72 | (set_parent_size, "3ii", ()), 73 | (set_parent_configure, "3u", ()) 74 | ], 75 | [] 76 | ); 77 | 78 | wayland_interface!( 79 | xdg_popup_interface, 80 | xdg_popup, 81 | 3, 82 | [ 83 | (destroy, "", ()), 84 | (grab, "ou", (wl_seat_interface)), 85 | (reposition, "3ou", (xdg_positioner_interface)) 86 | ], 87 | [("configure", "iiii"), ("popup_done", "")] 88 | ); 89 | 90 | crate::wl_listener!( 91 | xdg_wm_base_listener, 92 | xdg_wm_base, 93 | xdg_wm_base_dummy, 94 | fn ping(serial: core::ffi::c_uint), 95 | ); 96 | 97 | crate::wl_listener!( 98 | xdg_surface_listener, 99 | xdg_surface, 100 | xdg_surface_dummy, 101 | fn configure(serial: core::ffi::c_uint), 102 | ); 103 | 104 | crate::wl_listener!( 105 | xdg_toplevel_listener, 106 | xdg_toplevel, 107 | xdg_toplevel_dummy, 108 | fn configure( 109 | width: core::ffi::c_int, 110 | height: core::ffi::c_int, 111 | states: *mut wl_array, 112 | ), 113 | fn close(), 114 | ); 115 | -------------------------------------------------------------------------------- /src/native/linux_wayland/keycodes.rs: -------------------------------------------------------------------------------- 1 | //! There are quite a few different notions for keycodes. And most of them are `u32` so it does get 2 | //! very confusing... 3 | //! Basically 4 | //! - Wayland server sends a scancode `key` of type `c_uint` 5 | //! - `key + 8` becomes a `xkb` scancode of type `xkb_keycode_t` 6 | //! - We feed this to `xkb` to get a `keysym` of type `xkb_keysym_t` 7 | //! - The `keysym` can be modifier-dependent: `Shift + Key1` can be translated to either `Key1` 8 | //! (without modifier) or `Exclam` (with modifier) 9 | //! - We then feed the `keysym` to `translate_keysym` to get a Miniquad `Keycode` 10 | //! 11 | //! Note that the default Miniquad behavior is without modifier; there is not even a Keycode for 12 | //! `Exclam`. So we must provide the unmodified `keysym` or we will get a `Keycode::Unknown`. 13 | //! 14 | //! On the other hand, the modified `keysym` is useful when we want to translate it into the 15 | //! underlying character. 16 | use crate::event::KeyCode; 17 | use crate::native::linux_wayland::libxkbcommon::xkb_keysym_t; 18 | 19 | pub fn translate_keysym(keysym: xkb_keysym_t) -> KeyCode { 20 | // See xkbcommon/xkbcommon-keysyms.h 21 | match keysym { 22 | 65307 => KeyCode::Escape, 23 | 65056 => KeyCode::Tab, // LeftTab 24 | 65289 => KeyCode::Tab, 25 | 65505 => KeyCode::LeftShift, 26 | 65506 => KeyCode::RightShift, 27 | 65507 => KeyCode::LeftControl, 28 | 65508 => KeyCode::RightControl, 29 | 65511 | 65513 => KeyCode::LeftAlt, 30 | 65406 | 65027 | 65512 | 65514 => KeyCode::RightAlt, 31 | 65515 => KeyCode::LeftSuper, 32 | 65516 => KeyCode::RightSuper, 33 | 65383 => KeyCode::Menu, 34 | 65407 => KeyCode::NumLock, 35 | 65509 => KeyCode::CapsLock, 36 | 65377 => KeyCode::PrintScreen, 37 | 65300 => KeyCode::ScrollLock, 38 | 65299 => KeyCode::Pause, 39 | 65535 => KeyCode::Delete, 40 | 65288 => KeyCode::Backspace, 41 | 65293 => KeyCode::Enter, 42 | 65360 => KeyCode::Home, 43 | 65367 => KeyCode::End, 44 | 65365 => KeyCode::PageUp, 45 | 65366 => KeyCode::PageDown, 46 | 65379 => KeyCode::Insert, 47 | 65361 => KeyCode::Left, 48 | 65363 => KeyCode::Right, 49 | 65364 => KeyCode::Down, 50 | 65362 => KeyCode::Up, 51 | 65470 => KeyCode::F1, 52 | 65471 => KeyCode::F2, 53 | 65472 => KeyCode::F3, 54 | 65473 => KeyCode::F4, 55 | 65474 => KeyCode::F5, 56 | 65475 => KeyCode::F6, 57 | 65476 => KeyCode::F7, 58 | 65477 => KeyCode::F8, 59 | 65478 => KeyCode::F9, 60 | 65479 => KeyCode::F10, 61 | 65480 => KeyCode::F11, 62 | 65481 => KeyCode::F12, 63 | 65482 => KeyCode::F13, 64 | 65483 => KeyCode::F14, 65 | 65484 => KeyCode::F15, 66 | 65485 => KeyCode::F16, 67 | 65486 => KeyCode::F17, 68 | 65487 => KeyCode::F18, 69 | 65488 => KeyCode::F19, 70 | 65489 => KeyCode::F20, 71 | 65490 => KeyCode::F21, 72 | 65491 => KeyCode::F22, 73 | 65492 => KeyCode::F23, 74 | 65493 => KeyCode::F24, 75 | 65494 => KeyCode::F25, 76 | 65455 => KeyCode::KpDivide, 77 | 65450 => KeyCode::KpMultiply, 78 | 65453 => KeyCode::KpSubtract, 79 | 65451 => KeyCode::KpAdd, 80 | 65438 => KeyCode::Kp0, 81 | 65436 => KeyCode::Kp1, 82 | 65433 => KeyCode::Kp2, 83 | 65435 => KeyCode::Kp3, 84 | 65430 => KeyCode::Kp4, 85 | 65437 => KeyCode::Kp5, 86 | 65432 => KeyCode::Kp6, 87 | 65429 => KeyCode::Kp7, 88 | 65431 => KeyCode::Kp8, 89 | 65434 => KeyCode::Kp9, 90 | 65439 => KeyCode::KpDecimal, 91 | 65469 => KeyCode::KpEqual, 92 | 65421 => KeyCode::KpEnter, 93 | 65 | 97 => KeyCode::A, 94 | 66 | 98 => KeyCode::B, 95 | 67 | 99 => KeyCode::C, 96 | 68 | 100 => KeyCode::D, 97 | 69 | 101 => KeyCode::E, 98 | 70 | 102 => KeyCode::F, 99 | 71 | 103 => KeyCode::G, 100 | 72 | 104 => KeyCode::H, 101 | 73 | 105 => KeyCode::I, 102 | 74 | 106 => KeyCode::J, 103 | 75 | 107 => KeyCode::K, 104 | 76 | 108 => KeyCode::L, 105 | 77 | 109 => KeyCode::M, 106 | 78 | 110 => KeyCode::N, 107 | 79 | 111 => KeyCode::O, 108 | 80 | 112 => KeyCode::P, 109 | 81 | 113 => KeyCode::Q, 110 | 82 | 114 => KeyCode::R, 111 | 83 | 115 => KeyCode::S, 112 | 84 | 116 => KeyCode::T, 113 | 85 | 117 => KeyCode::U, 114 | 86 | 118 => KeyCode::V, 115 | 87 | 119 => KeyCode::W, 116 | 88 | 120 => KeyCode::X, 117 | 89 | 121 => KeyCode::Y, 118 | 90 | 122 => KeyCode::Z, 119 | 49 => KeyCode::Key1, 120 | 50 => KeyCode::Key2, 121 | 51 => KeyCode::Key3, 122 | 52 => KeyCode::Key4, 123 | 53 => KeyCode::Key5, 124 | 54 => KeyCode::Key6, 125 | 55 => KeyCode::Key7, 126 | 56 => KeyCode::Key8, 127 | 57 => KeyCode::Key9, 128 | 48 => KeyCode::Key0, 129 | 32 => KeyCode::Space, 130 | 45 => KeyCode::Minus, 131 | 61 => KeyCode::Equal, 132 | 91 => KeyCode::LeftBracket, 133 | 93 => KeyCode::RightBracket, 134 | 92 => KeyCode::Backslash, 135 | 59 => KeyCode::Semicolon, 136 | 39 => KeyCode::Apostrophe, 137 | 96 => KeyCode::GraveAccent, 138 | 44 => KeyCode::Comma, 139 | 46 => KeyCode::Period, 140 | 47 => KeyCode::Slash, 141 | 60 => KeyCode::World1, 142 | _ => KeyCode::Unknown, 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/native/linux_wayland/libwayland_egl.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types, dead_code)] 2 | 3 | use super::libwayland_client::wl_surface; 4 | 5 | #[repr(C)] 6 | #[derive(Debug, Copy, Clone)] 7 | pub struct wl_egl_window { 8 | _unused: [u8; 0], 9 | } 10 | 11 | use core::ffi::c_int; 12 | crate::declare_module!( 13 | LibWaylandEgl, 14 | "libwayland-egl.so", 15 | "libwayland-egl.so.1", 16 | ... 17 | ... 18 | pub fn wl_egl_window_create(*mut wl_surface, c_int, c_int) -> *mut wl_egl_window, 19 | pub fn wl_egl_window_destroy(*mut wl_egl_window), 20 | pub fn wl_egl_window_resize(*mut wl_egl_window, c_int, c_int, c_int, c_int), 21 | pub fn wl_egl_window_get_attached_size(*mut wl_egl_window, *mut c_int, *mut c_int), 22 | ... 23 | ... 24 | ); 25 | -------------------------------------------------------------------------------- /src/native/linux_wayland/libxkbcommon.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types, non_upper_case_globals, dead_code)] 2 | 3 | #[repr(C)] 4 | #[derive(Debug, Copy, Clone)] 5 | pub struct xkb_context { 6 | _unused: [u8; 0], 7 | } 8 | #[repr(C)] 9 | #[derive(Debug, Copy, Clone)] 10 | pub struct xkb_keymap { 11 | _unused: [u8; 0], 12 | } 13 | #[repr(C)] 14 | #[derive(Debug, Copy, Clone)] 15 | pub struct xkb_state { 16 | _unused: [u8; 0], 17 | } 18 | 19 | pub const XKB_STATE_MODS_EFFECTIVE: c_int = 1 << 3; 20 | pub const XKB_MOD_NAME_SHIFT: &str = "Shift"; 21 | pub const XKB_MOD_NAME_CTRL: &str = "Control"; 22 | pub const XKB_MOD_NAME_ALT: &str = "Mod1"; 23 | pub const XKB_MOD_NAME_LOGO: &str = "Mod4"; 24 | 25 | use core::ffi::{c_char, c_int, c_uint}; 26 | pub type xkb_keycode_t = c_uint; 27 | pub type xkb_keysym_t = c_uint; 28 | pub type xkb_mod_index_t = c_uint; 29 | crate::declare_module!( 30 | LibXkbCommon, 31 | "libxkbcommon.so", 32 | "libxkbcommon.so.0", 33 | "libxkbcommon.so.0.0.0", 34 | "libxkbcommon.so.0.0.0.0", 35 | ... 36 | ... 37 | pub fn xkb_context_new(c_int) -> *mut xkb_context, 38 | pub fn xkb_context_unref(*mut xkb_context), 39 | pub fn xkb_keymap_new_from_string(*mut xkb_context, *mut libc::FILE, c_int, c_int) -> *mut xkb_keymap, 40 | pub fn xkb_keymap_unref(*mut xkb_keymap), 41 | pub fn xkb_keymap_key_repeats(*mut xkb_keymap, xkb_keycode_t) -> c_int, 42 | pub fn xkb_state_new(*mut xkb_keymap) -> *mut xkb_state, 43 | pub fn xkb_state_unref(*mut xkb_state), 44 | pub fn xkb_state_key_get_one_sym(*mut xkb_state, xkb_keycode_t) -> xkb_keysym_t, 45 | pub fn xkb_keymap_mod_get_index(*mut xkb_keymap, *const c_char) -> xkb_mod_index_t, 46 | pub fn xkb_state_mod_index_is_active(*mut xkb_state, xkb_mod_index_t, c_int) -> c_int, 47 | pub fn xkb_state_update_mask(*mut xkb_state, c_uint, c_uint, c_uint, c_uint, c_uint, c_uint) -> c_int, 48 | pub fn xkb_keysym_to_utf32(xkb_keysym_t) -> c_uint, 49 | ... 50 | ... 51 | ); 52 | 53 | impl LibXkbCommon { 54 | // The keycodes in Miniquad are obtained without modifiers (for example, `Shift + Key1` is 55 | // translated to `Key1` and not `Exclam`) 56 | pub unsafe fn keymap_key_get_sym_without_mod( 57 | &mut self, 58 | keymap: *mut xkb_keymap, 59 | keycode: xkb_keycode_t, 60 | ) -> xkb_keysym_t { 61 | let xkb_state = (self.xkb_state_new)(keymap); 62 | let keysym = (self.xkb_state_key_get_one_sym)(xkb_state, keycode); 63 | (self.xkb_state_unref)(xkb_state); 64 | keysym 65 | } 66 | } 67 | 68 | pub mod libxkbcommon_ex { 69 | use super::*; 70 | use crate::KeyMods; 71 | 72 | /// In `xkb` the modifier indices are tied to a particular `xkb_keymap` and not hardcoded. 73 | #[derive(Copy, Clone)] 74 | pub struct XkbKeymap { 75 | pub xkb_keymap: *mut xkb_keymap, 76 | shift: xkb_mod_index_t, 77 | ctrl: xkb_mod_index_t, 78 | alt: xkb_mod_index_t, 79 | logo: xkb_mod_index_t, 80 | } 81 | 82 | impl Default for XkbKeymap { 83 | fn default() -> Self { 84 | XkbKeymap { 85 | xkb_keymap: std::ptr::null_mut(), 86 | shift: 0, 87 | ctrl: 0, 88 | alt: 0, 89 | logo: 0, 90 | } 91 | } 92 | } 93 | 94 | impl XkbKeymap { 95 | pub unsafe fn cache_mod_indices(&mut self, libxkb: &mut LibXkbCommon) { 96 | let shift = std::ffi::CString::new(XKB_MOD_NAME_SHIFT).unwrap(); 97 | self.shift = (libxkb.xkb_keymap_mod_get_index)(self.xkb_keymap, shift.as_ptr()); 98 | let ctrl = std::ffi::CString::new(XKB_MOD_NAME_CTRL).unwrap(); 99 | self.ctrl = (libxkb.xkb_keymap_mod_get_index)(self.xkb_keymap, ctrl.as_ptr()); 100 | let alt = std::ffi::CString::new(XKB_MOD_NAME_ALT).unwrap(); 101 | self.alt = (libxkb.xkb_keymap_mod_get_index)(self.xkb_keymap, alt.as_ptr()); 102 | let logo = std::ffi::CString::new(XKB_MOD_NAME_LOGO).unwrap(); 103 | self.logo = (libxkb.xkb_keymap_mod_get_index)(self.xkb_keymap, logo.as_ptr()); 104 | } 105 | pub unsafe fn get_keymods( 106 | &self, 107 | libxkb: &mut LibXkbCommon, 108 | xkb_state: *mut xkb_state, 109 | ) -> KeyMods { 110 | let mut mods = KeyMods::default(); 111 | let is_active = libxkb.xkb_state_mod_index_is_active; 112 | if (is_active)(xkb_state, self.shift, XKB_STATE_MODS_EFFECTIVE) == 1 { 113 | mods.shift = true; 114 | } 115 | if (is_active)(xkb_state, self.ctrl, XKB_STATE_MODS_EFFECTIVE) == 1 { 116 | mods.ctrl = true; 117 | } 118 | if (is_active)(xkb_state, self.alt, XKB_STATE_MODS_EFFECTIVE) == 1 { 119 | mods.alt = true; 120 | } 121 | if (is_active)(xkb_state, self.logo, XKB_STATE_MODS_EFFECTIVE) == 1 { 122 | mods.logo = true; 123 | } 124 | mods 125 | } 126 | } 127 | } 128 | 129 | pub use libxkbcommon_ex::XkbKeymap; 130 | -------------------------------------------------------------------------------- /src/native/linux_wayland/shm.rs: -------------------------------------------------------------------------------- 1 | use super::libwayland_client::{ 2 | wl_buffer, wl_proxy, wl_shm, wl_shm_format_WL_SHM_FORMAT_ARGB8888, wl_shm_pool, 3 | LibWaylandClient, WL_SHM_CREATE_POOL, WL_SHM_POOL_CREATE_BUFFER, WL_SHM_POOL_DESTROY, 4 | }; 5 | use crate::wl_request_constructor; 6 | 7 | unsafe fn wl_shm_pool_destroy(libwayland: &mut LibWaylandClient, wl_shm_pool: *mut wl_shm_pool) { 8 | (libwayland.wl_proxy_marshal)(wl_shm_pool as _, WL_SHM_POOL_DESTROY); 9 | (libwayland.wl_proxy_destroy)(wl_shm_pool as _); 10 | } 11 | 12 | unsafe extern "C" fn create_tmpfile_cloexec(tmpname: *mut libc::c_char) -> libc::c_int { 13 | let fd = libc::mkostemp(tmpname, libc::O_CLOEXEC); 14 | if fd >= 0 { 15 | libc::unlink(tmpname); 16 | } 17 | fd 18 | } 19 | 20 | unsafe extern "C" fn create_anonymous_file(size: usize) -> libc::c_int { 21 | let xdg_folder_path = std::env::var("XDG_RUNTIME_DIR").expect("XDG_RUNTIME_DIR not set"); 22 | let filepath = format!("{}/miniquad-shared-XXXXXX", xdg_folder_path); 23 | let c_filepath = std::ffi::CString::new(filepath).unwrap(); 24 | let fd = create_tmpfile_cloexec(c_filepath.as_ptr() as _); 25 | 26 | if fd < 0 { 27 | panic!("Cant create temp file"); 28 | } 29 | 30 | let ret = libc::posix_fallocate(fd, 0, size as _); 31 | 32 | if ret != 0 { 33 | libc::close(fd); 34 | panic!("Cant create temp file") 35 | } 36 | fd 37 | } 38 | 39 | pub unsafe fn create_shm_buffer( 40 | libwayland: &mut LibWaylandClient, 41 | shm: *mut wl_shm, 42 | width: i32, 43 | height: i32, 44 | pixels: &[u8], 45 | ) -> *mut wl_buffer { 46 | let stride = width * 4; 47 | let length = width * height * 4; 48 | 49 | let fd = create_anonymous_file(length as _); 50 | if fd < 0 { 51 | panic!("Failed to create temporary file"); 52 | } 53 | let data = libc::mmap( 54 | std::ptr::null_mut(), 55 | length as _, 56 | libc::PROT_READ | libc::PROT_WRITE, 57 | libc::MAP_SHARED, 58 | fd, 59 | 0, 60 | ); 61 | 62 | if data == -1 as _ { 63 | libc::close(fd); 64 | panic!("Failed to mmap temporary file"); 65 | } 66 | 67 | let pool = wl_request_constructor!( 68 | libwayland, 69 | shm, 70 | WL_SHM_CREATE_POOL, 71 | libwayland.wl_shm_pool_interface, 72 | fd, 73 | length 74 | ); 75 | libc::close(fd); 76 | 77 | let target = data as *mut u8; 78 | for i in 0..width * height * 4 { 79 | *target.offset(i as _) = pixels[i as usize]; 80 | } 81 | 82 | let buffer = wl_request_constructor!( 83 | libwayland, 84 | pool, 85 | WL_SHM_POOL_CREATE_BUFFER, 86 | libwayland.wl_buffer_interface, 87 | 0, 88 | width, 89 | height, 90 | stride, 91 | wl_shm_format_WL_SHM_FORMAT_ARGB8888 92 | ); 93 | 94 | libc::munmap(data, length as _); 95 | 96 | wl_shm_pool_destroy(libwayland, pool); 97 | buffer 98 | } 99 | -------------------------------------------------------------------------------- /src/native/linux_x11/clipboard.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals, non_snake_case)] 2 | 3 | //! Clipboard implementation for X11 4 | //! 5 | //! Clipboard API on X11 is pretty weird 6 | //! 7 | //! so use this with caution. 8 | 9 | use super::libx11::*; 10 | 11 | unsafe fn get_clipboard( 12 | libx11: &mut LibX11, 13 | display: *mut Display, 14 | window: Window, 15 | format: Atom, 16 | ) -> Option> { 17 | // We ask X server to ask current clipboard owner to convert selection to the desired format 18 | // Selection owner is other X11 application and if it will not satisfy our request for any reason - 19 | // we will wait for appropriate event forever 20 | // but OK lets believe that all other linux apps will gracefully send us nice utf-8 strings 21 | (libx11.XConvertSelection)( 22 | display, 23 | libx11.extensions.clipboard, 24 | format, 25 | libx11.extensions.xsel_data, 26 | window, 27 | CurrentTime as Time, 28 | ); 29 | 30 | // And now X server will respond with clipboard data 31 | // But we want "get_clipboard" to be blocking function and get result asap 32 | // So we just start to wait for the event right here 33 | // In case that our app already is clipboard owner - we need to handle SelectionNotify for data response 34 | // and SelectionRequest - to handle this request we just did couple of lines above 35 | let mut event = XEvent { type_0: 0 }; 36 | loop { 37 | (libx11.XNextEvent)(display, &mut event); 38 | if event.type_0 == SelectionNotify 39 | && event.xselection.selection == libx11.extensions.clipboard 40 | { 41 | // The property should be XSEL_DATA, otherwise we return None 42 | return (event.xselection.property == libx11.extensions.xsel_data) 43 | .then(|| get_property_bytes(libx11, display, window, event.xselection.property)); 44 | } 45 | if event.type_0 == SelectionRequest { 46 | respond_to_clipboard_request(libx11, display, &mut event as *mut _); 47 | } 48 | } 49 | } 50 | 51 | /// Get the bytes from the window property 52 | /// INCR protocol is implemented; will block until all data are received 53 | /// Useful for getting data following a SelectionNotify event 54 | // 55 | // X11 has the INCR protocol to transfer data in several batches 56 | // https://www.x.org/releases/X11R7.7/doc/xorg-docs/icccm/icccm.html#INCR_Properties 57 | // 58 | // The data are sequentially written to the given property on the window 59 | // After each read, we delete the property to notify X that we are ready to receive more data 60 | // Used both for the clipboard and the drag-n-drop protocol 61 | pub(crate) unsafe fn get_property_bytes( 62 | libx11: &mut LibX11, 63 | display: *mut Display, 64 | window: Window, 65 | property: Atom, 66 | ) -> Vec { 67 | let mut bytes = Vec::new(); 68 | let mut buf = std::ptr::null_mut::(); 69 | let mut size: libc::c_ulong = 0; 70 | let mut actual_type = 0 as Atom; 71 | let mut actual_format: libc::c_int = 0; 72 | let mut _size_after: libc::c_ulong = 0; 73 | let mut incr_mode = false; 74 | loop { 75 | (libx11.XGetWindowProperty)( 76 | display, 77 | window, 78 | property, 79 | 0 as _, 80 | libc::c_long::MAX, 81 | false as _, 82 | AnyPropertyType, 83 | &mut actual_type, 84 | &mut actual_format, 85 | &mut size, 86 | &mut _size_after, 87 | &mut buf as *mut *mut libc::c_char as _, 88 | ); 89 | 90 | if actual_type == libx11.extensions.incr { 91 | // we are in INCR mode 92 | incr_mode = true; 93 | } else if size == 0 { 94 | // no more data to read 95 | (libx11.XFree)(buf as *mut libc::c_void); 96 | return bytes; 97 | } else { 98 | let n_bits = match actual_format { 99 | 8 => std::mem::size_of::(), 100 | 16 => std::mem::size_of::(), 101 | 32 => std::mem::size_of::(), 102 | _ => unreachable!(), 103 | }; 104 | bytes.extend(std::slice::from_raw_parts( 105 | buf as *const _, 106 | n_bits * size as usize, 107 | )); 108 | 109 | // if not in INCR mode, then we've got all the data 110 | if !incr_mode { 111 | (libx11.XFree)(buf as *mut libc::c_void); 112 | return bytes; 113 | } 114 | } 115 | 116 | if incr_mode { 117 | (libx11.XDeleteProperty)(display, window, property); 118 | // wait until we get a new batch 119 | let mut event = XEvent { type_0: 0 }; 120 | loop { 121 | (libx11.XNextEvent)(display, &mut event); 122 | if event.type_0 == PropertyNotify && event.xproperty.state == PropertyNewValue { 123 | break; 124 | } 125 | } 126 | } 127 | } 128 | } 129 | 130 | // Next message for clipboard request 131 | static mut MESSAGE: Option = None; 132 | 133 | /// Claim that our app is X11 clipboard owner 134 | /// Now when some other linux app will ask X11 for clipboard content - it will be redirected to our app 135 | unsafe fn claim_clipboard_ownership( 136 | libx11: &mut LibX11, 137 | display: *mut Display, 138 | window: Window, 139 | message: String, 140 | ) { 141 | (libx11.XSetSelectionOwner)( 142 | display, 143 | libx11.extensions.clipboard, 144 | window, 145 | CurrentTime as Time, 146 | ); 147 | 148 | MESSAGE = Some(message); 149 | } 150 | 151 | /// this function is supposed to be called from sapp's event loop 152 | /// when XSelectionEvent received. 153 | /// It will parse event and call XSendEvent with event response 154 | pub(crate) unsafe fn respond_to_clipboard_request( 155 | libx11: &mut LibX11, 156 | display: *mut Display, 157 | event: *const XEvent, 158 | ) { 159 | assert!((*event).type_0 == SelectionRequest); // is it really SelectionRequest 160 | 161 | let empty_message = String::new(); 162 | let message = MESSAGE.as_ref().unwrap_or(&empty_message); 163 | 164 | let utf8_string = libx11.extensions.utf8_string; 165 | let xselectionrequest = (*event).xselectionrequest; 166 | let mut ev = XSelectionEvent { 167 | type_0: SelectionNotify, 168 | serial: 0, 169 | send_event: 0, 170 | display: xselectionrequest.display, 171 | requestor: xselectionrequest.requestor, 172 | selection: xselectionrequest.selection, 173 | target: xselectionrequest.target, 174 | property: xselectionrequest.property, 175 | time: xselectionrequest.time, 176 | }; 177 | 178 | // only UTF8 requests are supported 179 | if xselectionrequest.target == utf8_string { 180 | (libx11.XChangeProperty)( 181 | xselectionrequest.display, 182 | xselectionrequest.requestor, 183 | xselectionrequest.property, 184 | utf8_string, 185 | 8 as libc::c_int, 186 | PropModeReplace, 187 | message.as_bytes().as_ptr(), 188 | message.len() as _, 189 | ); 190 | 191 | (libx11.XSendEvent)( 192 | display, 193 | ev.requestor, 194 | false as _, 195 | NoEventMask, 196 | &mut ev as *mut XSelectionEvent as *mut XEvent, 197 | ); 198 | } else { 199 | // signal X that request is denied 200 | ev.property = 0 as Atom; 201 | 202 | (libx11.XSendEvent)( 203 | display, 204 | ev.requestor, 205 | false as _, 206 | NoEventMask, 207 | &mut ev as *mut XSelectionEvent as *mut XEvent, 208 | ); 209 | } 210 | } 211 | 212 | pub struct X11Clipboard { 213 | libx11: LibX11, 214 | display: *mut Display, 215 | window: Window, 216 | } 217 | unsafe impl Send for X11Clipboard {} 218 | unsafe impl Sync for X11Clipboard {} 219 | 220 | impl X11Clipboard { 221 | pub fn new(libx11: LibX11, display: *mut Display, window: Window) -> X11Clipboard { 222 | X11Clipboard { 223 | libx11, 224 | display, 225 | window, 226 | } 227 | } 228 | } 229 | impl crate::native::Clipboard for X11Clipboard { 230 | fn get(&mut self) -> Option { 231 | let utf8_string = self.libx11.extensions.utf8_string; 232 | let bytes = 233 | unsafe { get_clipboard(&mut self.libx11, self.display, self.window, utf8_string)? }; 234 | Some(std::str::from_utf8(&bytes).ok()?.to_string()) 235 | } 236 | 237 | fn set(&mut self, data: &str) { 238 | unsafe { 239 | claim_clipboard_ownership(&mut self.libx11, self.display, self.window, data.to_owned()); 240 | }; 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/native/linux_x11/drag_n_drop.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals, non_snake_case)] 2 | 3 | // Drag-n-Drop specification at 4 | // https://freedesktop.org/wiki/Specifications/XDND/ 5 | // C++ implementation example 6 | // https://www.codeproject.com/Tips/5387630/How-to-handle-X11-Drag-n-Drop-events 7 | 8 | // The basic logic is similar to the mouse events 9 | // mouse_down => on_enter 10 | // mouse_motion => on_pos 11 | // mouse_up => on_drop 12 | 13 | use super::libx11::*; 14 | 15 | #[derive(Default)] 16 | pub struct X11DnD { 17 | source: Window, // source window 18 | version: libc::c_long, // version of XDnD, should be 5 19 | format: Atom, // we want UTF8_STRING for the paths of the dropped files 20 | } 21 | 22 | impl super::X11Display { 23 | pub(super) unsafe fn init_drag_n_drop(&mut self) { 24 | // Add the window property XdndAware to inform X that we accept drag-n-drop 25 | (self.libx11.XChangeProperty)( 26 | self.display, 27 | self.window, 28 | self.libx11.extensions.xdnd_aware, 29 | 4 as _, 30 | 32, 31 | PropModeReplace, 32 | [5].as_ptr() as _, // XDnD version number 33 | 1, 34 | ); 35 | } 36 | } 37 | 38 | impl X11DnD { 39 | pub unsafe fn on_enter( 40 | &mut self, 41 | libx11: &mut LibX11, 42 | display: *mut Display, 43 | _window: Window, 44 | data: ClientMessageData, 45 | ) { 46 | // Store the relevant info 47 | self.source = data.l[0] as _; 48 | self.version = data.l[1] >> 24; 49 | 50 | // Then we need to determine the format (or type) to get the data 51 | // We want UTF8_STRING 52 | // The available ones depend on the source window and their number can vary 53 | self.format = 0; 54 | let formats: Vec = if data.l[1] & 1 == 0 { 55 | // If there are fewer than 3, they are already in the client message we got 56 | vec![data.l[2] as _, data.l[3] as _, data.l[4] as _] 57 | } else { 58 | // Otherwise we need to read them from the window property 59 | let bytes = super::clipboard::get_property_bytes( 60 | libx11, 61 | display, 62 | self.source, 63 | libx11.extensions.xdnd_type_list, 64 | ); 65 | // Realign the bytes into Atoms 66 | bytes.align_to::().1.to_vec() 67 | }; 68 | 69 | for format in formats { 70 | if format == libx11.extensions.utf8_string { 71 | self.format = format; 72 | break; 73 | } 74 | } 75 | } 76 | 77 | pub unsafe fn on_position( 78 | &mut self, 79 | libx11: &mut LibX11, 80 | display: *mut Display, 81 | window: Window, 82 | _data: ClientMessageData, 83 | ) { 84 | if self.version <= 5 { 85 | // We need to send back a client message of type XdndStatus 86 | let mut reply = XClientMessageEvent { 87 | type_0: ClientMessage, 88 | serial: 0, 89 | send_event: true as _, 90 | message_type: libx11.extensions.xdnd_status, 91 | window: self.source, 92 | display, 93 | format: 32, 94 | data: ClientMessageData { 95 | l: [window as _, 0, 0, 0, 0], 96 | }, 97 | }; 98 | 99 | // The source window supports the desired format 100 | if self.format != 0 { 101 | // Notify that we can receive the drop 102 | reply.data.l[1] = 1; 103 | 104 | // Notify that we accept the XdndActionCopy action 105 | if self.version >= 2 { 106 | reply.data.l[4] = libx11.extensions.xdnd_action_copy as _; 107 | } 108 | } 109 | (libx11.XSendEvent)( 110 | display, 111 | self.source, 112 | false as _, 113 | NoEventMask, 114 | &mut reply as *mut XClientMessageEvent as *mut _, 115 | ); 116 | (libx11.XFlush)(display); 117 | } 118 | } 119 | 120 | pub unsafe fn on_drop( 121 | &mut self, 122 | libx11: &mut LibX11, 123 | display: *mut Display, 124 | window: Window, 125 | data: ClientMessageData, 126 | ) { 127 | if self.version <= 5 { 128 | if self.format != 0 { 129 | // Request to retrieve the data 130 | // The actual data will then be sent via a SelectionNotify event 131 | let mut time = CurrentTime; 132 | if self.version >= 1 { 133 | time = data.l[3]; 134 | } 135 | (libx11.XConvertSelection)( 136 | display, 137 | libx11.extensions.xdnd_selection, 138 | self.format, 139 | libx11.extensions.xdnd_selection, 140 | window, 141 | time as Time, 142 | ); 143 | } else if self.version >= 2 { 144 | let mut reply = XClientMessageEvent { 145 | type_0: ClientMessage, 146 | serial: 0, 147 | send_event: true as _, 148 | message_type: libx11.extensions.xdnd_finished, 149 | window: self.source, 150 | display, 151 | format: 32, 152 | data: ClientMessageData { 153 | l: [window as _, 0, 0, 0, 0], 154 | }, 155 | }; 156 | (libx11.XSendEvent)( 157 | display, 158 | self.source, 159 | false as _, 160 | NoEventMask, 161 | &mut reply as *mut XClientMessageEvent as *mut _, 162 | ); 163 | (libx11.XFlush)(display); 164 | } 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/native/linux_x11/keycodes.rs: -------------------------------------------------------------------------------- 1 | //! Varios X11 keycode to event mappings 2 | //! for keyboard, mouse, basically any long table of keycode->enum belongs here 3 | //! 4 | //! All the data entries in this file came from sokol_app.h 5 | 6 | use super::{Display, LibX11}; 7 | use crate::event::{KeyCode, KeyMods, MouseButton}; 8 | use crate::native::linux_x11::libx11::LibXkbCommon; 9 | 10 | pub unsafe fn translate_key(libx11: &mut LibX11, display: *mut Display, scancode: i32) -> KeyCode { 11 | let mut dummy: libc::c_int = 0; 12 | let keysyms = 13 | (libx11.XGetKeyboardMapping)(display, scancode as _, 1 as libc::c_int, &mut dummy); 14 | assert!(!keysyms.is_null()); 15 | 16 | let keysym = *keysyms.offset(0 as libc::c_int as isize); 17 | (libx11.XFree)(keysyms as *mut libc::c_void); 18 | match keysym { 19 | 65307 => KeyCode::Escape, 20 | 65289 => KeyCode::Tab, 21 | 65505 => KeyCode::LeftShift, 22 | 65506 => KeyCode::RightShift, 23 | 65507 => KeyCode::LeftControl, 24 | 65508 => KeyCode::RightControl, 25 | 65511 | 65513 => KeyCode::LeftAlt, 26 | 65406 | 65027 | 65512 | 65514 => KeyCode::RightAlt, 27 | 65515 => KeyCode::LeftSuper, 28 | 65516 => KeyCode::RightSuper, 29 | 65383 => KeyCode::Menu, 30 | 65407 => KeyCode::NumLock, 31 | 65509 => KeyCode::CapsLock, 32 | 65377 => KeyCode::PrintScreen, 33 | 65300 => KeyCode::ScrollLock, 34 | 65299 => KeyCode::Pause, 35 | 65535 => KeyCode::Delete, 36 | 65288 => KeyCode::Backspace, 37 | 65293 => KeyCode::Enter, 38 | 65360 => KeyCode::Home, 39 | 65367 => KeyCode::End, 40 | 65365 => KeyCode::PageUp, 41 | 65366 => KeyCode::PageDown, 42 | 65379 => KeyCode::Insert, 43 | 65361 => KeyCode::Left, 44 | 65363 => KeyCode::Right, 45 | 65364 => KeyCode::Down, 46 | 65362 => KeyCode::Up, 47 | 65470 => KeyCode::F1, 48 | 65471 => KeyCode::F2, 49 | 65472 => KeyCode::F3, 50 | 65473 => KeyCode::F4, 51 | 65474 => KeyCode::F5, 52 | 65475 => KeyCode::F6, 53 | 65476 => KeyCode::F7, 54 | 65477 => KeyCode::F8, 55 | 65478 => KeyCode::F9, 56 | 65479 => KeyCode::F10, 57 | 65480 => KeyCode::F11, 58 | 65481 => KeyCode::F12, 59 | 65482 => KeyCode::F13, 60 | 65483 => KeyCode::F14, 61 | 65484 => KeyCode::F15, 62 | 65485 => KeyCode::F16, 63 | 65486 => KeyCode::F17, 64 | 65487 => KeyCode::F18, 65 | 65488 => KeyCode::F19, 66 | 65489 => KeyCode::F20, 67 | 65490 => KeyCode::F21, 68 | 65491 => KeyCode::F22, 69 | 65492 => KeyCode::F23, 70 | 65493 => KeyCode::F24, 71 | 65494 => KeyCode::F25, 72 | 65455 => KeyCode::KpDivide, 73 | 65450 => KeyCode::KpMultiply, 74 | 65453 => KeyCode::KpSubtract, 75 | 65451 => KeyCode::KpAdd, 76 | 65438 => KeyCode::Kp0, 77 | 65436 => KeyCode::Kp1, 78 | 65433 => KeyCode::Kp2, 79 | 65435 => KeyCode::Kp3, 80 | 65430 => KeyCode::Kp4, 81 | 65437 => KeyCode::Kp5, 82 | 65432 => KeyCode::Kp6, 83 | 65429 => KeyCode::Kp7, 84 | 65431 => KeyCode::Kp8, 85 | 65434 => KeyCode::Kp9, 86 | 65439 => KeyCode::KpDecimal, 87 | 65469 => KeyCode::KpEqual, 88 | 65421 => KeyCode::KpEnter, 89 | 97 => KeyCode::A, 90 | 98 => KeyCode::B, 91 | 99 => KeyCode::C, 92 | 100 => KeyCode::D, 93 | 101 => KeyCode::E, 94 | 102 => KeyCode::F, 95 | 103 => KeyCode::G, 96 | 104 => KeyCode::H, 97 | 105 => KeyCode::I, 98 | 106 => KeyCode::J, 99 | 107 => KeyCode::K, 100 | 108 => KeyCode::L, 101 | 109 => KeyCode::M, 102 | 110 => KeyCode::N, 103 | 111 => KeyCode::O, 104 | 112 => KeyCode::P, 105 | 113 => KeyCode::Q, 106 | 114 => KeyCode::R, 107 | 115 => KeyCode::S, 108 | 116 => KeyCode::T, 109 | 117 => KeyCode::U, 110 | 118 => KeyCode::V, 111 | 119 => KeyCode::W, 112 | 120 => KeyCode::X, 113 | 121 => KeyCode::Y, 114 | 122 => KeyCode::Z, 115 | 49 => KeyCode::Key1, 116 | 50 => KeyCode::Key2, 117 | 51 => KeyCode::Key3, 118 | 52 => KeyCode::Key4, 119 | 53 => KeyCode::Key5, 120 | 54 => KeyCode::Key6, 121 | 55 => KeyCode::Key7, 122 | 56 => KeyCode::Key8, 123 | 57 => KeyCode::Key9, 124 | 48 => KeyCode::Key0, 125 | 32 => KeyCode::Space, 126 | 45 => KeyCode::Minus, 127 | 61 => KeyCode::Equal, 128 | 91 => KeyCode::LeftBracket, 129 | 93 => KeyCode::RightBracket, 130 | 92 => KeyCode::Backslash, 131 | 59 => KeyCode::Semicolon, 132 | 39 => KeyCode::Apostrophe, 133 | 96 => KeyCode::GraveAccent, 134 | 44 => KeyCode::Comma, 135 | 46 => KeyCode::Period, 136 | 47 => KeyCode::Slash, 137 | 60 => KeyCode::World1, 138 | _ => KeyCode::Unknown, 139 | } 140 | } 141 | 142 | pub unsafe fn translate_mod(x11_mods: i32) -> KeyMods { 143 | let mut mods = KeyMods::default(); 144 | if x11_mods & super::libx11::ShiftMask != 0 { 145 | mods.shift = true; 146 | } 147 | if x11_mods & super::libx11::ControlMask != 0 { 148 | mods.ctrl = true; 149 | } 150 | if x11_mods & super::libx11::Mod1Mask != 0 { 151 | mods.alt = true; 152 | } 153 | if x11_mods & super::libx11::Mod4Mask != 0 { 154 | mods.logo = true; 155 | } 156 | mods 157 | } 158 | 159 | pub unsafe fn translate_mouse_button(button: i32) -> MouseButton { 160 | match button { 161 | 1 => MouseButton::Left, 162 | 2 => MouseButton::Middle, 163 | 3 => MouseButton::Right, 164 | _ => MouseButton::Unknown, 165 | } 166 | } 167 | 168 | pub unsafe extern "C" fn keysym_to_unicode( 169 | libxkbcommon: &mut LibXkbCommon, 170 | keysym: super::libx11::KeySym, 171 | ) -> i32 { 172 | (libxkbcommon.xkb_keysym_to_utf32)(keysym as u32) as i32 173 | } 174 | -------------------------------------------------------------------------------- /src/native/linux_x11/libx11_ex.rs: -------------------------------------------------------------------------------- 1 | // little helpers for LibX11 2 | use super::*; 3 | 4 | impl LibX11 { 5 | pub unsafe fn update_system_dpi(&mut self, display: *mut Display) -> f32 { 6 | let mut dpi_scale = 1.; 7 | let rms = (self.XResourceManagerString)(display); 8 | if !rms.is_null() { 9 | let db = (self.XrmGetStringDatabase)(rms); 10 | if !db.is_null() { 11 | let mut value = XrmValue { 12 | size: 0, 13 | addr: std::ptr::null_mut::(), 14 | }; 15 | let mut type_ = std::ptr::null_mut(); 16 | if (self.XrmGetResource)( 17 | db, 18 | b"Xft.dpi\x00".as_ptr() as _, 19 | b"Xft.Dpi\x00".as_ptr() as _, 20 | &mut type_, 21 | &mut value, 22 | ) != 0 23 | { 24 | if !type_.is_null() && libc::strcmp(type_, b"String\x00".as_ptr() as _) == 0 { 25 | dpi_scale = libc::atof(value.addr as *const _) as f32 / 96.0; 26 | } 27 | } 28 | (self.XrmDestroyDatabase)(db); 29 | } 30 | }; 31 | dpi_scale 32 | } 33 | 34 | pub unsafe fn grab_error_handler(&mut self) { 35 | pub unsafe extern "C" fn _sapp_x11_error_handler( 36 | mut _display: *mut Display, 37 | event: *mut XErrorEvent, 38 | ) -> libc::c_int { 39 | eprintln!("Error: {}", (*event).error_code); 40 | 0 as libc::c_int 41 | } 42 | 43 | (self.XSetErrorHandler)(Some( 44 | _sapp_x11_error_handler 45 | as unsafe extern "C" fn(_: *mut Display, _: *mut XErrorEvent) -> libc::c_int, 46 | )); 47 | } 48 | 49 | pub unsafe fn release_error_handler(&mut self, display: *mut Display) { 50 | (self.XSync)(display, false as _); 51 | (self.XSetErrorHandler)(None); 52 | } 53 | 54 | pub unsafe fn query_window_size( 55 | &mut self, 56 | display: *mut Display, 57 | window: Window, 58 | ) -> (i32, i32) { 59 | let mut attribs: XWindowAttributes = std::mem::zeroed(); 60 | (self.XGetWindowAttributes)(display, window, &mut attribs); 61 | (attribs.width, attribs.height) 62 | } 63 | 64 | pub unsafe fn update_window_title( 65 | &mut self, 66 | display: *mut Display, 67 | window: Window, 68 | title: &str, 69 | ) { 70 | let c_title = std::ffi::CString::new(title).unwrap(); 71 | 72 | (self.Xutf8SetWMProperties)( 73 | display, 74 | window, 75 | c_title.as_ptr(), 76 | c_title.as_ptr(), 77 | std::ptr::null_mut(), 78 | 0 as libc::c_int, 79 | std::ptr::null_mut(), 80 | std::ptr::null_mut(), 81 | std::ptr::null_mut(), 82 | ); 83 | (self.XChangeProperty)( 84 | display, 85 | window, 86 | self.extensions.net_wm_name, 87 | self.extensions.utf8_string, 88 | 8, 89 | PropModeReplace, 90 | c_title.as_ptr() as *mut libc::c_uchar, 91 | c_title.as_bytes().len() as libc::c_int, 92 | ); 93 | (self.XChangeProperty)( 94 | display, 95 | window, 96 | self.extensions.net_wm_icon_name, 97 | self.extensions.utf8_string, 98 | 8 as libc::c_int, 99 | PropModeReplace, 100 | c_title.as_ptr() as *mut libc::c_uchar, 101 | c_title.as_bytes().len() as libc::c_int, 102 | ); 103 | (self.XFlush)(display); 104 | } 105 | 106 | pub unsafe fn update_window_icon( 107 | &mut self, 108 | display: *mut Display, 109 | window: Window, 110 | icon: &crate::conf::Icon, 111 | ) { 112 | let bytes_length = (icon.small.len() + icon.medium.len() + icon.big.len()) / 4 + 3 * 2; 113 | 114 | // "This is an array of 32bit packed CARDINAL ARGB" (c) freedesktop 115 | // While, in fact, X11 expect this to be 64bit on 64bit systems, which is proved 116 | // by quite a few "unsigned long icon[]" I've seen in quite a few real-life programs. 117 | // Leaving this comment nn case of icon-related bugs in the future. 118 | let mut icon_bytes: Vec = vec![0; bytes_length]; 119 | let icons = [ 120 | (16, &icon.small[..]), 121 | (32, &icon.medium[..]), 122 | (64, &icon.big[..]), 123 | ]; 124 | 125 | { 126 | let mut target = icon_bytes.iter_mut(); 127 | for (dim, pixels) in icons { 128 | *target.next().unwrap() = dim; 129 | *target.next().unwrap() = dim; 130 | for pixels in pixels.chunks(4) { 131 | let r = pixels[0] as u32; 132 | let g = pixels[1] as u32; 133 | let b = pixels[2] as u32; 134 | let a = pixels[3] as u32; 135 | 136 | *target.next().unwrap() = 137 | ((r << 16) | (g << 8) | (b << 0) | (a << 24)) as usize; 138 | } 139 | } 140 | } 141 | 142 | (self.XChangeProperty)( 143 | display, 144 | window, 145 | self.extensions.net_wm_icon, 146 | self.extensions.cardinal, 147 | 32, 148 | PropModeReplace, 149 | icon_bytes.as_mut_ptr() as *mut _ as *mut _, 150 | icon_bytes.len() as _, 151 | ); 152 | } 153 | 154 | pub unsafe fn create_window( 155 | &mut self, 156 | root: Window, 157 | display: *mut Display, 158 | visual: *mut Visual, 159 | depth: libc::c_int, 160 | conf: &crate::conf::Conf, 161 | ) -> Window { 162 | let mut wa = XSetWindowAttributes { 163 | background_pixmap: 0, 164 | background_pixel: 0, 165 | border_pixmap: 0, 166 | border_pixel: 0, 167 | bit_gravity: 0, 168 | win_gravity: 0, 169 | backing_store: 0, 170 | backing_planes: 0, 171 | backing_pixel: 0, 172 | save_under: 0, 173 | event_mask: 0, 174 | do_not_propagate_mask: 0, 175 | override_redirect: 0, 176 | colormap: 0, 177 | cursor: 0, 178 | }; 179 | libc::memset( 180 | &mut wa as *mut XSetWindowAttributes as *mut libc::c_void, 181 | 0 as libc::c_int, 182 | ::std::mem::size_of::() as _, 183 | ); 184 | let wamask = (CWBorderPixel | CWColormap | CWEventMask) as u32; 185 | 186 | if !visual.is_null() { 187 | let colormap = (self.XCreateColormap)(display, root, visual, AllocNone); 188 | wa.colormap = colormap; 189 | } 190 | wa.border_pixel = 0 as libc::c_int as libc::c_ulong; 191 | wa.event_mask = StructureNotifyMask 192 | | KeyPressMask 193 | | KeyReleaseMask 194 | | PointerMotionMask 195 | | ButtonPressMask 196 | | ButtonReleaseMask 197 | | ExposureMask 198 | | FocusChangeMask 199 | | VisibilityChangeMask 200 | | EnterWindowMask 201 | | LeaveWindowMask 202 | | PropertyChangeMask; 203 | self.grab_error_handler(); 204 | 205 | let window = (self.XCreateWindow)( 206 | display, 207 | root, 208 | 0 as libc::c_int, 209 | 0 as libc::c_int, 210 | conf.window_width as _, 211 | conf.window_height as _, 212 | 0 as libc::c_int as libc::c_uint, 213 | depth, 214 | InputOutput as libc::c_uint, 215 | visual, 216 | wamask as libc::c_ulong, 217 | &mut wa, 218 | ); 219 | self.release_error_handler(display); 220 | assert!(window != 0, "X11: Failed to create window"); 221 | 222 | let mut protocols: [Atom; 1] = [self.extensions.wm_delete_window]; 223 | (self.XSetWMProtocols)(display, window, protocols.as_mut_ptr(), 1 as libc::c_int); 224 | let hints = (self.XAllocSizeHints)(); 225 | (*hints).flags |= PWinGravity; 226 | if !conf.window_resizable { 227 | (*hints).flags |= PMinSize | PMaxSize; 228 | (*hints).min_width = conf.window_width; 229 | (*hints).min_height = conf.window_height; 230 | (*hints).max_width = conf.window_width; 231 | (*hints).max_height = conf.window_height; 232 | } 233 | (*hints).win_gravity = StaticGravity; 234 | (self.XSetWMNormalHints)(display, window, hints); 235 | (self.XFree)(hints as *mut libc::c_void); 236 | 237 | let class_hint = (self.XAllocClassHint)(); 238 | let wm_class = std::ffi::CString::new(conf.platform.linux_wm_class).unwrap(); 239 | (*class_hint).res_name = wm_class.as_ptr() as _; 240 | (*class_hint).res_class = wm_class.as_ptr() as _; 241 | (self.XSetClassHint)(display, window, class_hint); 242 | (self.XFree)(class_hint as *mut libc::c_void); 243 | 244 | if let Some(ref icon) = conf.icon { 245 | self.update_window_icon(display, window, icon); 246 | } 247 | // For an unknown reason, this should happen after update_window_icon, 248 | // otherwise X11 will skip ChangeProperty(WM_ICON) 249 | self.update_window_title(display, window, &conf.window_title); 250 | 251 | window 252 | } 253 | 254 | pub unsafe fn show_window(&mut self, display: *mut Display, window: Window) { 255 | (self.XMapWindow)(display, window); 256 | (self.XRaiseWindow)(display, window); 257 | (self.XFlush)(display); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/native/linux_x11/rand.rs: -------------------------------------------------------------------------------- 1 | extern "C" { 2 | pub fn rand() -> ::core::ffi::c_int; 3 | } 4 | pub const RAND_MAX: u32 = 2147483647; 5 | -------------------------------------------------------------------------------- /src/native/linux_x11/x_cursor.rs: -------------------------------------------------------------------------------- 1 | use super::libx11::{Cursor, Display, LibX11, Window, XColor}; 2 | 3 | pub unsafe fn create_empty_cursor( 4 | display: *mut Display, 5 | root: Window, 6 | libx11: &mut LibX11, 7 | ) -> Cursor { 8 | let mut cursor_color = XColor { 9 | pixel: 0 as libc::c_int as libc::c_ulong, 10 | red: 0, 11 | green: 0, 12 | blue: 0, 13 | flags: 0, 14 | pad: 0, 15 | }; 16 | 17 | let mut cursor_color_data: [libc::c_char; 1] = [0 as libc::c_int as libc::c_char]; 18 | let cursor_pixmap = (libx11.XCreateBitmapFromData)( 19 | display, 20 | root, 21 | cursor_color_data.as_mut_ptr(), 22 | 1 as libc::c_int as libc::c_uint, 23 | 1 as libc::c_int as libc::c_uint, 24 | ); 25 | let empty_cursor = (libx11.XCreatePixmapCursor)( 26 | display, 27 | cursor_pixmap, 28 | cursor_pixmap, 29 | &mut cursor_color, 30 | &mut cursor_color, 31 | 0 as libc::c_int as libc::c_uint, 32 | 0 as libc::c_int as libc::c_uint, 33 | ); 34 | (libx11.XFreePixmap)(display, cursor_pixmap); 35 | 36 | empty_cursor 37 | } 38 | -------------------------------------------------------------------------------- /src/native/linux_x11/xi_input.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals, non_snake_case)] 2 | 3 | use super::{ 4 | libx11::{self, Display, Window, _XPrivDisplay}, 5 | xi_input, 6 | }; 7 | 8 | pub const XIAllDevices: libc::c_int = 0 as libc::c_int; 9 | pub const XI_RawMotion: libc::c_int = 17 as libc::c_int; 10 | pub const XI_RawMotionMask: libc::c_int = (1 as libc::c_int) << XI_RawMotion; 11 | 12 | #[derive(Copy, Clone)] 13 | #[repr(C)] 14 | pub struct XIEventMask { 15 | pub deviceid: libc::c_int, 16 | pub mask_len: libc::c_int, 17 | pub mask: *mut libc::c_uchar, 18 | } 19 | 20 | #[derive(Copy, Clone)] 21 | #[repr(C)] 22 | pub struct XIValuatorState { 23 | pub mask_len: libc::c_int, 24 | pub mask: *mut libc::c_uchar, 25 | pub values: *mut libc::c_double, 26 | } 27 | 28 | pub type Time = libc::c_ulong; 29 | 30 | #[derive(Copy, Clone)] 31 | #[repr(C)] 32 | pub struct XIRawEvent { 33 | pub type_0: libc::c_int, 34 | pub serial: libc::c_ulong, 35 | pub send_event: libc::c_int, 36 | pub display: *mut Display, 37 | pub extension: libc::c_int, 38 | pub evtype: libc::c_int, 39 | pub time: Time, 40 | pub deviceid: libc::c_int, 41 | pub sourceid: libc::c_int, 42 | pub detail: libc::c_int, 43 | pub flags: libc::c_int, 44 | pub valuators: XIValuatorState, 45 | pub raw_values: *mut libc::c_double, 46 | } 47 | 48 | use core::ffi::{c_char, c_int}; 49 | crate::declare_module!( 50 | LibXi, 51 | "libXi.so", 52 | "libXi.so.6", 53 | ... 54 | ... 55 | pub fn XQueryExtension(*mut Display, *const c_char, *mut c_int, *mut c_int, *mut c_int) -> c_int, 56 | pub fn XIQueryVersion(*mut Display, *mut c_int, *mut c_int) -> c_int, 57 | pub fn XISelectEvents(*mut Display, Window, *mut XIEventMask, c_int), 58 | pub fn XGetEventData(*mut Display, *mut libx11::XGenericEventCookie) -> c_int, 59 | pub fn XFreeEventData(*mut Display, *mut libx11::XGenericEventCookie), 60 | ... 61 | ... 62 | pub xi_extension_opcode: Option, 63 | ); 64 | 65 | impl LibXi { 66 | pub unsafe fn query_xi_extension( 67 | &mut self, 68 | libx11: &mut libx11::LibX11, 69 | display: *mut Display, 70 | ) { 71 | let mut ev = 0; 72 | let mut err = 0; 73 | let mut xi_opcode = 0; 74 | 75 | if (libx11.XQueryExtension)( 76 | display, 77 | b"XInputExtension\x00" as *const u8 as *const libc::c_char, 78 | &mut xi_opcode, 79 | &mut ev, 80 | &mut err, 81 | ) == 0 82 | { 83 | return; 84 | } 85 | 86 | // check the version of XInput 87 | let mut major = 2; 88 | let mut minor = 3; 89 | if (self.XIQueryVersion)(display, &mut major, &mut minor) != 0 { 90 | return; 91 | } 92 | 93 | // select events to listen 94 | let mut mask = XI_RawMotionMask; 95 | let mut masks = XIEventMask { 96 | deviceid: XIAllDevices, 97 | mask_len: ::std::mem::size_of::() as _, 98 | mask: &mut mask as *mut _ as *mut _, 99 | }; 100 | 101 | (self.XISelectEvents)( 102 | display, 103 | // this weird pointers is macro expansion of DefaultRootWindow(display) 104 | (*(*(display as _XPrivDisplay)) 105 | .screens 106 | .offset((*(display as _XPrivDisplay)).default_screen as isize)) 107 | .root, 108 | &mut masks, 109 | 1 as libc::c_int, 110 | ); 111 | self.xi_extension_opcode = Some(xi_opcode); 112 | } 113 | 114 | /// Get mouse delta from XI_RawMotion's event XGenericEventCookie data 115 | pub unsafe fn read_cookie( 116 | &mut self, 117 | xcookie: &mut libx11::XGenericEventCookie, 118 | display: *mut Display, 119 | ) -> (f64, f64) { 120 | assert!(xcookie.evtype == xi_input::XI_RawMotion); 121 | 122 | (self.XGetEventData)(display, xcookie); 123 | 124 | let raw_event = xcookie.data as *mut xi_input::XIRawEvent; 125 | 126 | let dx = *(*raw_event).raw_values; 127 | let dy = *(*raw_event).raw_values.offset(1); 128 | 129 | (self.XFreeEventData)(display, &mut (*xcookie) as *mut _); 130 | 131 | (dx, dy) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/native/module.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, PartialEq, Eq, Clone)] 2 | pub enum Error { 3 | DlOpenError(String), 4 | DlSymError(String), 5 | } 6 | 7 | impl Display for Error { 8 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 9 | match self { 10 | Self::DlOpenError(msg) => write!(f, "Shared library open error:\n{msg}"), 11 | Self::DlSymError(msg) => write!(f, "Shared library symlink error:\n{msg}"), 12 | } 13 | } 14 | } 15 | 16 | #[cfg(any(target_os = "linux", target_os = "android"))] 17 | pub mod linux { 18 | use super::Error; 19 | use libc::{dlclose, dlopen, dlsym, RTLD_LAZY, RTLD_LOCAL}; 20 | use std::{ 21 | ffi::{c_void, CString}, 22 | ptr::NonNull, 23 | }; 24 | 25 | pub struct Module(NonNull); 26 | 27 | impl Module { 28 | pub fn load(path: &str) -> Result { 29 | let cpath = CString::new(path).unwrap(); 30 | let module = unsafe { dlopen(cpath.as_ptr(), RTLD_LAZY | RTLD_LOCAL) }; 31 | if module.is_null() { 32 | Err(Error::DlOpenError(path.to_string())) 33 | } else { 34 | Ok(Module(unsafe { NonNull::new_unchecked(module) })) 35 | } 36 | } 37 | 38 | pub fn get_symbol(&self, name: &str) -> Result { 39 | let cname = CString::new(name).unwrap(); 40 | let symbol = unsafe { dlsym(self.0.as_ptr(), cname.as_ptr()) }; 41 | if symbol.is_null() { 42 | return Err(Error::DlSymError(name.to_string())); 43 | } 44 | Ok(unsafe { std::mem::transmute_copy::<_, F>(&symbol) }) 45 | } 46 | } 47 | 48 | impl Drop for Module { 49 | fn drop(&mut self) { 50 | unsafe { dlclose(self.0.as_ptr()) }; 51 | } 52 | } 53 | } 54 | 55 | #[cfg(target_os = "windows")] 56 | mod windows { 57 | use super::Error; 58 | use winapi::{ 59 | shared::minwindef::HINSTANCE, 60 | um::libloaderapi::{FreeLibrary, GetProcAddress, LoadLibraryA}, 61 | }; 62 | 63 | pub struct Module(pub HINSTANCE); 64 | 65 | impl Module { 66 | pub fn load(path: &str) -> Result { 67 | let cpath = std::ffi::CString::new(path).unwrap(); 68 | let library = unsafe { LoadLibraryA(cpath.as_ptr()) }; 69 | if library.is_null() { 70 | return Err(Error::DlOpenError(path.to_string())); 71 | } 72 | Ok(Self(library)) 73 | } 74 | pub fn get_symbol(&self, name: &str) -> Result { 75 | let cname = std::ffi::CString::new(name).unwrap(); 76 | let proc = unsafe { GetProcAddress(self.0, cname.as_ptr() as *const _) }; 77 | if proc.is_null() { 78 | return Err(Error::DlSymError(name.to_string())); 79 | } 80 | Ok(unsafe { std::mem::transmute_copy(&proc) }) 81 | } 82 | } 83 | 84 | impl Drop for Module { 85 | fn drop(&mut self) { 86 | unsafe { FreeLibrary(self.0) }; 87 | } 88 | } 89 | } 90 | 91 | use std::fmt::Display; 92 | 93 | #[cfg(any(target_os = "linux", target_os = "android"))] 94 | pub use linux::*; 95 | 96 | #[cfg(target_os = "windows")] 97 | pub use windows::Module; 98 | 99 | #[cfg(any(target_os = "linux", target_os = "android"))] 100 | #[macro_export] 101 | macro_rules! declare_module { 102 | ($name:ident, 103 | $path:literal$(, $fallback:literal)*$(,)? 104 | ... 105 | // static 106 | $($s_vis:vis $s_name:ident: $s_type:ty,)* 107 | ... 108 | // function 109 | $($f_vis:vis fn $f_name:ident($($f_arg:ty),*$(,)?)$( -> $f_ret:ty )?,)* 110 | ... 111 | // function with variadic arguments 112 | $($v_vis:vis fn $v_name:ident($($v_arg:ty),*, ...)$( -> $v_ret:ty )?,)* 113 | ... 114 | // extra field (should be `Default` so `try_load` can construct it) 115 | $($vis:vis $field:ident: $field_ty:ty,)*) => { 116 | #[derive(Clone)] 117 | pub struct $name { 118 | _module: std::rc::Rc<$crate::native::module::Module>, 119 | $($s_vis $s_name: $s_type,)* 120 | $($f_vis $f_name: unsafe extern "C" fn ($($f_arg),*)$( -> $f_ret)?,)* 121 | $($v_vis $v_name: unsafe extern "C" fn ($($v_arg),*, ...)$( -> $v_ret)?,)* 122 | $($vis $field: $field_ty)* 123 | } 124 | impl $name { 125 | pub fn try_load() -> Result { 126 | $crate::native::module::Module::load($path)$(.or_else(|_| $crate::native::module::Module::load($fallback)))* 127 | .map(|module| 128 | $name { 129 | $($s_name: module.get_symbol::<$s_type>(stringify!($s_name)).unwrap(),)* 130 | $($f_name: module.get_symbol:: $f_ret)?>(stringify!($f_name)).unwrap(),)* 131 | $($v_name: module.get_symbol:: $v_ret)?>(stringify!($v_name)).unwrap(),)* 132 | $($field: Default::default(),)* 133 | _module: module.into(), 134 | } 135 | ) 136 | } 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/native/query_stab.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case, dead_code)] 2 | 3 | use crate::native::gl::{GLenum, GLint, GLuint, GLuint64}; 4 | 5 | pub const GL_TIME_ELAPSED: u32 = 35007; 6 | 7 | pub unsafe fn glGetQueryObjectui64v(_id: GLuint, _pname: GLenum, _params: *mut GLuint64) { 8 | unimplemented!(); 9 | } 10 | 11 | pub unsafe fn glGetQueryObjectiv(_id: GLuint, _pname: GLenum, _params: *mut GLint) { 12 | unimplemented!(); 13 | } 14 | -------------------------------------------------------------------------------- /src/native/wasm/fs.rs: -------------------------------------------------------------------------------- 1 | extern "C" { 2 | pub fn fs_load_file(ptr: *const i8, len: u32) -> u32; 3 | pub fn fs_get_buffer_size(file_id: u32) -> i32; 4 | pub fn fs_take_buffer(file_id: u32, ptr: *mut u8, max_size: u32); 5 | } 6 | -------------------------------------------------------------------------------- /src/native/wasm/keycodes.rs: -------------------------------------------------------------------------------- 1 | //! Translation of JS key/mouse codes to miniquads' 2 | //! TODO: JS can send better, more easy to use data and most of this file should 3 | //! go away. 4 | 5 | use crate::event::{KeyCode, KeyMods, MouseButton, TouchPhase}; 6 | 7 | pub fn translate_mouse_button(button: i32) -> MouseButton { 8 | match button { 9 | 0 => MouseButton::Left, 10 | 1 => MouseButton::Right, 11 | 2 => MouseButton::Middle, 12 | _ => MouseButton::Unknown, 13 | } 14 | } 15 | pub fn translate_mod(wasm_mods: i32) -> KeyMods { 16 | const SAPP_MODIFIER_SHIFT: i32 = 1; 17 | const SAPP_MODIFIER_CTRL: i32 = 2; 18 | const SAPP_MODIFIER_ALT: i32 = 4; 19 | const SAPP_MODIFIER_SUPER: i32 = 8; 20 | 21 | let mut mods = KeyMods::default(); 22 | if wasm_mods & SAPP_MODIFIER_SHIFT != 0 { 23 | mods.shift = true; 24 | } 25 | if wasm_mods & SAPP_MODIFIER_CTRL != 0 { 26 | mods.ctrl = true; 27 | } 28 | if wasm_mods & SAPP_MODIFIER_ALT != 0 { 29 | mods.alt = true; 30 | } 31 | if wasm_mods & SAPP_MODIFIER_SUPER != 0 { 32 | mods.logo = true; 33 | } 34 | mods 35 | } 36 | 37 | pub fn translate_keycode(keycode: i32) -> KeyCode { 38 | match keycode { 39 | 32 => KeyCode::Space, 40 | 39 => KeyCode::Apostrophe, 41 | 44 => KeyCode::Comma, 42 | 45 => KeyCode::Minus, 43 | 46 => KeyCode::Period, 44 | 47 => KeyCode::Slash, 45 | 48 => KeyCode::Key0, 46 | 49 => KeyCode::Key1, 47 | 50 => KeyCode::Key2, 48 | 51 => KeyCode::Key3, 49 | 52 => KeyCode::Key4, 50 | 53 => KeyCode::Key5, 51 | 54 => KeyCode::Key6, 52 | 55 => KeyCode::Key7, 53 | 56 => KeyCode::Key8, 54 | 57 => KeyCode::Key9, 55 | 59 => KeyCode::Semicolon, 56 | 61 => KeyCode::Equal, 57 | 65 => KeyCode::A, 58 | 66 => KeyCode::B, 59 | 67 => KeyCode::C, 60 | 68 => KeyCode::D, 61 | 69 => KeyCode::E, 62 | 70 => KeyCode::F, 63 | 71 => KeyCode::G, 64 | 72 => KeyCode::H, 65 | 73 => KeyCode::I, 66 | 74 => KeyCode::J, 67 | 75 => KeyCode::K, 68 | 76 => KeyCode::L, 69 | 77 => KeyCode::M, 70 | 78 => KeyCode::N, 71 | 79 => KeyCode::O, 72 | 80 => KeyCode::P, 73 | 81 => KeyCode::Q, 74 | 82 => KeyCode::R, 75 | 83 => KeyCode::S, 76 | 84 => KeyCode::T, 77 | 85 => KeyCode::U, 78 | 86 => KeyCode::V, 79 | 87 => KeyCode::W, 80 | 88 => KeyCode::X, 81 | 89 => KeyCode::Y, 82 | 90 => KeyCode::Z, 83 | 91 => KeyCode::LeftBracket, 84 | 92 => KeyCode::Backslash, 85 | 93 => KeyCode::RightBracket, 86 | 96 => KeyCode::Apostrophe, 87 | 256 => KeyCode::Escape, 88 | 257 => KeyCode::Enter, 89 | 258 => KeyCode::Tab, 90 | 259 => KeyCode::Backspace, 91 | 260 => KeyCode::Insert, 92 | 261 => KeyCode::Delete, 93 | 262 => KeyCode::Right, 94 | 263 => KeyCode::Left, 95 | 264 => KeyCode::Down, 96 | 265 => KeyCode::Up, 97 | 266 => KeyCode::PageUp, 98 | 267 => KeyCode::PageDown, 99 | 268 => KeyCode::Home, 100 | 269 => KeyCode::End, 101 | 280 => KeyCode::CapsLock, 102 | 281 => KeyCode::ScrollLock, 103 | 282 => KeyCode::NumLock, 104 | 283 => KeyCode::PrintScreen, 105 | 284 => KeyCode::Pause, 106 | 290 => KeyCode::F1, 107 | 291 => KeyCode::F2, 108 | 292 => KeyCode::F3, 109 | 293 => KeyCode::F4, 110 | 294 => KeyCode::F5, 111 | 295 => KeyCode::F6, 112 | 296 => KeyCode::F7, 113 | 297 => KeyCode::F8, 114 | 298 => KeyCode::F9, 115 | 299 => KeyCode::F10, 116 | 300 => KeyCode::F11, 117 | 301 => KeyCode::F12, 118 | 302 => KeyCode::F13, 119 | 303 => KeyCode::F14, 120 | 304 => KeyCode::F15, 121 | 305 => KeyCode::F16, 122 | 306 => KeyCode::F17, 123 | 307 => KeyCode::F18, 124 | 308 => KeyCode::F19, 125 | 309 => KeyCode::F20, 126 | 310 => KeyCode::F21, 127 | 311 => KeyCode::F22, 128 | 312 => KeyCode::F23, 129 | 313 => KeyCode::F24, 130 | 320 => KeyCode::Kp0, 131 | 321 => KeyCode::Kp1, 132 | 322 => KeyCode::Kp2, 133 | 323 => KeyCode::Kp3, 134 | 324 => KeyCode::Kp4, 135 | 325 => KeyCode::Kp5, 136 | 326 => KeyCode::Kp6, 137 | 327 => KeyCode::Kp7, 138 | 328 => KeyCode::Kp8, 139 | 329 => KeyCode::Kp9, 140 | 330 => KeyCode::KpDecimal, 141 | 331 => KeyCode::KpDivide, 142 | 332 => KeyCode::KpMultiply, 143 | 333 => KeyCode::KpSubtract, 144 | 334 => KeyCode::KpAdd, 145 | 335 => KeyCode::KpEnter, 146 | 336 => KeyCode::KpEqual, 147 | 340 => KeyCode::LeftShift, 148 | 341 => KeyCode::LeftControl, 149 | 342 => KeyCode::LeftAlt, 150 | 343 => KeyCode::LeftSuper, 151 | 344 => KeyCode::RightShift, 152 | 345 => KeyCode::RightControl, 153 | 346 => KeyCode::RightAlt, 154 | 347 => KeyCode::RightSuper, 155 | 348 => KeyCode::Menu, 156 | _ => KeyCode::Unknown, 157 | } 158 | } 159 | 160 | pub fn translate_touch_phase(phase: i32) -> TouchPhase { 161 | const TOUCHES_BEGAN: i32 = 10; 162 | const TOUCHES_MOVED: i32 = 11; 163 | const TOUCHES_ENDED: i32 = 12; 164 | const TOUCHES_CANCELLED: i32 = 13; 165 | 166 | match phase { 167 | TOUCHES_BEGAN => TouchPhase::Started, 168 | TOUCHES_MOVED => TouchPhase::Moved, 169 | TOUCHES_ENDED => TouchPhase::Ended, 170 | TOUCHES_CANCELLED => TouchPhase::Cancelled, 171 | _ => TouchPhase::Moved, 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/native/windows/clipboard.rs: -------------------------------------------------------------------------------- 1 | use winapi::um::winbase::{GlobalAlloc, GlobalLock, GlobalSize, GlobalUnlock, GMEM_MOVEABLE}; 2 | use winapi::um::winuser::CF_UNICODETEXT; 3 | use winapi::um::winuser::{ 4 | CloseClipboard, EmptyClipboard, GetClipboardData, OpenClipboard, SetClipboardData, 5 | }; 6 | 7 | use std::ptr; 8 | 9 | struct ClipboardGuard; 10 | impl ClipboardGuard { 11 | unsafe fn open() -> Option { 12 | let result = OpenClipboard(ptr::null_mut()); 13 | if result == false as _ { 14 | return None; 15 | } 16 | Some(ClipboardGuard) 17 | } 18 | } 19 | 20 | impl Drop for ClipboardGuard { 21 | fn drop(&mut self) { 22 | unsafe { 23 | CloseClipboard(); 24 | } 25 | } 26 | } 27 | 28 | unsafe fn get_raw_clipboard() -> Option> { 29 | // https://docs.microsoft.com/en-us/windows/win32/dataxchg/about-the-clipboard 30 | 31 | let guard = ClipboardGuard::open(); 32 | 33 | if guard.is_none() { 34 | eprintln!("Failed to open clipboard"); 35 | return None; 36 | } 37 | 38 | // Returns a handle to a clipboard object 39 | let clipboard_data = GetClipboardData(CF_UNICODETEXT); 40 | if clipboard_data.is_null() { 41 | return None; 42 | } 43 | 44 | let data_ptr = GlobalLock(clipboard_data) as *const u16; 45 | if data_ptr.is_null() { 46 | return None; 47 | } 48 | let data_size = GlobalSize(clipboard_data) as usize; 49 | 50 | let slice = std::slice::from_raw_parts(data_ptr, data_size); 51 | let len = slice.iter().position(|b| *b == 0).unwrap_or(data_size); 52 | 53 | // search for the first null byte to see where the string ends. 54 | 55 | let mut res = vec![0; len]; 56 | ptr::copy_nonoverlapping(data_ptr, res.as_mut_ptr(), len); 57 | 58 | Some(res) 59 | } 60 | 61 | unsafe fn set_raw_clipboard(data: *const u8, len: usize) { 62 | let guard = ClipboardGuard::open(); 63 | 64 | if guard.is_none() { 65 | eprintln!("Failed to open clipboard"); 66 | return; 67 | } 68 | 69 | let alloc_handle = GlobalAlloc(GMEM_MOVEABLE, len); 70 | 71 | if alloc_handle.is_null() { 72 | eprintln!("Failed to set clipboard: memory not allocated"); 73 | return; 74 | } 75 | 76 | let lock = GlobalLock(alloc_handle) as *mut u8; 77 | ptr::copy_nonoverlapping(data, lock, len); 78 | 79 | GlobalUnlock(lock as _); 80 | EmptyClipboard(); 81 | 82 | SetClipboardData(CF_UNICODETEXT, alloc_handle); 83 | } 84 | 85 | pub struct WindowsClipboard {} 86 | impl WindowsClipboard { 87 | pub fn new() -> WindowsClipboard { 88 | WindowsClipboard {} 89 | } 90 | } 91 | impl crate::native::Clipboard for WindowsClipboard { 92 | fn get(&mut self) -> Option { 93 | unsafe { get_raw_clipboard().map(|data| String::from_utf16_lossy(&data)) } 94 | } 95 | 96 | fn set(&mut self, data: &str) { 97 | unsafe { 98 | let text_w = format!("{}\0", data).encode_utf16().collect::>(); 99 | set_raw_clipboard(text_w.as_ptr() as _, text_w.len() * 2); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/native/windows/keycodes.rs: -------------------------------------------------------------------------------- 1 | use crate::event::KeyCode; 2 | 3 | pub fn translate_keycode(keycode: u32) -> KeyCode { 4 | // same as GLFW 5 | match keycode { 6 | 0x00B => KeyCode::Key0, 7 | 0x002 => KeyCode::Key1, 8 | 0x003 => KeyCode::Key2, 9 | 0x004 => KeyCode::Key3, 10 | 0x005 => KeyCode::Key4, 11 | 0x006 => KeyCode::Key5, 12 | 0x007 => KeyCode::Key6, 13 | 0x008 => KeyCode::Key7, 14 | 0x009 => KeyCode::Key8, 15 | 0x00A => KeyCode::Key9, 16 | 0x01E => KeyCode::A, 17 | 0x030 => KeyCode::B, 18 | 0x02E => KeyCode::C, 19 | 0x020 => KeyCode::D, 20 | 0x012 => KeyCode::E, 21 | 0x021 => KeyCode::F, 22 | 0x022 => KeyCode::G, 23 | 0x023 => KeyCode::H, 24 | 0x017 => KeyCode::I, 25 | 0x024 => KeyCode::J, 26 | 0x025 => KeyCode::K, 27 | 0x026 => KeyCode::L, 28 | 0x032 => KeyCode::M, 29 | 0x031 => KeyCode::N, 30 | 0x018 => KeyCode::O, 31 | 0x019 => KeyCode::P, 32 | 0x010 => KeyCode::Q, 33 | 0x013 => KeyCode::R, 34 | 0x01F => KeyCode::S, 35 | 0x014 => KeyCode::T, 36 | 0x016 => KeyCode::U, 37 | 0x02F => KeyCode::V, 38 | 0x011 => KeyCode::W, 39 | 0x02D => KeyCode::X, 40 | 0x015 => KeyCode::Y, 41 | 0x02C => KeyCode::Z, 42 | 0x028 => KeyCode::Apostrophe, 43 | 0x02B => KeyCode::Backslash, 44 | 0x033 => KeyCode::Comma, 45 | 0x00D => KeyCode::Equal, 46 | 0x029 => KeyCode::GraveAccent, 47 | 0x01A => KeyCode::LeftBracket, 48 | 0x00C => KeyCode::Minus, 49 | 0x034 => KeyCode::Period, 50 | 0x01B => KeyCode::RightBracket, 51 | 0x027 => KeyCode::Semicolon, 52 | 0x035 => KeyCode::Slash, 53 | 0x056 => KeyCode::World2, 54 | 0x00E => KeyCode::Backspace, 55 | 0x153 => KeyCode::Delete, 56 | 0x14F => KeyCode::End, 57 | 0x01C => KeyCode::Enter, 58 | 0x001 => KeyCode::Escape, 59 | 0x147 => KeyCode::Home, 60 | 0x152 => KeyCode::Insert, 61 | 0x15D => KeyCode::Menu, 62 | 0x151 => KeyCode::PageDown, 63 | 0x149 => KeyCode::PageUp, 64 | 0x045 => KeyCode::Pause, 65 | 0x146 => KeyCode::Pause, 66 | 0x039 => KeyCode::Space, 67 | 0x00F => KeyCode::Tab, 68 | 0x03A => KeyCode::CapsLock, 69 | 0x145 => KeyCode::NumLock, 70 | 0x046 => KeyCode::ScrollLock, 71 | 0x03B => KeyCode::F1, 72 | 0x03C => KeyCode::F2, 73 | 0x03D => KeyCode::F3, 74 | 0x03E => KeyCode::F4, 75 | 0x03F => KeyCode::F5, 76 | 0x040 => KeyCode::F6, 77 | 0x041 => KeyCode::F7, 78 | 0x042 => KeyCode::F8, 79 | 0x043 => KeyCode::F9, 80 | 0x044 => KeyCode::F10, 81 | 0x057 => KeyCode::F11, 82 | 0x058 => KeyCode::F12, 83 | 0x064 => KeyCode::F13, 84 | 0x065 => KeyCode::F14, 85 | 0x066 => KeyCode::F15, 86 | 0x067 => KeyCode::F16, 87 | 0x068 => KeyCode::F17, 88 | 0x069 => KeyCode::F18, 89 | 0x06A => KeyCode::F19, 90 | 0x06B => KeyCode::F20, 91 | 0x06C => KeyCode::F21, 92 | 0x06D => KeyCode::F22, 93 | 0x06E => KeyCode::F23, 94 | 0x076 => KeyCode::F24, 95 | 0x038 => KeyCode::LeftAlt, 96 | 0x01D => KeyCode::LeftControl, 97 | 0x02A => KeyCode::LeftShift, 98 | 0x15B => KeyCode::LeftSuper, 99 | 0x137 => KeyCode::PrintScreen, 100 | 0x138 => KeyCode::RightAlt, 101 | 0x11D => KeyCode::RightControl, 102 | 0x036 => KeyCode::RightShift, 103 | 0x15C => KeyCode::RightSuper, 104 | 0x150 => KeyCode::Down, 105 | 0x14B => KeyCode::Left, 106 | 0x14D => KeyCode::Right, 107 | 0x148 => KeyCode::Up, 108 | 0x052 => KeyCode::Kp0, 109 | 0x04F => KeyCode::Kp1, 110 | 0x050 => KeyCode::Kp2, 111 | 0x051 => KeyCode::Kp3, 112 | 0x04B => KeyCode::Kp4, 113 | 0x04C => KeyCode::Kp5, 114 | 0x04D => KeyCode::Kp6, 115 | 0x047 => KeyCode::Kp7, 116 | 0x048 => KeyCode::Kp8, 117 | 0x049 => KeyCode::Kp9, 118 | 0x04E => KeyCode::KpAdd, 119 | 0x053 => KeyCode::KpDecimal, 120 | 0x135 => KeyCode::KpDivide, 121 | 0x11C => KeyCode::KpEnter, 122 | 0x037 => KeyCode::KpMultiply, 123 | 0x04A => KeyCode::KpSubtract, 124 | _ => KeyCode::Unknown, 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/native/windows/libopengl32.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_camel_case_types, dead_code, non_snake_case)] 2 | 3 | use winapi::shared::{minwindef::*, ntdef::*, windef::*}; 4 | 5 | pub type wglCreateContext = extern "system" fn(_: HDC) -> HGLRC; 6 | pub type wglDeleteContext = extern "system" fn(_: HGLRC) -> bool; 7 | pub type wglGetProcAddress = extern "system" fn(_: LPCSTR) -> PROC; 8 | pub type wglGetCurrentDC = extern "system" fn() -> HDC; 9 | pub type wglMakeCurrent = extern "system" fn(_: HDC, _: HGLRC) -> bool; 10 | 11 | pub struct LibOpengl32 { 12 | pub module: crate::native::module::Module, 13 | pub wglCreateContext: wglCreateContext, 14 | pub wglDeleteContext: wglDeleteContext, 15 | pub wglGetProcAddress: wglGetProcAddress, 16 | pub wglGetCurrentDC: wglGetCurrentDC, 17 | pub wglMakeCurrent: wglMakeCurrent, 18 | } 19 | 20 | impl LibOpengl32 { 21 | pub fn try_load() -> Option { 22 | crate::native::module::Module::load("opengl32.dll") 23 | .map(|module| LibOpengl32 { 24 | wglCreateContext: module.get_symbol("wglCreateContext").unwrap(), 25 | wglDeleteContext: module.get_symbol("wglDeleteContext").unwrap(), 26 | wglGetProcAddress: module.get_symbol("wglGetProcAddress").unwrap(), 27 | wglGetCurrentDC: module.get_symbol("wglGetCurrentDC").unwrap(), 28 | wglMakeCurrent: module.get_symbol("wglMakeCurrent").unwrap(), 29 | module, 30 | }) 31 | .ok() 32 | } 33 | } 34 | --------------------------------------------------------------------------------