├── .editorconfig ├── .github ├── dependabot.yml ├── rustc-problem-matcher.json ├── test-problem-matcher.json └── workflows │ ├── ci.yml │ └── dependency-review.yml ├── .gitignore ├── .markdownlint.yaml ├── .prettierrc.toml ├── .run ├── Run AquariWM (Wayland).run.xml └── Run AquariWM (X11).run.xml ├── .rustfmt.toml ├── .trunk ├── .gitignore └── trunk.yaml ├── CONTRIBUTING.md ├── Cargo.toml ├── LICENSE ├── README.md ├── derive-extras ├── Cargo.toml └── src │ ├── lib.rs │ └── numbers_to_words.rs ├── publish-docs ├── rust-toolchain.toml └── src ├── cli.rs ├── display_server.rs ├── display_server ├── wayland.rs ├── wayland │ ├── grabs.rs │ ├── grabs │ │ ├── move_grab.rs │ │ └── resize_grab.rs │ ├── input.rs │ └── state.rs ├── x11.rs └── x11 │ ├── testing.rs │ └── util.rs ├── layout.rs ├── layout ├── implementations.rs ├── implementations │ ├── iter.rs │ └── node_changes.rs └── managers.rs ├── main.rs └── state.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | # What is this? EditorConfig is a file which many text editors natively support, and many others 6 | # have plugins available to achieve the same, which tells a text editor universal settings that it 7 | # should use when editing a particular project. This is useful so that a style guide can be 8 | # maintained throughout the project. 9 | 10 | # This declares that this is the .editorconfig file for the whole project, i.e. the root. 11 | root = true 12 | 13 | [*] 14 | indent_style = tab 15 | indent_size = 4 16 | end_of_line = lf 17 | charset = utf-8 18 | trim_trailing_whitespace = true 19 | insert_final_newline = true 20 | max_line_length = 120 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | # Please see the documentation for all Dependabot workflow configuration options: 6 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 7 | 8 | version: 2 9 | updates: 10 | 11 | # Maintain Cargo dependencies for security updates 12 | - package-ecosystem: "cargo" 13 | directory: "/" 14 | schedule: 15 | interval: "daily" 16 | 17 | # Maintain GitHub Actions dependencies for security updates 18 | - package-ecosystem: "github-actions" 19 | directory: "/" 20 | schedule: 21 | interval: "daily" 22 | -------------------------------------------------------------------------------- /.github/rustc-problem-matcher.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "rustc", 5 | "severity": "warning", 6 | "pattern": [ 7 | { 8 | "regexp": "^(warning|warn|error|note)(?:\\[(\\w+)\\])?:\\s+(.*)$", 9 | "severity": 1, 10 | "code": 2, 11 | "message": 3 12 | }, 13 | { 14 | "regexp": "^\\s+(?:-->\\s+((?:[\\w\\-.]+\\/)*)([\\w\\-.]+):(\\d+):(\\d+)$)?", 15 | "fromPath": 1, 16 | "file": 2, 17 | "line": 3, 18 | "column": 4 19 | } 20 | ] 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.github/test-problem-matcher.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "rust-tests", 5 | "severity": "error", 6 | "pattern": [ 7 | { 8 | "regexp": "^----[\\w\\s:]+----$" 9 | }, 10 | { 11 | "regexp": "^(.*)$", 12 | "message": 1 13 | }, 14 | { 15 | "regexp": "^.*$" 16 | }, 17 | { 18 | "regexp": "^(?:.*,\\s+((?:[\\w\\-.]+\\/)*)([\\w\\-.]+):(\\d+):(\\d+)$)?", 19 | "fromPath": 1, 20 | "file": 2, 21 | "line": 3, 22 | "column": 4 23 | } 24 | ] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | # Runs checks, tests, code analysis, auto-formats code, applies recommended 6 | # fixes, and publishes documentation. 7 | name: Continuous integration 8 | 9 | on: [ push, pull_request ] 10 | 11 | permissions: 12 | contents: write 13 | pages: write 14 | id-token: write 15 | 16 | jobs: 17 | cancel-runs: 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | # If this workflow is already in progress or queued, we cancel it; we are 22 | # about to do the exact same tests and documentation on potentially new 23 | # code, so it is pointless to continue them. 24 | - name: Cancel existing workflow runs 25 | uses: styfle/cancel-workflow-action@0.12.0 26 | with: 27 | access_token: ${{ github.token }} 28 | 29 | # Automatically applies suggested fixes from `clippy` and formats with rustfmt. 30 | fix-n-format: 31 | runs-on: ubuntu-latest 32 | continue-on-error: true 33 | 34 | outputs: 35 | # The ID of the commit made for the fixes, or, if no fixes were applied, 36 | # the commit that triggered the workflow. 37 | commit-id: ${{ steps.commit-id.outputs.COMMIT_ID }} 38 | 39 | steps: 40 | # Check out (a.k.a. clones) the AquariWM repository. 41 | - name: Checkout AquariWM 42 | uses: actions/checkout@v4 43 | 44 | # Install `libsystemd-dev`, `libudev-dev`, `libseat-dev`, `libinput-dev`, and 45 | # `libxkbcommon-dev` to satisfy dependencies. 46 | - name: Install system libraries required to build AquariWM 47 | run: | 48 | sudo apt-get update 49 | sudo apt-get install libsystemd-dev libudev-dev libseat-dev libinput-dev libxkbcommon-dev 50 | 51 | # Install the latest nightly release of the Rust toolchain. 52 | - name: Install latest nightly 53 | uses: actions-rs/toolchain@v1 54 | with: 55 | profile: minimal 56 | toolchain: nightly 57 | override: true 58 | components: clippy, rustfmt 59 | 60 | - name: Configure git credentials 61 | run: | 62 | git config user.name github-actions 63 | git config user.email github-actions@github.com 64 | 65 | # Apply fixes with `clippy`. 66 | - name: Apply recommended fixes 67 | uses: actions-rs/cargo@v1 68 | with: 69 | command: clippy 70 | args: --workspace --fix --color always 71 | 72 | - name: Commit clippy fix changes 73 | # Commit changes, or, if that fails, do nothing. 74 | run: | 75 | git pull origin ${{ github.ref }} 76 | git diff --quiet || echo "### Applied recommended clippy fixes." >> $GITHUB_STEP_SUMMARY 77 | git diff --quiet || (git commit -am "[CI${{ github.run_number}}] applied recommended clippy fixes" && echo "$(git log --format='%H' -n 1)" >> $GITHUB_STEP_SUMMARY && echo "$(git log --format='%H' -n 1)" >> .git-blame-ignore-revs) 78 | 79 | # Automatically format the code with `rustfmt`. 80 | - name: Format code with `rustfmt` 81 | uses: actions-rs/cargo@v1 82 | with: 83 | command: fmt 84 | args: --all 85 | 86 | - name: Commit rustfmt changes 87 | run: | 88 | git pull origin ${{ github.ref }} 89 | git diff --quiet || echo "### Formatted code with rustfmt." >> $GITHUB_STEP_SUMMARY 90 | git diff --quiet || (git commit -am "[CI${{ github.run_number}}] formatted code with rustfmt" && echo "$(git log --format='%H' -n 1)" >> $GITHUB_STEP_SUMMARY && echo "$(git log --format='%H' -n 1)" >> .git-blame-ignore-revs) 91 | 92 | # This sets the `commit-id` to the latest commit. If there were changes 93 | # made, that means it will be the commit for those changes, otherwise it 94 | # will be the commit that triggered the workflow. 95 | - name: Set the `commit-id` output 96 | id: commit-id 97 | run: echo "COMMIT_ID=$(git log --format='%H' -n 1)" >> $GITHUB_OUTPUT 98 | 99 | - name: Push changes 100 | run: | 101 | git push origin HEAD:${{ github.ref }} || : 102 | 103 | # Runs unit tests. 104 | run-tests: 105 | needs: fix-n-format 106 | if: success() || failure() 107 | runs-on: ubuntu-latest 108 | 109 | steps: 110 | # Check out (a.k.a. clones) the AquariWM repository. 111 | - name: Checkout AquariWM 112 | uses: actions/checkout@v4 113 | with: 114 | ref: ${{ needs.fix-n-format.outputs.commit-id }} 115 | 116 | # Install `libsystemd-dev`, `libudev-dev`, `libseat-dev`, `libinput-dev`, and 117 | # `libxkbcommon-dev` to satisfy dependencies. 118 | - name: Install system libraries required to build AquariWM 119 | run: | 120 | sudo apt-get update 121 | sudo apt-get install libsystemd-dev libudev-dev libseat-dev libinput-dev libxkbcommon-dev 122 | 123 | # Install the latest nightly release of the Rust toolchain. 124 | - name: Install latest nightly 125 | uses: actions-rs/toolchain@v1 126 | with: 127 | profile: minimal 128 | toolchain: nightly 129 | override: true 130 | 131 | # Matches test failures so annotations can be added and such. 132 | - name: Add test problem matching 133 | run: echo "::add-matcher::.github/test-problem-matcher.json" 134 | 135 | # Run unit tests with `cargo test`. 136 | - name: Run tests 137 | run: cargo test --workspace --color never 138 | 139 | - name: Remove test problem matching 140 | if: ${{ success() || failure() }} 141 | run: echo "::remove-matcher owner=rust-tests::" 142 | 143 | # Analyses the code with `clippy`. 144 | clippy-analysis: 145 | runs-on: ubuntu-latest 146 | # We run clippy analysis after any fixes that can be applied have been. 147 | needs: fix-n-format 148 | if: success() || failure() 149 | 150 | steps: 151 | # Check out (a.k.a. clones) the AquariWM repository with fixes made by `clippy` 152 | # in `clippy-fixes`, if any. 153 | - name: Checkout AquariWM 154 | uses: actions/checkout@v4 155 | with: 156 | ref: ${{ needs.fix-n-format.outputs.commit-id }} 157 | 158 | # Install `libsystemd-dev`, `libudev-dev`, `libseat-dev`, `libinput-dev`, and 159 | # `libxkbcommon-dev` to satisfy dependencies. 160 | - name: Install system libraries required to build AquariWM 161 | run: | 162 | sudo apt-get update 163 | sudo apt-get install libsystemd-dev libudev-dev libseat-dev libinput-dev libxkbcommon-dev 164 | 165 | # Install the latest nightly release of the Rust toolchain. 166 | - name: Install latest nightly 167 | uses: actions-rs/toolchain@v1 168 | with: 169 | profile: minimal 170 | toolchain: nightly 171 | override: true 172 | components: clippy 173 | 174 | # Matches errors, warnings, etc. so annotations can be added and such. 175 | - name: Add Rust problem matching 176 | run: echo "::add-matcher::.github/rustc-problem-matcher.json" 177 | 178 | # Analyse the code with `clippy`. 179 | - name: Clippy analysis 180 | run: cargo clippy --workspace --color never 181 | 182 | - name: Remove Rust problem matching 183 | if: ${{ success() || failure() }} 184 | run: echo "::remove-matcher owner=rustc::" 185 | 186 | # Generate the docs with rustdoc. 187 | build-docs: 188 | runs-on: ubuntu-latest 189 | # We only build the documentation after the code has been changed so that 190 | # the code sources linked in the documentation are up-to-date. 191 | needs: fix-n-format 192 | if: success() || failure() 193 | 194 | steps: 195 | # Check out (a.k.a. clones) the AquariWM repository with fixes made by `clippy` in 196 | # `clippy-fixes`, if any, and formatting made by `rustfmt` in `auto-format`, if any. 197 | - name: Checkout AquariWM 198 | uses: actions/checkout@v4 199 | with: 200 | ref: ${{ needs.fix-n-format.outputs.commit-id }} 201 | path: aquariwm 202 | 203 | # Install `libsystemd-dev`, `libudev-dev`, `libseat-dev`, `libinput-dev`, and 204 | # `libxkbcommon-dev` to satisfy dependencies. 205 | - name: Install system libraries required to build AquariWM 206 | run: | 207 | sudo apt-get update 208 | sudo apt-get install libsystemd-dev libudev-dev libseat-dev libinput-dev libxkbcommon-dev 209 | 210 | # Check out a template to put the generated docs in. 211 | - name: Checkout AquariWM docs template 212 | uses: actions/checkout@v4 213 | with: 214 | repository: AquariWM/aquariwm-docs-template 215 | path: template 216 | 217 | # Install the Rust toolchain so that docs can be generated. 218 | - name: Install latest nightly 219 | uses: actions-rs/toolchain@v1 220 | with: 221 | profile: minimal 222 | toolchain: nightly 223 | override: true 224 | 225 | # Matches errors, warnings, etc. so annotations can be added and such. 226 | - name: Add Rust problem matching 227 | run: echo "::add-matcher::aquariwm/.github/rustc-problem-matcher.json" 228 | 229 | # Setup GitHub Pages to easily deploy to it. 230 | - name: Setup GitHub Pages 231 | uses: actions/configure-pages@v4 232 | 233 | # Build documentation with `rustdoc`. 234 | - name: Build documentation 235 | working-directory: aquariwm 236 | run: cargo doc --no-deps --workspace --color never 237 | 238 | - name: Remove Rust problem matching 239 | if: ${{ success() || failure() }} 240 | run: echo "::remove-matcher owner=rustc::" 241 | 242 | # Place the built documentation into the template, ready to be deployed. 243 | - name: Move generated docs into docs template 244 | run: mv aquariwm/target/doc template/doc 245 | 246 | # Upload the template, now containing the built docs, as an artifact that 247 | # can be accessed by the `deploy-docs` job. 248 | - name: Upload GitHub Pages artifact 249 | uses: actions/upload-pages-artifact@v2 250 | with: 251 | path: template 252 | 253 | # Deploy the documentation with GitHub Pages. 254 | deploy-docs: 255 | if: github.event_name == 'push' && github.ref_type == 'branch' && github.ref_name == 'main' 256 | environment: 257 | name: github-pages 258 | url: ${{ steps.deployment.outputs.page_url }} 259 | 260 | runs-on: ubuntu-latest 261 | # Can't deploy the documentation until it exists! 262 | needs: build-docs 263 | 264 | steps: 265 | # Deploys the documentation to GitHub Pages using the artifact (stored 266 | # but not committed changes for Actions) saved earlier. 267 | - name: Deploy documentation to GitHub Pages 268 | uses: actions/deploy-pages@v3 269 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | # Dependency Review Action 2 | # 3 | # This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. 4 | # 5 | # Source repository: https://github.com/actions/dependency-review-action 6 | # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement 7 | name: 'Dependency Review' 8 | on: [pull_request] 9 | 10 | permissions: 11 | contents: read 12 | 13 | jobs: 14 | dependency-review: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: 'Checkout Repository' 18 | uses: actions/checkout@v4 19 | - name: 'Dependency Review' 20 | uses: actions/dependency-review-action@v3 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | # Generated by Cargo 6 | # will have compiled files and executables 7 | /target/ 8 | 9 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 10 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 11 | Cargo.lock 12 | 13 | # These are backup files generated by rustfmt 14 | **/*.rs.bk 15 | 16 | # Added by cargo 17 | /target 18 | 19 | .idea/ 20 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | # Autoformatter friendly markdownlint config (all formatting rules disabled) 6 | default: true 7 | blank_lines: false 8 | bullet: false 9 | html: false 10 | indentation: false 11 | line_length: false 12 | spaces: false 13 | url: false 14 | whitespace: false 15 | -------------------------------------------------------------------------------- /.prettierrc.toml: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | useTabs = true 6 | -------------------------------------------------------------------------------- /.run/Run AquariWM (Wayland).run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 26 | 27 | -------------------------------------------------------------------------------- /.run/Run AquariWM (X11).run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 26 | 27 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | hard_tabs = true 6 | combine_control_expr = false 7 | wrap_comments = true 8 | comment_width = 100 9 | max_width = 120 10 | condense_wildcard_suffixes = true 11 | format_strings = true 12 | 13 | imports_layout = "HorizontalVertical" 14 | imports_granularity = "Crate" 15 | merge_imports = true 16 | group_imports = "StdExternalCrate" 17 | 18 | match_block_trailing_comma = true 19 | normalize_comments = true 20 | normalize_doc_attributes = true 21 | unstable_features = true 22 | use_field_init_shorthand = true 23 | use_try_shorthand = true 24 | -------------------------------------------------------------------------------- /.trunk/.gitignore: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | *out 6 | *logs 7 | external 8 | -------------------------------------------------------------------------------- /.trunk/trunk.yaml: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | version: 0.1 6 | cli: 7 | version: 0.15.0-beta 8 | lint: 9 | enabled: 10 | - actionlint@1.6.15 11 | - clippy@1.58.1 12 | - gitleaks@8.8.11 13 | - markdownlint@0.31.1 14 | - prettier@2.7.1 15 | - rustfmt@1.58.1 16 | - taplo@release-taplo-cli-0.6.8 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # Please note... 6 | This document is only temporary, and should be replaced by a contributors' wiki. To summarise code 7 | style requirements (until the wiki contains such information): 8 | - AquariWM uses _hard tab characters_ throughout the project for indentation, rather than spaces. 9 | More information about this can be found in the pinned discussion in the Discussions tab of the 10 | GitHub repository (AquariWM/aquariwm). `EditorConfig` and `rustfmt` are configured to support 11 | this already, and we recommend that you install a plugin for your editor to recognise 12 | `.editorconfig` files automatically, as well as to format your Rust code automatically with 13 | `cargo fmt`. 14 | - The code style guidelines of the language you are using should be used _unless_ we specify 15 | otherwise; for instance, as we specify that hard tab characters shall be used, that takes 16 | preference over Rust's official code style guidelines. 17 | 18 | You can find code style guidelines for Rust 19 | [here](https://doc.rust-lang.org/1.0.0/style/README.html). For license header information, please 20 | read ahead. 21 | 22 | ## License headers 23 | AquariWM's core is licensed under the Mozilla Public License v2.0 (MPL-2.0) license. As stated by 24 | Mozilla: 25 | 26 | > This license must be used for all new code, unless the containing project, module or 27 | externally-imported codebase uses a different license. If you can't put a header in the file due to 28 | its structure, please put it in a LICENSE file in the same directory. 29 | 30 | All new code contributed to AquariWM's core is required to include the following license headers at 31 | the beginning of all relevant files. If the file format in question does not support comments, 32 | please include a `LICENSE` file in the same directory with the plain-text version of this header. 33 | 34 | ### Rust, and others with C-like comments 35 | ```rust 36 | // This Source Code Form is subject to the terms of the Mozilla Public 37 | // License, v. 2.0. If a copy of the MPL was not distributed with this 38 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 39 | ``` 40 | 41 | ### Shell scripts, and others 42 | ```bash 43 | # This Source Code Form is subject to the terms of the Mozilla Public 44 | # License, v. 2.0. If a copy of the MPL was not distributed with this 45 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 46 | ``` 47 | 48 | ### Markdown, HTML, and other markup languages 49 | ```markdown 50 | 53 | ``` 54 | 55 | ### `LICENSE` files, and others 56 | ``` 57 | This Source Code Form is subject to the terms of the Mozilla Public 58 | License, v. 2.0. If a copy of the MPL was not distributed with this 59 | file, You can obtain one at https://mozilla.org/MPL/2.0/. -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | [package] 6 | name = "aquariwm" 7 | version = "0.1.0" 8 | authors = ["AquariWM", "Antikyth"] 9 | edition = "2021" 10 | readme = true 11 | repository = "https://github.com/AquariWM/aquariwm" 12 | license = "MPL-2.0" 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [workspace] 17 | members = ["derive-extras"] 18 | 19 | [features] 20 | # TODO: When compiling for release, maybe don't include the testing feature? 21 | default = ["wayland", "x11", "testing"] 22 | 23 | wayland = ["dep:smithay"] 24 | x11 = ["dep:x11rb-async", "async", "winit?/x11"] 25 | 26 | # NOTE: winit is provided even if x11 is not enabled, because there is no way to specify that two 27 | # features must be enabled for an optional dependency. 28 | testing = ["smithay?/backend_winit", "dep:winit"] 29 | # Features required for async AquariWM implementations (i.e. our X11 implementation). 30 | async = ["dep:futures", "dep:tokio"] 31 | 32 | [dependencies] 33 | bitflags = "2.2.1" 34 | thiserror = "1.0.50" 35 | truncate-integer = "0.5.0" 36 | derive-extras = { path = "./derive-extras" } 37 | 38 | # CLI 39 | clap = { version = "4.4.7", features = ["derive"] } 40 | clap_complete = "4.4.4" 41 | 42 | # Logging 43 | tracing = "0.1.40" 44 | tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } 45 | 46 | #################################################################################################### 47 | # Display server specific 48 | #################################################################################################### 49 | # X11 50 | [dependencies.x11rb-async] 51 | version = "0.13.0" 52 | optional = true 53 | 54 | # Wayland 55 | [dependencies.smithay] 56 | git = "https://github.com/Smithay/smithay" 57 | features = ["wayland_frontend", "desktop"] 58 | optional = true 59 | 60 | #################################################################################################### 61 | # Async 62 | #################################################################################################### 63 | 64 | 65 | 66 | [dependencies.futures] 67 | version = "0.3.29" 68 | optional = true 69 | 70 | [dependencies.tokio] 71 | version = "1.33.0" 72 | features = ["full"] 73 | optional = true 74 | 75 | #################################################################################################### 76 | # Testing 77 | #################################################################################################### 78 | 79 | [dependencies.winit] 80 | version = "0.29.3" 81 | default-features = false 82 | optional = true 83 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | # AquariWM 6 | 7 | AquariWM will be an X11 window manager and Wayland compositor focused on a highly modular design. It will also focus on 8 | high-quality documentation and being accessible and welcoming to all. 9 | 10 | ## Modularity 11 | 12 | Tiling window layout managers will be able to be provided by external applications ('AquariWM clients'). AquariWM 13 | clients will also be able to provide decoration managers, providing the window decorations for windows. 14 | 15 | Other areas of modularity will exist too but ideas for them will become clear as AquariWM is developed - areas could 16 | include input methods, config/settings managers, or adding modularity that X11 already has to the AquariWM Wayland 17 | compositor (window managers and compositors being provided by external clients being one example). 18 | 19 | ## Current state 20 | 21 | At the time of writing (December 2023), AquariWM development is in early stages, though the layout manager system is 22 | implemented in Rust (with the goal of transitioning to use a custom protocol in the future, the specifics of which are 23 | yet to be decided). @Antikyth, the only author of AquariWM at the time of writing, is working on 24 | [`generational-arena-tree`], a tree implementation in Rust that gives the flexibility to implement more complex features 25 | for tiling layouts (e.g. taking windows' minimum and maximum sizes into account). Specifically, it allows: 26 | - nodes to be mutated directly, 27 | - nodes to be iterated over mutably, 28 | - nodes to be split by type into separate branches (nodes that may have children) and leaves (nodes that may not have 29 | children), which each have their own associated data type. 30 | - This is required because, in window layouts, every branch has an orientation, and every leaf has a window. No branch 31 | may have a window, and no leaf may have an orientation. 32 | 33 | Here is a screenshot of a working Main + Stack layout manager implemented in the current state of AquariWM: 34 | ![A picture of a Main + Stack layout manager functioning in AquariWM, with window gaps enabled](https://cdn.discordapp.com/attachments/1012049086121246843/1176465058449076294/image.png?ex=657831f7&is=6565bcf7&hm=be348cc7313d69a9da3f1b5bb39dde9ef2261a679034438aa45eefc5d423b0c4&) 35 | 36 | [`generational-arena-tree`]: https://github.com/AquariWM/generational-arena-tree 37 | -------------------------------------------------------------------------------- /derive-extras/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "derive-extras" 3 | authors = ["Antikyth"] 4 | version = "0.1.0" 5 | license = "MPL-2.0" 6 | description = "Adds a number of useful extra `#[derive(...)]` macros." 7 | edition = "2021" 8 | 9 | [lib] 10 | proc-macro = true 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [dependencies] 15 | quote = "1.0.33" 16 | syn = "2.0.39" 17 | proc-macro2 = "1.0.69" 18 | -------------------------------------------------------------------------------- /derive-extras/src/lib.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use proc_macro::TokenStream; 6 | use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; 7 | use quote::{quote, ToTokens}; 8 | use syn::{ 9 | parse_macro_input, 10 | parse_quote, 11 | spanned::Spanned, 12 | Attribute, 13 | Data, 14 | DataEnum, 15 | DataStruct, 16 | DeriveInput, 17 | Error, 18 | Fields, 19 | GenericParam, 20 | Index, 21 | Meta, 22 | Type, 23 | Variant, 24 | }; 25 | 26 | use crate::numbers_to_words::encode_ordinal; 27 | 28 | mod numbers_to_words; 29 | 30 | /// Derives [`Default`] with more flexibility than Rust's built-in `#[derive(Default)]`. 31 | /// 32 | /// In addition to the functionality allowed by the built-in `#[derive(Default)]`, this version 33 | /// allows: 34 | /// - overriding the default values for particular fields using `#[default = ...]` or 35 | /// `#[default(...)]`; and 36 | /// - using `#[default]` on a non-unit enum variant 37 | /// 38 | /// # Examples 39 | /// 40 | /// ## Structs 41 | /// ``` 42 | /// # #[allow(unused_qualifications)] 43 | /// #[derive(derive_extras::Default)] 44 | /// struct ExampleStruct { 45 | /// x: i32, 46 | /// #[default = 15] 47 | /// y: i32, 48 | /// z: i32, 49 | /// } 50 | /// ``` 51 | /// For this struct, the following [`Default`] implementation is derived: 52 | /// ``` 53 | /// # struct ExampleStruct { 54 | /// # x: i32, 55 | /// # y: i32, 56 | /// # z: i32, 57 | /// # } 58 | /// # 59 | /// impl Default for ExampleStruct { 60 | /// fn default() -> Self { 61 | /// Self { 62 | /// x: Default::default(), 63 | /// y: 15, 64 | /// z: Default::default(), 65 | /// } 66 | /// } 67 | /// } 68 | /// ``` 69 | /// 70 | /// ## Enums 71 | /// ``` 72 | /// # #[allow(unused_qualifications)] 73 | /// #[derive(derive_extras::Default)] 74 | /// enum ExampleEnum { 75 | /// Unit, 76 | /// Tuple(i32, i32, i32), 77 | /// 78 | /// #[default] 79 | /// Struct { 80 | /// x: i32, 81 | /// #[default = 15] 82 | /// y: i32, 83 | /// z: i32, 84 | /// }, 85 | /// } 86 | /// ``` 87 | /// For this enum, the following [`Default`] implementation is derived: 88 | /// ``` 89 | /// # enum ExampleEnum { 90 | /// # Unit, 91 | /// # Tuple(i32, i32, i32), 92 | /// # Struct { 93 | /// # x: i32, 94 | /// # y: i32, 95 | /// # z: i32, 96 | /// # }, 97 | /// # } 98 | /// # 99 | /// impl Default for ExampleEnum { 100 | /// fn default() -> Self { 101 | /// Self::Struct { 102 | /// x: Default::default(), 103 | /// y: 15, 104 | /// z: Default::default(), 105 | /// } 106 | /// } 107 | /// } 108 | /// ``` 109 | /// 110 | /// [`Default`]: core::default::Default 111 | #[proc_macro_derive(Default, attributes(default))] 112 | pub fn default(input: TokenStream) -> TokenStream { 113 | let input = parse_macro_input!(input as DeriveInput); 114 | 115 | let DeriveInput { 116 | mut generics, 117 | ident, 118 | data, 119 | .. 120 | } = input; 121 | 122 | for param in &mut generics.params { 123 | if let GenericParam::Type(param) = param { 124 | param.bounds.push(parse_quote!(::core::default::Default)) 125 | } 126 | } 127 | 128 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 129 | 130 | let body = match data { 131 | Data::Struct(DataStruct { fields, .. }) => { 132 | let init = expand_default_for_fields(fields); 133 | 134 | quote!(Self #init) 135 | }, 136 | 137 | Data::Enum(r#enum) => expand_default_for_enum(r#enum).unwrap_or_else(|error| error.into_compile_error()), 138 | 139 | Data::Union(_) => unimplemented!("unions are not supported"), 140 | }; 141 | 142 | let tokens = quote! { 143 | impl #impl_generics ::core::default::Default for #ident #ty_generics #where_clause { 144 | fn default() -> Self { 145 | #body 146 | } 147 | } 148 | }; 149 | 150 | tokens.into() 151 | } 152 | 153 | fn expand_default_for_enum(r#enum: DataEnum) -> syn::Result { 154 | let variant = { 155 | let mut variant = None; 156 | 157 | for var in r#enum.variants { 158 | for attr in &var.attrs { 159 | if attr.meta.require_path_only().map(|path| path.is_ident("default"))? { 160 | match &variant { 161 | None => variant = Some(var), 162 | Some(_) => return Err(Error::new(attr.span(), "conflicting #[default] attribute")), 163 | } 164 | 165 | break; 166 | } 167 | } 168 | } 169 | 170 | variant 171 | }; 172 | 173 | match variant { 174 | Some(Variant { ident, fields, .. }) => { 175 | let init = expand_default_for_fields(fields); 176 | 177 | Ok(quote!(Self::#ident #init)) 178 | }, 179 | 180 | None => Err(Error::new( 181 | Span::call_site(), 182 | "one enum variant must have a #[default] attribute", 183 | )), 184 | } 185 | } 186 | 187 | fn expand_default_for_fields(fields: Fields) -> Option { 188 | match fields { 189 | Fields::Unit => None, 190 | 191 | Fields::Named(fields) => { 192 | let field_init = fields.named.into_pairs().map(|pair| { 193 | let (field, comma) = pair.into_tuple(); 194 | 195 | let (ident, ty, attrs) = (field.ident, field.ty, field.attrs); 196 | 197 | let value = field_value(ty, attrs); 198 | 199 | quote!(#ident: #value #comma) 200 | }); 201 | 202 | Some(quote!({ #(#field_init)* })) 203 | }, 204 | 205 | Fields::Unnamed(fields) => { 206 | let field_init = fields.unnamed.into_pairs().map(|pair| { 207 | let (field, comma) = pair.into_tuple(); 208 | 209 | let value = field_value(field.ty, field.attrs); 210 | 211 | quote!(#value #comma) 212 | }); 213 | 214 | Some(quote!((#(#field_init)*))) 215 | }, 216 | } 217 | } 218 | 219 | fn field_value(ty: Type, attrs: Vec) -> TokenStream2 { 220 | let default_attr = attrs 221 | .into_iter() 222 | .find(|attribute| attribute.meta.path().is_ident("default")); 223 | 224 | match default_attr { 225 | // If there is a default attribute, use its value. 226 | Some(default_attr) => match default_attr.meta { 227 | Meta::Path(path) => Error::new( 228 | path.span(), 229 | format!( 230 | "expected a value for this attribute: `{}(...)` or `{} = ...`", 231 | "default", "default", 232 | ), 233 | ) 234 | .into_compile_error(), 235 | 236 | Meta::List(meta) => { 237 | let tokens = meta.tokens; 238 | 239 | quote!({ #tokens }) 240 | }, 241 | 242 | Meta::NameValue(meta) => meta.value.into_token_stream(), 243 | }, 244 | 245 | // If there is no default attribute, use `Default::default()`. 246 | None => { 247 | quote!(<#ty as ::core::default::Default>::default()) 248 | }, 249 | } 250 | } 251 | 252 | /// Generates appropriate builder methods for a struct. 253 | /// 254 | /// This assumes that the struct itself acts like a builder. 255 | /// 256 | /// This derive macro also adds a `#[new]` helper attribute. If this is added to the struct, a `new` 257 | /// function is also generated with a `where Self: Default` bound. 258 | /// 259 | /// The builder methods generated for each field will have that field's visibility: private fields 260 | /// will have private methods, etc. 261 | /// 262 | /// # Examples 263 | /// ``` 264 | /// # use derive_extras::builder; 265 | /// # 266 | /// #[derive(Default, builder)] 267 | /// #[new] 268 | /// struct Example { 269 | /// pub x: i32, 270 | /// pub y: i32, 271 | /// } 272 | /// ``` 273 | /// This will derive the following implementations: 274 | /// ``` 275 | /// # #[derive(Default)] 276 | /// # struct Example { 277 | /// # pub x: i32, 278 | /// # pub y: i32, 279 | /// # } 280 | /// # 281 | /// impl Example { 282 | /// /// Creates a new `Example`. 283 | /// /// 284 | /// /// This is equivalent to Example::[default()]. 285 | /// /// 286 | /// /// [default()]: Default::default() 287 | /// pub fn new() -> Self 288 | /// where 289 | /// Self: Default, 290 | /// { 291 | /// Self::default() 292 | /// } 293 | /// 294 | /// /// Sets `x` to the given value. 295 | /// pub fn x(mut self, x: i32) -> Self { 296 | /// self.x = x; 297 | /// 298 | /// self 299 | /// } 300 | /// 301 | /// /// Sets `y` to the given value. 302 | /// pub fn y(mut self, y: i32) -> Self { 303 | /// self.y = y; 304 | /// 305 | /// self 306 | /// } 307 | /// } 308 | /// ``` 309 | /// 310 | /// 311 | /// `#[derive(builder)]` also works on tuple structs (with any number of fields): 312 | /// ``` 313 | /// # use derive_extras::builder; 314 | /// # 315 | /// #[derive(Default, builder)] 316 | /// struct Example(pub i32, pub i32); 317 | /// ``` 318 | /// This will derive the following implementations: 319 | /// ``` 320 | /// # #[derive(Default)] 321 | /// # struct Example(pub i32, pub i32); 322 | /// # 323 | /// impl Example { 324 | /// /// Sets the first field to the given value. 325 | /// pub fn first(mut self, first: i32) -> Self { 326 | /// self.0 = first; 327 | /// 328 | /// self 329 | /// } 330 | /// 331 | /// /// Sets the second field to the given value. 332 | /// pub fn second(mut self, second: i32) -> Self { 333 | /// self.1 = second; 334 | /// 335 | /// self 336 | /// } 337 | /// } 338 | /// ``` 339 | #[proc_macro_derive(builder, attributes(new))] 340 | pub fn builder(input: TokenStream) -> TokenStream { 341 | let input = parse_macro_input!(input as DeriveInput); 342 | 343 | let tokens = match input.data { 344 | Data::Struct(r#struct) => { 345 | let name = input.ident; 346 | 347 | let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); 348 | 349 | let new = input.attrs.iter().any(|attr| attr.path().is_ident("new")).then(|| { 350 | quote! { 351 | #[doc = concat!("Creates a new `", stringify!(#name), "`.")] 352 | #[doc = ""] 353 | #[doc = concat!("This is equivalent to ", stringify!(#name), "::[default()].")] 354 | #[doc = ""] 355 | #[doc = "[default()]: ::core::default::Default::default()"] 356 | pub fn new() -> Self 357 | where 358 | Self: ::core::default::Default, 359 | { 360 | ::default() 361 | } 362 | } 363 | }); 364 | 365 | let config_methods = match &r#struct.fields { 366 | Fields::Unit => None, 367 | 368 | // Named fields. 369 | Fields::Named(fields) => { 370 | let methods: TokenStream2 = fields 371 | .named 372 | .iter() 373 | .map(|field| { 374 | let vis = &field.vis; 375 | let ty = &field.ty; 376 | 377 | let ident = &field.ident; 378 | 379 | let docs = ident 380 | .as_ref() 381 | .map(|ident| format!("Sets `{ident}` to the given value.")); 382 | 383 | quote! { 384 | #[doc = #docs] 385 | #vis fn #ident(mut self, #ident: #ty) -> Self { 386 | self.#ident = #ident; 387 | 388 | self 389 | } 390 | } 391 | }) 392 | .collect(); 393 | 394 | Some(methods) 395 | }, 396 | 397 | // Unnamed fields. 398 | Fields::Unnamed(fields) => { 399 | let methods = fields 400 | .unnamed 401 | .iter() 402 | .enumerate() 403 | .map(|(i, field)| { 404 | let vis = &field.vis; 405 | let ty = &field.ty; 406 | 407 | let index = Index::from(i); 408 | let ident = Ident::new(&encode_ordinal(i + 1, '_'), Span::call_site()); 409 | 410 | let ordinal = encode_ordinal(i + 1, ' '); 411 | let docs = format!("Sets the {ordinal} field to the given value."); 412 | 413 | quote! { 414 | #[doc = #docs] 415 | #vis fn #ident(mut self, #ident: #ty) -> Self { 416 | self.#index = #ident; 417 | 418 | self 419 | } 420 | } 421 | }) 422 | .collect(); 423 | 424 | Some(methods) 425 | }, 426 | }; 427 | 428 | // Final generated implementation. 429 | quote! { 430 | impl #impl_generics #name #ty_generics #where_clause { 431 | #new 432 | 433 | #config_methods 434 | } 435 | } 436 | }, 437 | 438 | Data::Enum(_enum) => unimplemented!("enums are not supported"), 439 | Data::Union(_union) => unimplemented!("unions are not supported"), 440 | }; 441 | 442 | tokens.into() 443 | } 444 | -------------------------------------------------------------------------------- /derive-extras/src/numbers_to_words.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use std::iter; 6 | 7 | const DIGITS: [&str; 20] = [ 8 | "zero", 9 | "one", 10 | "two", 11 | "three", 12 | "four", 13 | "five", 14 | "six", 15 | "seven", 16 | "eight", 17 | "nine", 18 | "ten", 19 | "eleven", 20 | "twelve", 21 | "thirteen", 22 | "fourteen", 23 | "fifteen", 24 | "sixteen", 25 | "seventeen", 26 | "eighteen", 27 | "nineteen", 28 | ]; 29 | const TENS: [&str; 8] = [ 30 | "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety", 31 | ]; 32 | const ORDERS: [&str; 12] = [ 33 | "thousand", 34 | "million", 35 | "billion", 36 | "trillion", 37 | "quadrillion", 38 | "quintillion", 39 | "sextillion", 40 | "septillion", 41 | "octillion", 42 | "nonillion", 43 | "decillion", 44 | "undecillion", 45 | ]; 46 | 47 | const DIGITS_ORDINAL: [&str; 20] = [ 48 | "zeroth", 49 | "first", 50 | "second", 51 | "third", 52 | "fourth", 53 | "fifth", 54 | "sixth", 55 | "seventh", 56 | "eighth", 57 | "ninth", 58 | "tenth", 59 | "eleventh", 60 | "twelfth", 61 | "thirteenth", 62 | "fourteenth", 63 | "fifteenth", 64 | "sixteenth", 65 | "seventeenth", 66 | "eighteenth", 67 | "nineteenth", 68 | ]; 69 | const TENS_ORDINAL: [&str; 8] = [ 70 | "twentieth", 71 | "thirtieth", 72 | "fortieth", 73 | "fiftieth", 74 | "sixtieth", 75 | "seventieth", 76 | "eightieth", 77 | "ninetieth", 78 | ]; 79 | const ORDERS_ORDINAL: [&str; 12] = [ 80 | "thousandth", 81 | "millionth", 82 | "billionth", 83 | "trillionth", 84 | "quadrillionth", 85 | "quintillionth", 86 | "sextillionth", 87 | "septillionth", 88 | "octillionth", 89 | "nonillionth", 90 | "decillionth", 91 | "undecillionth", 92 | ]; 93 | 94 | /// Encodes the given `number` as a [string] using the given `separator` between words. 95 | /// 96 | /// # Examples 97 | /// ```no_run 98 | /// # fn encode(number: usize, separator: char) -> String { "".to_owned() } 99 | /// assert_eq!(encode(1_782, ' '), "one thousand seven hundred and eighty two"); 100 | /// assert_eq!(encode(93, '_'), "ninety_three"); 101 | /// ``` 102 | /// 103 | /// [string]: String 104 | pub fn encode(number: usize, separator: char) -> String { 105 | let sep = separator; 106 | 107 | match number { 108 | // Digits and teens. 109 | 0..=19 => DIGITS[number].to_owned(), 110 | // Other tens. 111 | 20..=99 => { 112 | // Tens start at twenty, so subtract 2. 113 | let tens = (number / 10) - 2; 114 | let digits = number % 10; 115 | 116 | match digits { 117 | 0 => TENS[tens].to_owned(), 118 | _ => format!("{}{sep}{}", TENS[tens], DIGITS[digits]), 119 | } 120 | }, 121 | // Hundreds. 122 | 100..=999 => { 123 | let hundreds = number / 100; 124 | let rest = number % 100; 125 | 126 | match rest { 127 | 0 => format!("{}{sep}hundred", DIGITS[hundreds]), 128 | rest => format!("{}{sep}hundred{sep}and{}", DIGITS[hundreds], encode(rest, sep)), 129 | } 130 | }, 131 | // Other numbers. 132 | _ => { 133 | let (div, order) = iter::successors(Some(1usize), |n| n.checked_mul(1_000)) 134 | .zip(ORDERS) 135 | .find(|&(n, _)| n > number / 1_000) 136 | .unwrap(); 137 | 138 | let upper = number / div; 139 | let rest = number % div; 140 | 141 | match rest { 142 | 0 => format!("{}{sep}{}", encode(upper, sep), order), 143 | _ => format!("{}{sep}{}{sep}{}", encode(upper, sep), order, encode(rest, sep)), 144 | } 145 | }, 146 | } 147 | } 148 | 149 | /// Encodes the given ordinal `number` as a [string] using the given `separator` between words. 150 | /// 151 | /// # Examples 152 | /// ```no_run 153 | /// # fn encode_ordinal(number: usize, separator: char) -> String { "".to_owned() } 154 | /// assert_eq!(encode_ordinal(1_782, ' '), "one thousand seven hundred and eighty second"); 155 | /// assert_eq!(encode_ordinal(93, '_'), "ninety_third"); 156 | /// ``` 157 | /// 158 | /// [string]: String 159 | pub fn encode_ordinal(number: usize, separator: char) -> String { 160 | let sep = separator; 161 | 162 | match number { 163 | // Digits and teens. 164 | 0..=19 => DIGITS_ORDINAL[number].to_owned(), 165 | // Other tens. 166 | 20..=99 => { 167 | // Tens start at twenty, so subtract 2. 168 | let tens = (number / 10) - 2; 169 | let digits = number % 10; 170 | 171 | match digits { 172 | 0 => TENS_ORDINAL[tens].to_owned(), 173 | _ => format!("{}{sep}{}", TENS[tens], DIGITS_ORDINAL[digits]), 174 | } 175 | }, 176 | // Hundreds. 177 | 100..=999 => { 178 | let hundreds = number / 100; 179 | let rest = number % 100; 180 | 181 | match rest { 182 | 0 => format!("{}{sep}hundredth", DIGITS[hundreds]), 183 | _ => format!( 184 | "{}{sep}hundred{sep}and{sep}{}", 185 | DIGITS[hundreds], 186 | encode_ordinal(rest, sep) 187 | ), 188 | } 189 | }, 190 | // Other numbers. 191 | _ => { 192 | let (div, (order, order_ordinal)) = iter::successors(Some(1usize), |n| n.checked_mul(1_000)) 193 | .zip(ORDERS.iter().zip(ORDERS_ORDINAL)) 194 | .find(|&(n, _)| n > number / 1_000) 195 | .unwrap(); 196 | 197 | let upper = number / div; 198 | let rest = number % div; 199 | 200 | match rest { 201 | 0 => format!("{}{sep}{}", encode(upper, sep), order_ordinal), 202 | _ => format!("{}{sep}{}{sep}{}", encode(upper, sep), order, encode_ordinal(rest, sep)), 203 | } 204 | }, 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /publish-docs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This is a short script to build `aquariwm-server`'s documentation and publish it to GitHub Pages. 4 | # You can see the GitHub Pages site at https://docs.aquariwm.org/ 5 | 6 | cargo clean 7 | cargo doc 8 | rm -rf ./docs 9 | echo "" > ./target/doc/index.html 10 | cp -r ./target/doc ./docs 11 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use clap::Parser; 6 | 7 | #[derive(Debug, Parser)] 8 | pub struct Cli { 9 | /// Whether AquariWM should be launched in a testing window. 10 | #[cfg(feature = "testing")] 11 | #[arg(long, alias = "test")] 12 | pub testing: bool, 13 | 14 | /// Disables `testing`. 15 | #[cfg(feature = "testing")] 16 | #[arg(long = "no-testing", alias = "no-test", overrides_with = "testing")] 17 | pub no_testing: bool, 18 | 19 | #[arg(long = "window-gap", alias = "gap")] 20 | /// The gap between windows in a tiling layout. 21 | pub window_gap: Option, 22 | 23 | #[command(subcommand)] 24 | pub subcommand: Subcommand, 25 | } 26 | 27 | impl Cli { 28 | /// Returns whether testing is enabled. 29 | #[inline] 30 | pub const fn testing(&self) -> bool { 31 | #[cfg(feature = "testing")] 32 | if cfg!(debug_assertions) { 33 | !self.no_testing 34 | } else { 35 | self.testing 36 | } 37 | 38 | #[cfg(not(feature = "testing"))] 39 | false 40 | } 41 | } 42 | 43 | #[derive(Debug, clap::Subcommand)] 44 | pub enum Subcommand { 45 | /// Launch AquariWM running in Wayland mode. 46 | #[cfg(feature = "wayland")] 47 | Wayland, 48 | /// Launch AquariWM running in X11 mode. 49 | #[cfg(feature = "x11")] 50 | X11, 51 | } 52 | -------------------------------------------------------------------------------- /src/display_server.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use std::{error::Error, future::Future}; 6 | 7 | #[cfg(feature = "wayland")] 8 | pub use wayland::Wayland; 9 | #[cfg(feature = "x11")] 10 | pub use x11::X11; 11 | 12 | use crate::layout::LayoutSettings; 13 | 14 | #[cfg(feature = "wayland")] 15 | pub mod wayland; 16 | #[cfg(feature = "x11")] 17 | pub mod x11; 18 | 19 | /// An implementation of AquariWM for a particular display server (i.e. X11 or Wayland). 20 | pub trait DisplayServer { 21 | /// The return type used by the display server's [`run`] function. 22 | /// 23 | /// [`run`]: Self::run 24 | type Output; 25 | /// The name of the display server (e.g. `"X11"`). 26 | const NAME: &'static str; 27 | 28 | /// Runs the AquariWM implementation for this display server. 29 | fn run(testing: bool, settings: LayoutSettings) -> Self::Output; 30 | 31 | /// Returns AquariWM's title, formatted with the display server [`NAME`]. 32 | /// 33 | /// This can be used for the title of the testing window, for example. 34 | fn title() -> String { 35 | format!("AquariWM ({})", Self::NAME) 36 | } 37 | } 38 | 39 | /// A [display server] whose [`run`] function does not return a [future]. 40 | /// 41 | /// [display server]: DisplayServer 42 | /// [future]: Future 43 | /// [`run`]: Self::run 44 | #[cfg_attr(feature = "async", doc = "", doc = " # See also", doc = " - [AsyncDisplayServer]")] 45 | pub trait SyncDisplayServer: DisplayServer> { 46 | /// The error type returned from the display server's [`run`] function. 47 | /// 48 | /// [`run`]: Self::run 49 | type Error: Error; 50 | } 51 | 52 | #[doc(cfg(feature = "async"))] 53 | /// A [display server] whose [`run`] function returns a [future]. 54 | /// 55 | /// # See also 56 | /// - [SyncDisplayServer] 57 | /// 58 | /// [display server]: DisplayServer 59 | /// [future]: Future 60 | /// [`run`]: Self::run 61 | #[cfg(feature = "async")] 62 | pub trait AsyncDisplayServer: DisplayServer 63 | where 64 | Self::Output: Future>, 65 | { 66 | /// The error type returned from the display server's [`run`] function. 67 | /// 68 | /// [`run`]: Self::run 69 | type Error: Error; 70 | } 71 | -------------------------------------------------------------------------------- /src/display_server/wayland.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use std::{any::Any, env, time::Duration}; 6 | 7 | #[cfg(feature = "testing")] 8 | use smithay::backend::winit::{self, WinitEvent}; 9 | use smithay::{ 10 | backend::renderer::{ 11 | damage::OutputDamageTracker, 12 | element::surface::WaylandSurfaceRenderElement, 13 | gles::GlesRenderer, 14 | }, 15 | desktop::space::render_output, 16 | output::{self, Output, PhysicalProperties, Subpixel}, 17 | reexports::{ 18 | calloop, 19 | calloop::EventLoop, 20 | wayland_server::{backend::InitError, Display}, 21 | }, 22 | utils::{Rectangle, Transform}, 23 | }; 24 | use thiserror::Error; 25 | use tracing::{event, span, Level}; 26 | 27 | use crate::{ 28 | display_server::{DisplayServer, SyncDisplayServer}, 29 | layout::LayoutSettings, 30 | }; 31 | 32 | pub mod grabs; 33 | mod input; 34 | /// Manages AquariWM's state. 35 | pub mod state; 36 | 37 | pub const FPS: i32 = 60; 38 | pub const MS_PER_SECOND: i32 = 1000; 39 | 40 | pub const REFRESH_RATE: i32 = FPS * MS_PER_SECOND; 41 | pub const REFRESH_DELAY: u64 = (MS_PER_SECOND / FPS) as u64; 42 | 43 | #[derive(Debug, Error)] 44 | pub enum Error { 45 | #[error(transparent)] 46 | Calloop(#[from] calloop::Error), 47 | #[error(transparent)] 48 | CalloopInsert(#[from] calloop::InsertError>), 49 | 50 | #[error(transparent)] 51 | WaylandInit(#[from] InitError), 52 | 53 | #[cfg(feature = "testing")] 54 | #[error(transparent)] 55 | Winit(#[from] winit::Error), 56 | } 57 | 58 | pub struct Wayland; 59 | 60 | impl SyncDisplayServer for Wayland { 61 | type Error = Error; 62 | } 63 | 64 | impl DisplayServer for Wayland { 65 | type Output = Result<(), Error>; 66 | const NAME: &'static str = "Wayland"; 67 | 68 | fn run(testing: bool, settings: LayoutSettings) -> Result<(), Error> { 69 | // Log initialisation. 70 | let init_span = span!(Level::INFO, "Initialising").entered(); 71 | 72 | // Create an event loop for the compositor to run with. 73 | let mut event_loop = >::try_new()?; 74 | // Initialise the AquariWM state. 75 | let mut state = state::WaylandState::new(Display::new()?, &mut event_loop, settings); 76 | 77 | // Init winit for testing if the testing feature is enabled. 78 | #[cfg(feature = "testing")] 79 | if testing { 80 | Self::init_winit(&mut event_loop, &mut state)?; 81 | 82 | // Attempt to launch a terminal. 83 | match crate::launch_terminal() { 84 | Ok((name, _)) => event!(Level::INFO, "Launched {name:?}"), 85 | Err(error) => event!(Level::WARN, "Failed to launch terminal: {error}"), 86 | } 87 | } 88 | 89 | // End the initialisation span. 90 | init_span.exit(); 91 | 92 | event_loop.run(None, &mut state, move |_state| {})?; 93 | 94 | Ok(()) 95 | } 96 | } 97 | 98 | impl Wayland { 99 | #[cfg(feature = "testing")] 100 | pub fn init_winit( 101 | event_loop: &mut EventLoop, 102 | state: &mut state::WaylandState, 103 | ) -> Result<(), Error> { 104 | let _span = span!(Level::DEBUG, "Initialising winit").entered(); 105 | 106 | // Initialise winit. 107 | let (mut backend, winit) = winit::init()?; 108 | event!(Level::TRACE, "Initialised winit"); 109 | 110 | // Log the creation of the output. 111 | let output_span = span!(Level::TRACE, "Creating fake AquariWM Winit output").entered(); 112 | 113 | let output_mode = output::Mode { 114 | // Size of the winit window. 115 | size: backend.window_size(), 116 | // Refresh rate - 60 fps. 117 | refresh: FPS * MS_PER_SECOND, 118 | }; 119 | 120 | // Create a fake output for the winit window. 121 | let output = Output::new( 122 | // Output name. 123 | "winit".to_owned(), 124 | // Properties of the fake output. 125 | PhysicalProperties { 126 | // No physical size because there is no physical monitor. 127 | size: (0, 0).into(), 128 | subpixel: Subpixel::Unknown, 129 | make: "AquariWM".to_owned(), 130 | model: format!("({})", Self::NAME), 131 | }, 132 | ); 133 | output.create_global::(&state.display_handle); 134 | 135 | output.change_current_state( 136 | Some(output_mode), 137 | Some(Transform::Flipped180), 138 | None, 139 | // Move to 0,0. 140 | Some((0, 0).into()), 141 | ); 142 | // Prefer the `output_mode`. 143 | output.set_preferred(output_mode); 144 | 145 | state.space.map_output(&output, (0, 0)); 146 | event!(Level::TRACE, "Mapping the fake output"); 147 | 148 | // Exit the output log span. 149 | output_span.exit(); 150 | 151 | let mut damage_tracker = OutputDamageTracker::from_output(&output); 152 | 153 | // Set the `WAYLAND_DISPLAY` for the window manager to use to the socket name. 154 | env::set_var("WAYLAND_DISPLAY", &state.socket_name); 155 | 156 | event_loop 157 | .handle() 158 | .insert_source(winit, move |event, _, state| { 159 | let state::WaylandState { 160 | display_handle, 161 | popup_manager, 162 | space, 163 | start_time, 164 | loop_signal, 165 | .. 166 | } = state; 167 | 168 | match event { 169 | WinitEvent::Resized { size, .. } => { 170 | output.change_current_state( 171 | Some(output::Mode { 172 | size, 173 | refresh: REFRESH_RATE, 174 | }), 175 | None, 176 | None, 177 | None, 178 | ); 179 | }, 180 | 181 | WinitEvent::Input(event) => state.process_input_event(event), 182 | 183 | WinitEvent::Redraw => { 184 | let size = backend.window_size(); 185 | let damage = Rectangle::from_loc_and_size((0, 0), size); 186 | 187 | backend.bind().unwrap(); 188 | 189 | // RustRover has a false negative here, I filed a report at: 190 | // https://youtrack.jetbrains.com/issue/RUST-12497/False-negative-for-E0107-wrong-number-of-type-arguments-when-some-type-arguments-are-feature-flag-gated 191 | // To be fair, this is an absolutely unhinged function signature. 192 | render_output::<_, WaylandSurfaceRenderElement, _, _>( 193 | &output, 194 | backend.renderer(), 195 | 1.0, 196 | 0, 197 | [&*space], 198 | &[], 199 | &mut damage_tracker, 200 | [0.1, 0.1, 0.1, 0.1], 201 | ) 202 | .unwrap(); 203 | backend.submit(Some(&[damage])).unwrap(); 204 | 205 | for window in space.elements() { 206 | window.send_frame(&output, start_time.elapsed(), Some(Duration::ZERO), |_, _| { 207 | Some(output.clone()) 208 | }); 209 | } 210 | 211 | space.refresh(); 212 | popup_manager.cleanup(); 213 | let _ = display_handle.flush_clients(); 214 | 215 | // Ask for a redraw to schedule a new frame. 216 | backend.window().request_redraw(); 217 | }, 218 | 219 | WinitEvent::CloseRequested => { 220 | loop_signal.stop(); 221 | }, 222 | 223 | _ => (), 224 | } 225 | }) 226 | .map_err(|error| calloop::InsertError::> { 227 | inserted: Box::new(error.inserted), 228 | error: error.error, 229 | })?; 230 | 231 | Ok(()) 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/display_server/wayland/grabs.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | pub mod move_grab; 6 | pub mod resize_grab; 7 | 8 | /// The primary mouse button. 9 | /// 10 | /// This is far more commonly known as the left mouse button, but we use the term 'primary mouse 11 | /// button' instead, as a number of people may switch the order of their mouse buttons such that it 12 | /// is not on the left of the mouse. 13 | /// 14 | /// This is defined as [`BTN_LEFT`] in the Linux kernel's `input-event-codes.h` header file. 15 | /// 16 | /// [`BTN_LEFT`]: https://github.com/torvalds/linux/blob/2b93c2c3c02f4243d4c773b880fc86e2788f013d/include/uapi/linux/input-event-codes.h#L356 17 | pub const PRIMARY_BUTTON: u32 = 0x110; 18 | -------------------------------------------------------------------------------- /src/display_server/wayland/grabs/move_grab.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use smithay::{ 6 | desktop::Window, 7 | input::pointer::{ 8 | self, 9 | AxisFrame, 10 | ButtonEvent, 11 | GestureHoldBeginEvent, 12 | GestureHoldEndEvent, 13 | GesturePinchBeginEvent, 14 | GesturePinchEndEvent, 15 | GesturePinchUpdateEvent, 16 | GestureSwipeBeginEvent, 17 | GestureSwipeEndEvent, 18 | GestureSwipeUpdateEvent, 19 | MotionEvent, 20 | PointerGrab, 21 | PointerInnerHandle, 22 | RelativeMotionEvent, 23 | }, 24 | reexports::wayland_server::protocol::wl_surface::WlSurface, 25 | utils::Logical as LogicalSpace, 26 | }; 27 | use tracing::{event, span, Level}; 28 | 29 | use super::{super::state, PRIMARY_BUTTON}; 30 | 31 | type Point = smithay::utils::Point; 32 | 33 | pub struct MoveSurfaceGrab { 34 | pub start_data: pointer::GrabStartData, 35 | pub window: Window, 36 | pub initial_window_location: Point, 37 | } 38 | 39 | impl PointerGrab for MoveSurfaceGrab { 40 | fn motion( 41 | &mut self, 42 | state: &mut state::WaylandState, 43 | handle: &mut PointerInnerHandle<'_, state::WaylandState>, 44 | _focus: Option<(WlSurface, Point)>, 45 | event: &MotionEvent, 46 | ) { 47 | let _span = span!(Level::DEBUG, "Moving window"); 48 | 49 | // While the grab is active, no client has pointer focus. 50 | handle.motion(state, None, event); 51 | 52 | let delta = event.location - self.start_data.location; 53 | let new_location = self.initial_window_location.to_f64() + delta; 54 | state 55 | .space 56 | .map_element(self.window.clone(), new_location.to_i32_round(), true); 57 | 58 | let (delta_x, delta_y) = (delta.x, delta.y); 59 | event!(Level::TRACE, delta_x, delta_y, "Updated window position"); 60 | } 61 | 62 | fn relative_motion( 63 | &mut self, 64 | state: &mut state::WaylandState, 65 | handle: &mut PointerInnerHandle<'_, state::WaylandState>, 66 | focus: Option<(WlSurface, Point)>, 67 | event: &RelativeMotionEvent, 68 | ) { 69 | handle.relative_motion(state, focus, event); 70 | } 71 | 72 | fn button( 73 | &mut self, 74 | state: &mut state::WaylandState, 75 | handle: &mut PointerInnerHandle<'_, state::WaylandState>, 76 | event: &ButtonEvent, 77 | ) { 78 | handle.button(state, event); 79 | 80 | if !handle.current_pressed().contains(&PRIMARY_BUTTON) { 81 | // No more buttons are pressed: release the grab. 82 | handle.unset_grab(state, event.serial, event.time, true); 83 | } 84 | } 85 | 86 | fn axis( 87 | &mut self, 88 | state: &mut state::WaylandState, 89 | handle: &mut PointerInnerHandle<'_, state::WaylandState>, 90 | details: AxisFrame, 91 | ) { 92 | handle.axis(state, details); 93 | } 94 | 95 | fn frame(&mut self, state: &mut state::WaylandState, handle: &mut PointerInnerHandle<'_, state::WaylandState>) { 96 | handle.frame(state); 97 | } 98 | 99 | fn gesture_swipe_begin( 100 | &mut self, 101 | state: &mut state::WaylandState, 102 | handle: &mut PointerInnerHandle<'_, state::WaylandState>, 103 | event: &GestureSwipeBeginEvent, 104 | ) { 105 | handle.gesture_swipe_begin(state, event); 106 | } 107 | 108 | fn gesture_swipe_update( 109 | &mut self, 110 | state: &mut state::WaylandState, 111 | handle: &mut PointerInnerHandle<'_, state::WaylandState>, 112 | event: &GestureSwipeUpdateEvent, 113 | ) { 114 | handle.gesture_swipe_update(state, event); 115 | } 116 | 117 | fn gesture_swipe_end( 118 | &mut self, 119 | state: &mut state::WaylandState, 120 | handle: &mut PointerInnerHandle<'_, state::WaylandState>, 121 | event: &GestureSwipeEndEvent, 122 | ) { 123 | handle.gesture_swipe_end(state, event); 124 | } 125 | 126 | fn gesture_pinch_begin( 127 | &mut self, 128 | state: &mut state::WaylandState, 129 | handle: &mut PointerInnerHandle<'_, state::WaylandState>, 130 | event: &GesturePinchBeginEvent, 131 | ) { 132 | handle.gesture_pinch_begin(state, event); 133 | } 134 | 135 | fn gesture_pinch_update( 136 | &mut self, 137 | state: &mut state::WaylandState, 138 | handle: &mut PointerInnerHandle<'_, state::WaylandState>, 139 | event: &GesturePinchUpdateEvent, 140 | ) { 141 | handle.gesture_pinch_update(state, event); 142 | } 143 | 144 | fn gesture_pinch_end( 145 | &mut self, 146 | state: &mut state::WaylandState, 147 | handle: &mut PointerInnerHandle<'_, state::WaylandState>, 148 | event: &GesturePinchEndEvent, 149 | ) { 150 | handle.gesture_pinch_end(state, event); 151 | } 152 | 153 | fn gesture_hold_begin( 154 | &mut self, 155 | state: &mut state::WaylandState, 156 | handle: &mut PointerInnerHandle<'_, state::WaylandState>, 157 | event: &GestureHoldBeginEvent, 158 | ) { 159 | handle.gesture_hold_begin(state, event); 160 | } 161 | 162 | fn gesture_hold_end( 163 | &mut self, 164 | state: &mut state::WaylandState, 165 | handle: &mut PointerInnerHandle<'_, state::WaylandState>, 166 | event: &GestureHoldEndEvent, 167 | ) { 168 | handle.gesture_hold_end(state, event); 169 | } 170 | 171 | fn start_data(&self) -> &pointer::GrabStartData { 172 | &self.start_data 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/display_server/wayland/grabs/resize_grab.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use std::cell::RefCell; 6 | 7 | use bitflags::bitflags; 8 | use smithay::{ 9 | desktop::{Space, Window}, 10 | input::{ 11 | pointer::{ 12 | self, 13 | GestureHoldBeginEvent, 14 | GestureHoldEndEvent, 15 | GesturePinchBeginEvent, 16 | GesturePinchEndEvent, 17 | GesturePinchUpdateEvent, 18 | GestureSwipeBeginEvent, 19 | GestureSwipeEndEvent, 20 | GestureSwipeUpdateEvent, 21 | PointerGrab, 22 | }, 23 | SeatHandler, 24 | }, 25 | reexports::{wayland_protocols::xdg::shell::server::xdg_toplevel, wayland_server::protocol::wl_surface::WlSurface}, 26 | utils::Logical as LogicalSpace, 27 | wayland::{compositor, shell::xdg::SurfaceCachedState}, 28 | }; 29 | use tracing::{event, span, Level}; 30 | 31 | use super::{super::state, PRIMARY_BUTTON}; 32 | 33 | type Rectangle = smithay::utils::Rectangle; 34 | type Size = smithay::utils::Size; 35 | type Point = smithay::utils::Point; 36 | 37 | type PointerInnerHandle<'handle, State = state::WaylandState> = pointer::PointerInnerHandle<'handle, State>; 38 | type Focus = 39 | (::PointerFocus, Point); 40 | 41 | bitflags! { 42 | /// The edge of a window that is to be resized. 43 | #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] 44 | // RustRover currently has a false negative here. 45 | pub struct ResizeEdge: u32 { 46 | /// The top side of the window is to be resized. 47 | const TOP = 0b0001; 48 | /// The bottom side of the window is to be resized. 49 | const BOTTOM = 0b0010; 50 | /// The left side of the window is to be resized. 51 | const LEFT = 0b0100; 52 | /// The right side of the window is to be resized. 53 | const RIGHT = 0b1000; 54 | 55 | /// The top-left corner of the window is to be resized. 56 | const TOP_LEFT = Self::TOP.bits() | Self::LEFT.bits(); 57 | /// The bottom-left corner of the window is to be resized. 58 | const BOTTOM_LEFT = Self::BOTTOM.bits() | Self::LEFT.bits(); 59 | /// The top-right corner of the window is to be resized. 60 | const TOP_RIGHT = Self::TOP.bits() | Self::RIGHT.bits(); 61 | /// The bottom-right corner of the window is to be resized. 62 | const BOTTOM_RIGHT = Self::BOTTOM.bits() | Self::RIGHT.bits(); 63 | } 64 | } 65 | 66 | impl ResizeEdge { 67 | /// Whether the window's [`LEFT`] edge is being resized. 68 | /// 69 | /// [`LEFT`]: Self::LEFT 70 | pub fn left(&self) -> bool { 71 | self.intersects(Self::LEFT) 72 | } 73 | 74 | /// Whether the window's [`TOP`] edge is being resized. 75 | /// 76 | /// [`TOP`]: Self::TOP 77 | pub fn top(&self) -> bool { 78 | self.intersects(Self::TOP) 79 | } 80 | 81 | /// Whether the window's [`RIGHT`] edge is being resized. 82 | /// 83 | /// [`RIGHT`]: Self::RIGHT 84 | pub fn right(&self) -> bool { 85 | self.intersects(Self::RIGHT) 86 | } 87 | 88 | /// Whether the window's [`BOTTOM`] edge is being resized. 89 | /// 90 | /// [`BOTTOM`]: Self::BOTTOM 91 | pub fn bottom(&self) -> bool { 92 | self.intersects(Self::BOTTOM) 93 | } 94 | } 95 | 96 | impl From for ResizeEdge { 97 | #[inline] 98 | fn from(resize_edge: xdg_toplevel::ResizeEdge) -> Self { 99 | Self::from_bits(resize_edge as u32).unwrap() 100 | } 101 | } 102 | 103 | pub struct ResizeSurfaceGrab { 104 | start_data: pointer::GrabStartData, 105 | window: Window, 106 | 107 | edges: ResizeEdge, 108 | 109 | initial_rectangle: Rectangle, 110 | target_size: Size, 111 | } 112 | 113 | #[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] 114 | enum ResizeSurfaceState { 115 | #[default] 116 | Idle, 117 | 118 | /// Currently in the process of resizing. 119 | Resizing { 120 | edges: ResizeEdge, 121 | initial_rectangle: Rectangle, 122 | }, 123 | /// Resizing is complete, currently waiting for the final commit before we can do the final 124 | /// move. 125 | WaitingForFinalCommit { 126 | edges: ResizeEdge, 127 | initial_rectangle: Rectangle, 128 | }, 129 | } 130 | 131 | impl ResizeSurfaceState { 132 | /// Calls the given `callback` with the resize state for the given `surface`. 133 | fn with(surface: &WlSurface, callback: impl FnOnce(&mut Self) -> T) -> T { 134 | compositor::with_states(surface, |states| { 135 | states.data_map.insert_if_missing(RefCell::::default); 136 | let state = states.data_map.get::>().unwrap(); 137 | 138 | callback(&mut state.borrow_mut()) 139 | }) 140 | } 141 | 142 | fn commit(&mut self) -> Option<(ResizeEdge, Rectangle)> { 143 | match *self { 144 | Self::Idle => None, 145 | 146 | Self::Resizing { 147 | edges, 148 | initial_rectangle, 149 | } => Some((edges, initial_rectangle)), 150 | // Commit changes. 151 | Self::WaitingForFinalCommit { 152 | edges, 153 | initial_rectangle, 154 | } => { 155 | // The resize is complete. 156 | *self = Self::Idle; 157 | 158 | Some((edges, initial_rectangle)) 159 | }, 160 | } 161 | } 162 | } 163 | 164 | impl ResizeSurfaceGrab { 165 | pub fn start( 166 | start_data: pointer::GrabStartData, 167 | window: Window, 168 | edges: ResizeEdge, 169 | initial_rectangle: Rectangle, 170 | ) -> Self { 171 | ResizeSurfaceState::with(window.toplevel().wl_surface(), |state| { 172 | *state = ResizeSurfaceState::Resizing { 173 | edges, 174 | initial_rectangle, 175 | }; 176 | }); 177 | 178 | Self { 179 | start_data, 180 | window, 181 | edges, 182 | initial_rectangle, 183 | target_size: initial_rectangle.size, 184 | } 185 | } 186 | } 187 | 188 | impl PointerGrab for ResizeSurfaceGrab { 189 | fn motion( 190 | &mut self, 191 | state: &mut state::WaylandState, 192 | handle: &mut PointerInnerHandle<'_>, 193 | _focus: Option, 194 | event: &pointer::MotionEvent, 195 | ) { 196 | let _span = span!(Level::DEBUG, "Resizing window").entered(); 197 | 198 | // While the grab is active, no client has pointer focus. 199 | handle.motion(state, None, event); 200 | 201 | // Determine the change in the window's dimensions. 202 | let (delta_width, delta_height) = { 203 | // Difference between the current pointer position and the original pointer position. 204 | let delta = event.location - self.start_data.location; 205 | 206 | let delta_width = if self.edges.left() { 207 | // If the left edge is being resized, then moving right actually shrinks the window. 208 | -delta.x 209 | } else if self.edges.right() { 210 | delta.x 211 | } else { 212 | // If neither the left nor right edges are resized, then the change in width is 0. 213 | 0.0 214 | }; 215 | let delta_height = if self.edges.top() { 216 | // If the top edge is being resized, then moving down actually shrinks the window. 217 | -delta.y 218 | } else if self.edges.bottom() { 219 | delta.y 220 | } else { 221 | // If neither the top nor bottom edges are resized, then the change in height is 0. 222 | 0.0 223 | }; 224 | 225 | (delta_width as i32, delta_height as i32) 226 | }; 227 | 228 | // Determine the new dimensions of the window. 229 | let (new_width, new_height) = { 230 | let (initial_width, initial_height) = (self.initial_rectangle.size.w, self.initial_rectangle.size.h); 231 | 232 | (initial_width + delta_width, initial_height + delta_height) 233 | }; 234 | 235 | let top_level_surface = self.window.toplevel(); 236 | let wl_surface = top_level_surface.wl_surface(); 237 | 238 | // Determine the minimum and maximum sizes of the window. 239 | let ((min_width, min_height), (max_width, max_height)) = compositor::with_states(wl_surface, |states| { 240 | let data = states.cached_state.current::(); 241 | 242 | let max = |dimension| if dimension == 0 { i32::MAX } else { dimension }; 243 | 244 | let min_size = (data.min_size.w.max(1), data.min_size.h.max(1)); 245 | let max_size = (max(data.max_size.w), max(data.max_size.h)); 246 | 247 | (min_size, max_size) 248 | }); 249 | 250 | // Set the target size for resizing. 251 | self.target_size = Size::from(( 252 | new_width.max(min_width).min(max_width), 253 | new_height.max(min_height).min(max_height), 254 | )); 255 | 256 | // 'Register' the resize with the XDG top level surface. 257 | top_level_surface.with_pending_state(|state| { 258 | state.states.set(xdg_toplevel::State::Resizing); 259 | state.size = Some(self.target_size); 260 | }); 261 | event!(Level::TRACE, delta_width, delta_height, "Updated window size"); 262 | 263 | top_level_surface.send_pending_configure(); 264 | } 265 | 266 | fn relative_motion( 267 | &mut self, 268 | state: &mut state::WaylandState, 269 | handle: &mut PointerInnerHandle<'_>, 270 | focus: Option, 271 | event: &pointer::RelativeMotionEvent, 272 | ) { 273 | handle.relative_motion(state, focus, event); 274 | } 275 | 276 | fn button( 277 | &mut self, 278 | state: &mut state::WaylandState, 279 | handle: &mut PointerInnerHandle<'_>, 280 | event: &pointer::ButtonEvent, 281 | ) { 282 | handle.button(state, event); 283 | 284 | if !handle.current_pressed().contains(&PRIMARY_BUTTON) { 285 | // No more buttons are pressed: release the grab. 286 | handle.unset_grab(state, event.serial, event.time, true); 287 | 288 | let top_level_surface = self.window.toplevel(); 289 | let wl_surface = top_level_surface.wl_surface(); 290 | 291 | // 'Register' the end of the resize with the XDG top level surface. 292 | top_level_surface.with_pending_state(|state| { 293 | state.states.unset(xdg_toplevel::State::Resizing); 294 | state.size = Some(self.target_size); 295 | }); 296 | 297 | top_level_surface.send_pending_configure(); 298 | 299 | // Update the resize surface state. 300 | ResizeSurfaceState::with(wl_surface, |state| { 301 | *state = ResizeSurfaceState::WaitingForFinalCommit { 302 | edges: self.edges, 303 | initial_rectangle: self.initial_rectangle, 304 | } 305 | }); 306 | } 307 | } 308 | 309 | fn axis( 310 | &mut self, 311 | state: &mut state::WaylandState, 312 | handle: &mut PointerInnerHandle<'_>, 313 | details: pointer::AxisFrame, 314 | ) { 315 | handle.axis(state, details); 316 | } 317 | 318 | fn frame(&mut self, state: &mut state::WaylandState, handle: &mut PointerInnerHandle<'_>) { 319 | handle.frame(state); 320 | } 321 | 322 | fn gesture_swipe_begin( 323 | &mut self, 324 | state: &mut state::WaylandState, 325 | handle: &mut PointerInnerHandle<'_>, 326 | event: &GestureSwipeBeginEvent, 327 | ) { 328 | handle.gesture_swipe_begin(state, event); 329 | } 330 | 331 | fn gesture_swipe_update( 332 | &mut self, 333 | state: &mut state::WaylandState, 334 | handle: &mut PointerInnerHandle<'_>, 335 | event: &GestureSwipeUpdateEvent, 336 | ) { 337 | handle.gesture_swipe_update(state, event); 338 | } 339 | 340 | fn gesture_swipe_end( 341 | &mut self, 342 | state: &mut state::WaylandState, 343 | handle: &mut PointerInnerHandle<'_>, 344 | event: &GestureSwipeEndEvent, 345 | ) { 346 | handle.gesture_swipe_end(state, event); 347 | } 348 | 349 | fn gesture_pinch_begin( 350 | &mut self, 351 | state: &mut state::WaylandState, 352 | handle: &mut PointerInnerHandle<'_>, 353 | event: &GesturePinchBeginEvent, 354 | ) { 355 | handle.gesture_pinch_begin(state, event); 356 | } 357 | 358 | fn gesture_pinch_update( 359 | &mut self, 360 | state: &mut state::WaylandState, 361 | handle: &mut PointerInnerHandle<'_>, 362 | event: &GesturePinchUpdateEvent, 363 | ) { 364 | handle.gesture_pinch_update(state, event); 365 | } 366 | 367 | fn gesture_pinch_end( 368 | &mut self, 369 | state: &mut state::WaylandState, 370 | handle: &mut PointerInnerHandle<'_>, 371 | event: &GesturePinchEndEvent, 372 | ) { 373 | handle.gesture_pinch_end(state, event); 374 | } 375 | 376 | fn gesture_hold_begin( 377 | &mut self, 378 | state: &mut state::WaylandState, 379 | handle: &mut PointerInnerHandle<'_>, 380 | event: &GestureHoldBeginEvent, 381 | ) { 382 | handle.gesture_hold_begin(state, event); 383 | } 384 | 385 | fn gesture_hold_end( 386 | &mut self, 387 | state: &mut state::WaylandState, 388 | handle: &mut PointerInnerHandle<'_>, 389 | event: &GestureHoldEndEvent, 390 | ) { 391 | handle.gesture_hold_end(state, event); 392 | } 393 | 394 | fn start_data(&self) -> &pointer::GrabStartData { 395 | &self.start_data 396 | } 397 | } 398 | 399 | pub fn handle_commit(space: &mut Space, surface: &WlSurface) -> Option<()> { 400 | let window = space 401 | .elements() 402 | .find(|window| window.toplevel().wl_surface() == surface) 403 | .cloned()?; 404 | 405 | let mut window_location = space.element_location(&window)?; 406 | let geometry = window.geometry(); 407 | 408 | let (new_x, new_y) = ResizeSurfaceState::with(surface, |state| { 409 | state 410 | .commit() 411 | .map(|(edges, initial_rectangle)| { 412 | let (initial_x, initial_y) = (initial_rectangle.loc.x, initial_rectangle.loc.y); 413 | let (initial_width, initial_height) = (initial_rectangle.size.w, initial_rectangle.size.h); 414 | 415 | let (geometry_width, geometry_height) = (geometry.size.w, geometry.size.h); 416 | 417 | // If the window is being resized by the left edge, its x must be adjusted 418 | // accordingly. 419 | let new_x = edges 420 | .intersects(ResizeEdge::LEFT) 421 | .then_some(initial_x + (initial_width - geometry_width)); 422 | 423 | // If the window is being resized by the top edge, its y must be adjusted 424 | // accordingly. 425 | let new_y = edges 426 | .intersects(ResizeEdge::TOP) 427 | .then_some(initial_y + (initial_height - geometry_height)); 428 | 429 | (new_x, new_y) 430 | }) 431 | .unwrap_or((None, None)) 432 | }); 433 | 434 | if let Some(new_x) = new_x { 435 | window_location.x = new_x; 436 | } 437 | if let Some(new_y) = new_y { 438 | window_location.y = new_y; 439 | } 440 | 441 | // If either of the top or left edges of the window were resized, then its location must be 442 | // updated accordingly. 443 | if new_x.is_some() || new_y.is_some() { 444 | space.map_element(window, window_location, false); 445 | } 446 | 447 | Some(()) 448 | } 449 | -------------------------------------------------------------------------------- /src/display_server/wayland/input.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use smithay::{ 6 | backend::input::{ 7 | AbsolutePositionEvent, 8 | Axis, 9 | AxisSource, 10 | ButtonState, 11 | Event, 12 | InputBackend, 13 | InputEvent, 14 | KeyboardKeyEvent, 15 | PointerAxisEvent, 16 | PointerButtonEvent, 17 | }, 18 | input::{keyboard::FilterResult, pointer, pointer::AxisFrame}, 19 | utils::SERIAL_COUNTER, 20 | }; 21 | 22 | use super::state; 23 | 24 | impl state::WaylandState { 25 | pub fn process_input_event(&mut self, event: InputEvent) { 26 | match event { 27 | InputEvent::Keyboard { event, .. } => { 28 | let serial = SERIAL_COUNTER.next_serial(); 29 | let time = Event::time_msec(&event); 30 | 31 | self.seat.get_keyboard().unwrap().input::<(), _>( 32 | self, 33 | event.key_code(), 34 | event.state(), 35 | serial, 36 | time, 37 | |_, _, _| FilterResult::Forward, 38 | ); 39 | }, 40 | 41 | InputEvent::PointerMotion { .. } => (), 42 | InputEvent::PointerMotionAbsolute { event, .. } => { 43 | let output = self.space.outputs().next().unwrap(); 44 | 45 | let output_geometry = self.space.output_geometry(output).unwrap(); 46 | let (output_size, output_loc) = (output_geometry.size, output_geometry.loc); 47 | 48 | // Location of the pointer. 49 | let location = event.position_transformed(output_size) + output_loc.to_f64(); 50 | 51 | // The serial number to use in the pointer motion event. 52 | let serial = SERIAL_COUNTER.next_serial(); 53 | 54 | let pointer = self.seat.get_pointer().unwrap(); 55 | 56 | let under = self.surface_under(location); 57 | 58 | pointer.motion( 59 | self, 60 | under, 61 | &pointer::MotionEvent { 62 | location, 63 | serial, 64 | time: event.time_msec(), 65 | }, 66 | ); 67 | pointer.frame(self); 68 | }, 69 | 70 | InputEvent::PointerButton { event, .. } => { 71 | let pointer = self.seat.get_pointer().unwrap(); 72 | // FIXME: the keyboard may not be connected. 73 | let keyboard = self.seat.get_keyboard().unwrap(); 74 | 75 | let serial = SERIAL_COUNTER.next_serial(); 76 | 77 | let button = event.button_code(); 78 | let button_state = event.state(); 79 | 80 | if ButtonState::Pressed == button_state && !pointer.is_grabbed() { 81 | match self 82 | .space 83 | .element_under(pointer.current_location()) 84 | .map(|(window, location)| (window.clone(), location)) 85 | { 86 | Some((window, _)) => { 87 | self.space.raise_element(&window, true); 88 | 89 | let wl_surface = window.toplevel().wl_surface().clone(); 90 | keyboard.set_focus(self, Some(wl_surface), serial); 91 | 92 | for window in self.space.elements() { 93 | window.toplevel().send_pending_configure(); 94 | } 95 | }, 96 | 97 | None => { 98 | for window in self.space.elements() { 99 | window.set_activated(false); 100 | window.toplevel().send_pending_configure(); 101 | } 102 | 103 | keyboard.set_focus(self, None, serial); 104 | }, 105 | } 106 | } 107 | 108 | pointer.button( 109 | self, 110 | &pointer::ButtonEvent { 111 | button, 112 | state: button_state, 113 | serial, 114 | time: event.time_msec(), 115 | }, 116 | ); 117 | pointer.frame(self); 118 | }, 119 | 120 | InputEvent::PointerAxis { event, .. } => { 121 | let source = event.source(); 122 | 123 | let pointer = self.seat.get_pointer().unwrap(); 124 | 125 | let horizontal_amount_discrete = event.amount_discrete(Axis::Horizontal); 126 | let vertical_amount_discrete = event.amount_discrete(Axis::Vertical); 127 | 128 | let horizontal_amount = event 129 | .amount(Axis::Horizontal) 130 | .or_else(|| horizontal_amount_discrete.map(|discrete| discrete * 3.0)); 131 | let vertical_amount = event 132 | .amount(Axis::Vertical) 133 | .or_else(|| vertical_amount_discrete.map(|discrete| discrete * 3.0)); 134 | 135 | let frame = { 136 | let mut frame = AxisFrame::new(event.time_msec()).source(source); 137 | 138 | let mut update_amount = |axis| { 139 | let (amount, discrete) = match axis { 140 | Axis::Horizontal => (horizontal_amount, horizontal_amount_discrete), 141 | Axis::Vertical => (vertical_amount, vertical_amount_discrete), 142 | }; 143 | 144 | if let Some(amount) = amount { 145 | frame = frame.value(axis, amount); 146 | 147 | if let Some(discrete) = discrete { 148 | frame = frame.discrete(Axis::Horizontal, discrete as i32); 149 | } 150 | } 151 | 152 | if source == AxisSource::Finger && event.amount(axis) == Some(0.0) { 153 | frame = frame.stop(axis); 154 | } 155 | }; 156 | 157 | update_amount(Axis::Horizontal); 158 | update_amount(Axis::Vertical); 159 | 160 | frame 161 | }; 162 | 163 | pointer.axis(self, frame); 164 | pointer.frame(self); 165 | }, 166 | 167 | _ => (), 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/display_server/wayland/state.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use std::{ffi::OsString, sync::Arc, time}; 6 | 7 | use smithay::{ 8 | backend::renderer::utils::on_commit_buffer_handler, 9 | delegate_compositor, 10 | delegate_data_device, 11 | delegate_output, 12 | delegate_seat, 13 | delegate_shm, 14 | delegate_xdg_shell, 15 | desktop::{self as wl, PopupKind, PopupManager, Space, WindowSurfaceType}, 16 | input::{keyboard::XkbConfig, pointer, pointer::CursorImageStatus, Seat, SeatHandler, SeatState}, 17 | reexports::{ 18 | calloop, 19 | calloop::{generic::Generic, EventLoop, Interest, LoopSignal}, 20 | wayland_protocols::xdg::shell::server::xdg_toplevel, 21 | wayland_server::{ 22 | backend::{ClientData, ClientId, DisconnectReason}, 23 | protocol::{wl_buffer::WlBuffer, wl_seat::WlSeat, wl_surface::WlSurface}, 24 | Client, 25 | Display, 26 | DisplayHandle, 27 | Resource, 28 | }, 29 | }, 30 | utils::{Logical as LogicalSpace, Rectangle, Serial}, 31 | wayland::{ 32 | buffer::BufferHandler, 33 | compositor::{get_parent, is_sync_subsurface, CompositorClientState, CompositorHandler, CompositorState}, 34 | output::OutputManagerState, 35 | selection::{ 36 | data_device::{ 37 | set_data_device_focus, 38 | ClientDndGrabHandler, 39 | DataDeviceHandler, 40 | DataDeviceState, 41 | ServerDndGrabHandler, 42 | }, 43 | SelectionHandler, 44 | }, 45 | shell::xdg::{self, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState}, 46 | shm::{ShmHandler, ShmState}, 47 | socket::ListeningSocketSource, 48 | }, 49 | }; 50 | 51 | use super::grabs::{move_grab::MoveSurfaceGrab, resize_grab::ResizeSurfaceGrab}; 52 | use crate::{layout::LayoutSettings, state::MapState}; 53 | 54 | type Point = smithay::utils::Point; 55 | 56 | pub struct WaylandState { 57 | pub start_time: time::Instant, 58 | pub socket_name: OsString, 59 | pub display_handle: DisplayHandle, 60 | 61 | pub space: Space, 62 | pub loop_signal: LoopSignal, 63 | 64 | // AquariWM state. 65 | pub aquariwm_state: crate::state::AquariWm, 66 | 67 | // Smithay state. 68 | pub compositor_state: CompositorState, 69 | pub xdg_shell_state: XdgShellState, 70 | pub shm_state: ShmState, 71 | pub output_manager_state: OutputManagerState, 72 | pub seat_state: SeatState, 73 | pub data_device_state: DataDeviceState, 74 | 75 | pub popup_manager: PopupManager, 76 | 77 | pub seat: Seat, 78 | } 79 | 80 | // Seat impls {{{ 81 | impl SeatHandler for WaylandState { 82 | type KeyboardFocus = WlSurface; 83 | type PointerFocus = WlSurface; 84 | 85 | fn seat_state(&mut self) -> &mut SeatState { 86 | &mut self.seat_state 87 | } 88 | 89 | fn focus_changed(&mut self, seat: &Seat, focused: Option<&Self::KeyboardFocus>) { 90 | let display_handle = &self.display_handle; 91 | 92 | let client = focused.and_then(|surface| display_handle.get_client(surface.id()).ok()); 93 | set_data_device_focus(display_handle, seat, client); 94 | } 95 | 96 | fn cursor_image(&mut self, _seat: &Seat, _image: CursorImageStatus) {} 97 | } 98 | 99 | delegate_seat!(WaylandState); 100 | // }}} 101 | 102 | // Data device impls {{{ 103 | impl SelectionHandler for WaylandState { 104 | type SelectionUserData = (); 105 | } 106 | 107 | impl DataDeviceHandler for WaylandState { 108 | fn data_device_state(&self) -> &DataDeviceState { 109 | &self.data_device_state 110 | } 111 | } 112 | 113 | impl ClientDndGrabHandler for WaylandState {} 114 | 115 | impl ServerDndGrabHandler for WaylandState {} 116 | 117 | delegate_data_device!(WaylandState); 118 | // }}} 119 | 120 | // Output impl 121 | delegate_output!(WaylandState); 122 | 123 | // XDG shell impls {{{ 124 | impl XdgShellHandler for WaylandState { 125 | fn xdg_shell_state(&mut self) -> &mut XdgShellState { 126 | &mut self.xdg_shell_state 127 | } 128 | 129 | fn new_toplevel(&mut self, surface: ToplevelSurface) { 130 | let window = wl::Window::new(surface); 131 | // Track the window. 132 | self.aquariwm_state.add_window(window.clone(), MapState::Mapped); 133 | 134 | self.space.map_element(window, (0, 0), false); 135 | 136 | // TODO: configure window 137 | } 138 | 139 | fn new_popup(&mut self, surface: xdg::PopupSurface, _positioner: PositionerState) { 140 | let _ = self.popup_manager.track_popup(PopupKind::Xdg(surface)); 141 | } 142 | 143 | fn move_request(&mut self, surface: ToplevelSurface, seat: WlSeat, serial: Serial) { 144 | let seat = >::from_resource(&seat).unwrap(); 145 | 146 | let wl_surface = surface.wl_surface(); 147 | 148 | if let Some(start_data) = check_grab(&seat, wl_surface, serial) { 149 | let pointer = seat.get_pointer().unwrap(); 150 | 151 | let window = self 152 | .space 153 | .elements() 154 | .find(|window| window.toplevel().wl_surface() == wl_surface) 155 | .unwrap() 156 | .clone(); 157 | let initial_window_location = self.space.element_location(&window).unwrap(); 158 | 159 | let grab = MoveSurfaceGrab { 160 | start_data, 161 | window, 162 | initial_window_location, 163 | }; 164 | 165 | // False negative in RustRover/IntelliJ Rust plugin here. 166 | pointer.set_grab(self, grab, serial, pointer::Focus::Clear); 167 | } 168 | } 169 | 170 | fn resize_request( 171 | &mut self, 172 | surface: ToplevelSurface, 173 | seat: WlSeat, 174 | serial: Serial, 175 | edges: xdg_toplevel::ResizeEdge, 176 | ) { 177 | let seat = Seat::from_resource(&seat).unwrap(); 178 | 179 | let wl_surface = surface.wl_surface(); 180 | 181 | if let Some(start_data) = check_grab(&seat, wl_surface, serial) { 182 | let pointer = seat.get_pointer().unwrap(); 183 | 184 | let window = self 185 | .space 186 | .elements() 187 | .find(|window| window.toplevel().wl_surface() == wl_surface) 188 | .unwrap() 189 | .clone(); 190 | let initial_window_location = self.space.element_location(&window).unwrap(); 191 | let initial_window_size = window.geometry().size; 192 | 193 | surface.with_pending_state(|state| state.states.set(xdg_toplevel::State::Resizing)); 194 | surface.send_pending_configure(); 195 | 196 | let grab = ResizeSurfaceGrab::start( 197 | start_data, 198 | window, 199 | edges.into(), 200 | Rectangle::from_loc_and_size(initial_window_location, initial_window_size), 201 | ); 202 | 203 | // False negative in RustRover/IntelliJ Rust plugin here. 204 | pointer.set_grab(self, grab, serial, pointer::Focus::Clear); 205 | } 206 | } 207 | 208 | fn grab(&mut self, _surface: xdg::PopupSurface, _seat: WlSeat, _serial: Serial) { 209 | // TODO: popup grabs 210 | } 211 | 212 | fn reposition_request(&mut self, surface: xdg::PopupSurface, positioner: PositionerState, token: u32) { 213 | surface.with_pending_state(|state| { 214 | // We should be calculating the geometry here, not using the default implementation, as it does not 215 | // take the window position and output constraints into account. 216 | let geometry = positioner.get_geometry(); 217 | state.geometry = geometry; 218 | state.positioner = positioner; 219 | }); 220 | 221 | surface.send_repositioned(token); 222 | } 223 | } 224 | 225 | delegate_xdg_shell!(WaylandState); 226 | 227 | fn check_grab( 228 | seat: &Seat, 229 | surface: &WlSurface, 230 | serial: Serial, 231 | ) -> Option> { 232 | let pointer = seat.get_pointer()?; 233 | 234 | // If this surface has a pointer grab... 235 | if pointer.has_grab(serial) { 236 | let start_data = pointer.grab_start_data()?; 237 | let (focus_surface, _) = start_data.focus.as_ref()?; 238 | 239 | // If the focus was for the same surface, return the grab start data. 240 | if focus_surface.id().same_client_as(&surface.id()) { 241 | return Some(start_data); 242 | } 243 | } 244 | 245 | None 246 | } 247 | // }}} 248 | 249 | // Compositor impls {{{ 250 | impl CompositorHandler for WaylandState { 251 | fn compositor_state(&mut self) -> &mut CompositorState { 252 | &mut self.compositor_state 253 | } 254 | 255 | fn client_compositor_state<'client>(&self, client: &'client Client) -> &'client CompositorClientState { 256 | &client.get_data::().unwrap().compositor_state 257 | } 258 | 259 | fn commit(&mut self, surface: &WlSurface) { 260 | on_commit_buffer_handler::(surface); 261 | 262 | if !is_sync_subsurface(surface) { 263 | // Find the root node. 264 | let mut root = surface.clone(); 265 | while let Some(parent) = get_parent(&root) { 266 | root = parent; 267 | } 268 | 269 | if let Some(window) = self 270 | .space 271 | .elements() 272 | .find(|window| window.toplevel().wl_surface() == &root) 273 | { 274 | window.on_commit(); 275 | } 276 | } 277 | } 278 | } 279 | 280 | impl BufferHandler for WaylandState { 281 | fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {} 282 | } 283 | 284 | impl ShmHandler for WaylandState { 285 | fn shm_state(&self) -> &ShmState { 286 | &self.shm_state 287 | } 288 | } 289 | 290 | delegate_compositor!(WaylandState); 291 | delegate_shm!(WaylandState); 292 | // }}} 293 | 294 | impl WaylandState { 295 | pub fn new(display: Display, event_loop: &mut EventLoop, settings: LayoutSettings) -> Self { 296 | let start_time = time::Instant::now(); 297 | let display_handle = display.handle(); 298 | 299 | let mut seat_state = SeatState::new(); 300 | 301 | // A seat is a group of input devices. It typically has a pointer (mouse) and maintains a keyboard 302 | // focus and a pointer focus. 303 | let mut seat = seat_state.new_wl_seat(&display_handle, "winit"); 304 | 305 | // Add a keyboard. 306 | // FIXME: This is taken from the smallvil Smithay example - it assumes a keyboard is always 307 | // connected. We should actually track connected keyboards. 308 | seat.add_keyboard(XkbConfig::default(), 200, 25).unwrap(); 309 | // Add a mouse. 310 | // FIXME: This is taken from the smallvil Smithay example - it assumes a mouse is always 311 | // connected. We should actually track connected pointer devices. 312 | seat.add_pointer(); 313 | 314 | Self { 315 | start_time, 316 | socket_name: Self::init_wayland_listener(display, event_loop), 317 | 318 | // A two-dimensional plane on which outputs and windows can be mapped. 319 | space: Space::default(), 320 | loop_signal: event_loop.get_signal(), 321 | 322 | aquariwm_state: crate::state::AquariWm::new(settings), 323 | 324 | // A whole bunch of Smithay-related state. 325 | compositor_state: CompositorState::new::(&display_handle), 326 | xdg_shell_state: XdgShellState::new::(&display_handle), 327 | shm_state: ShmState::new::(&display_handle, Vec::new()), 328 | output_manager_state: OutputManagerState::new_with_xdg_output::(&display_handle), 329 | seat_state, 330 | data_device_state: DataDeviceState::new::(&display_handle), 331 | 332 | popup_manager: PopupManager::default(), 333 | 334 | display_handle, 335 | seat, 336 | } 337 | } 338 | 339 | fn init_wayland_listener(display: Display, event_loop: &mut EventLoop) -> OsString { 340 | let listening_socket = ListeningSocketSource::new_auto().unwrap(); 341 | 342 | let socket_name = listening_socket.socket_name().to_os_string(); 343 | 344 | let handle = event_loop.handle(); 345 | 346 | event_loop 347 | .handle() 348 | .insert_source(listening_socket, move |client_stream, _, state| { 349 | state 350 | .display_handle 351 | .insert_client(client_stream, Arc::new(ClientState::default())) 352 | .unwrap(); 353 | }) 354 | .expect("Failed to initialise the wayland event source."); 355 | 356 | handle 357 | .insert_source( 358 | Generic::new(display, Interest::READ, calloop::Mode::Level), 359 | |_, display, state| { 360 | // Safety: we don't drop the display. 361 | unsafe { display.get_mut().dispatch_clients(state).unwrap() }; 362 | 363 | Ok(calloop::PostAction::Continue) 364 | }, 365 | ) 366 | .unwrap(); 367 | 368 | socket_name 369 | } 370 | 371 | pub fn surface_under(&self, pos: Point) -> Option<(WlSurface, Point)> { 372 | self.space.element_under(pos).and_then(|(window, location)| { 373 | window 374 | .surface_under(pos - location.to_f64(), WindowSurfaceType::ALL) 375 | .map(|(surface, pos)| (surface, pos + location)) 376 | }) 377 | } 378 | } 379 | 380 | #[derive(Default)] 381 | pub struct ClientState { 382 | pub compositor_state: CompositorClientState, 383 | } 384 | 385 | impl ClientData for ClientState { 386 | fn initialized(&self, _client_id: ClientId) {} 387 | fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {} 388 | } 389 | -------------------------------------------------------------------------------- /src/display_server/x11.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use std::{env, fmt::Debug, future::Future, io, thread}; 6 | 7 | use futures::{future, try_join}; 8 | use thiserror::Error; 9 | use tracing::{event, span, Level}; 10 | use x11rb_async::{ 11 | self as x11rb, 12 | connection::Connection, 13 | protocol::{ 14 | xproto::{ 15 | self as x11, 16 | ChangeWindowAttributesAux as Attributes, 17 | ConnectionExt, 18 | CreateNotifyEvent as CreateNotify, 19 | DestroyNotifyEvent as DestroyNotify, 20 | EnterNotifyEvent as EnterNotify, 21 | EventMask, 22 | InputFocus, 23 | KeyPressEvent as KeyPress, 24 | MapRequestEvent as MapRequest, 25 | UnmapNotifyEvent as UnmapNotify, 26 | }, 27 | Event, 28 | }, 29 | rust_connection::RustConnection, 30 | }; 31 | 32 | use crate::{ 33 | display_server::{AsyncDisplayServer, DisplayServer}, 34 | layout, 35 | layout::LayoutSettings, 36 | state, 37 | }; 38 | 39 | #[cfg(feature = "testing")] 40 | mod testing; 41 | mod util; 42 | 43 | #[derive(Debug, thiserror::Error)] 44 | pub enum Error { 45 | /// An error attempting to connect to the X server. 46 | #[error(transparent)] 47 | Connect(#[from] x11rb::errors::ConnectError), 48 | /// An error occurring from an active connection to an X server. 49 | #[error(transparent)] 50 | Connection(#[from] x11rb::errors::ConnectionError), 51 | /// An error in a request's reply. 52 | #[error(transparent)] 53 | Reply(#[from] x11rb::errors::ReplyError), 54 | 55 | /// There was an error parsing a [`x11::MapState`]. 56 | #[error("There was an error attempting to parse a MapState: {0}")] 57 | MapStateParseError(#[from] ParseError), 58 | /// The window provided to [`X11::circulate_window`] was not a [floating] window. 59 | /// 60 | /// [floating]: layout::Mode::Floating 61 | #[error("The given window ({0}) is tiled, not floating")] 62 | NonFloatingWindow(x11::Window), 63 | 64 | #[error(transparent)] 65 | Io(#[from] io::Error), 66 | } 67 | 68 | pub type Result = std::result::Result; 69 | pub type ConnResult = std::result::Result; 70 | 71 | pub struct X11 { 72 | /// The connection to the X server. 73 | pub conn: RustConnection, 74 | /// The root window for the screen. 75 | pub root: x11::Window, 76 | } 77 | 78 | impl AsyncDisplayServer for X11 { 79 | type Error = Error; 80 | } 81 | 82 | impl DisplayServer for X11 { 83 | type Output = impl Future>; 84 | const NAME: &'static str = "X11"; 85 | 86 | fn run(testing: bool, settings: LayoutSettings) -> Self::Output { 87 | async move { 88 | let init_span = span!(Level::INFO, "Initialisation").entered(); 89 | 90 | // Spawn Xephyr - a nested X server - if `testing` is enabled so AquariWM runs in a testing 91 | // window. Keep it in scope so it can be killed when it is dropped. 92 | #[cfg(feature = "testing")] 93 | let _process = testing.then_some(testing::Xephyr::spawn()?); 94 | 95 | // Connect to the X server on the display specified by the `DISPLAY` env variable. 96 | let (connection, screen_num, drive) = RustConnection::connect(None).await?; 97 | 98 | // Spawn a task that reads from the connection. 99 | tokio::spawn(async move { 100 | if let Err(error) = drive.await { 101 | event!(Level::ERROR, "Error while driving the X11 connection: {}", error); 102 | } 103 | }); 104 | 105 | // Get the setup and, with it, the screen. 106 | let setup = connection.setup(); 107 | let screen = &setup.roots[screen_num]; 108 | // Get the root window of the screen. 109 | let (width, height, root) = (screen.width_in_pixels, screen.height_in_pixels, screen.root); 110 | 111 | // Wrap the connection to provide easy access to utility methods. 112 | let wm = Self { conn: connection, root }; 113 | 114 | // Attempt to register as a window manager. 115 | match wm.register_window_manager().await { 116 | Ok(_) => event!(Level::INFO, "Successfully registered window manager"), 117 | 118 | // If we failed to register the window manager, exit AquariWM. 119 | Err(error) => { 120 | event!( 121 | Level::ERROR, 122 | "Failed to register AquariWM as a window manager; another window manager is probably already \ 123 | running:\n{}", 124 | error 125 | ); 126 | 127 | return Err(error); 128 | }, 129 | } 130 | 131 | const ENTER: u8 = 0x0d; 132 | let exit_window_grab = async { 133 | wm.conn 134 | .grab_key( 135 | false, 136 | root, 137 | x11::ModMask::M4 | x11::ModMask::SHIFT, 138 | b'I', 139 | x11::GrabMode::ASYNC, 140 | x11::GrabMode::ASYNC, 141 | ) 142 | .await? 143 | .ignore_error(); 144 | 145 | >::Ok(()) 146 | }; 147 | let spawn_terminal_grab = async { 148 | wm.conn 149 | .grab_key( 150 | false, 151 | root, 152 | x11::ModMask::M4 | x11::ModMask::SHIFT, 153 | ENTER, 154 | x11::GrabMode::ASYNC, 155 | x11::GrabMode::ASYNC, 156 | ) 157 | .await? 158 | .ignore_error(); 159 | 160 | Ok(()) 161 | }; 162 | try_join!(exit_window_grab, spawn_terminal_grab)?; 163 | 164 | let mut state = state::AquariWm::with_tiling_layout_and_windows::>( 165 | 0, 166 | 0, 167 | width as u32, 168 | height as u32, 169 | wm.query_windows().await?, 170 | settings, 171 | ); 172 | 173 | if testing { 174 | event!(Level::INFO, "Testing mode enabled"); 175 | 176 | // Attempt to launch a terminal. 177 | match crate::launch_terminal() { 178 | Ok((name, _)) => event!(Level::INFO, "Launched {name:?}"), 179 | Err(error) => event!(Level::WARN, "Failed to launch terminal: {error}"), 180 | } 181 | } 182 | 183 | init_span.exit(); 184 | let event_loop_span = span!(Level::DEBUG, "Event loop"); 185 | 186 | let resize_window = |window: &_, x, y, width, height| wm.reconfigure_window(*window, x, y, width, height); 187 | 188 | loop { 189 | let _span = event_loop_span.enter(); 190 | 191 | // Flush the requests of the previous iteration, if there are any to flush. 192 | wm.conn.flush().await?; 193 | 194 | // Wait for the next event. 195 | let event = wm.conn.wait_for_event().await?; 196 | event!(Level::TRACE, "{:?}", event); 197 | 198 | match event { 199 | // Track the state of newly created windows. 200 | Event::CreateNotify(CreateNotify { window, .. }) => { 201 | state.add_window(window, state::MapState::Unmapped); 202 | 203 | state.apply_changes_async(resize_window).await?; 204 | }, 205 | // Stop tracking the state of destroyed windows. 206 | Event::DestroyNotify(DestroyNotify { window, .. }) => { 207 | state.remove_window(&window); 208 | 209 | state.apply_changes_async(resize_window).await?; 210 | }, 211 | 212 | // If a client requests to map its window, map it. 213 | Event::MapRequest(MapRequest { window, .. }) => { 214 | state.map_window(&window); 215 | 216 | try_join!( 217 | async { 218 | wm.conn.map_window(window).await?.check().await?; 219 | 220 | Ok(()) 221 | }, 222 | state.apply_changes_async(resize_window) 223 | )?; 224 | }, 225 | // If a client's window is unmapped, update state accordingly. 226 | Event::UnmapNotify(UnmapNotify { window, .. }) => { 227 | state.unmap_window(&window); 228 | 229 | state.apply_changes_async(resize_window).await?; 230 | }, 231 | 232 | // If a client requests to configure its window, honor it. For a tiling layout, this 233 | // should modify the configure request to place it in the tiling layout. 234 | Event::ConfigureRequest(request) => { 235 | wm.honor_configure_window(&request).await?; 236 | }, 237 | 238 | // If a client requests to raise or lower its window, honor it. For a tiling layout, 239 | // this should be rejected for tiled windows, as they should always be at the bottom 240 | // of the stack. 241 | Event::CirculateRequest(request) => { 242 | wm.circulate_window(&state, request.window, request.place).await?; 243 | }, 244 | 245 | // Focus a window when the cursor enters it. 246 | // TODO: move floating windows above (avoid flickering bug). 247 | // TODO: implement focus behavior setting 248 | Event::EnterNotify(EnterNotify { event, .. }) => { 249 | const CURRENT_TIME: u32 = 0; 250 | 251 | wm.conn 252 | .set_input_focus(InputFocus::PARENT, event, CURRENT_TIME) 253 | .await? 254 | .ignore_error(); 255 | }, 256 | 257 | Event::KeyPress(KeyPress { 258 | event, state, detail, .. 259 | }) => { 260 | event!( 261 | Level::INFO, 262 | "Key pressed, {event}, {state:?}, {detail}", 263 | event = event, 264 | state = state, 265 | detail = detail, 266 | ); 267 | 268 | if state == x11::KeyButMask::MOD4 | x11::KeyButMask::SHIFT { 269 | match detail { 270 | ENTER => { 271 | if let Err(error) = crate::launch_terminal() { 272 | event!(Level::WARN, "Failed to launch terminal: {error}"); 273 | } 274 | }, 275 | 276 | b'I' => { 277 | wm.conn.destroy_window(event).await?.ignore_error(); 278 | }, 279 | 280 | _ => (), 281 | } 282 | } 283 | }, 284 | 285 | _ => (), 286 | } 287 | } 288 | } 289 | } 290 | } 291 | 292 | impl X11 { 293 | /// Resizes the given `window` to the given dimensions. 294 | /// 295 | /// This is required because if the `resize_window` closure were to use an `async` 296 | /// block, it would have to be `async move` in order to move `width` and `height`, which 297 | /// would also move the `conn` (which we don't want to do). 298 | /// 299 | /// The `resize_window` closure is required because 300 | /// [`state::AquariWm::apply_changes_async`] does not expect a [`Self`] parameter. 301 | async fn reconfigure_window(&self, window: x11::Window, x: i32, y: i32, width: u32, height: u32) -> Result<()> { 302 | Ok(self 303 | .conn 304 | .configure_window( 305 | window, 306 | &x11::ConfigureWindowAux::new().x(x).y(y).width(width).height(height), 307 | ) 308 | .await? 309 | .check() 310 | .await?) 311 | } 312 | 313 | /// Circulates the given [floating] `window` in the given `direction`. 314 | /// 315 | /// # Errors 316 | /// If the given `window` is not [floating], a [`NonFloatingWindow` error] will be returned. 317 | /// This is because tiled windows are always meant to be at the bottom of the window stack, and 318 | /// tiled windows are non-overlapping: their order in the window stack does not matter. 319 | /// 320 | /// # Panics 321 | /// If the given `window` is not tracked by the given window manager `state`, this function will 322 | /// panic. This shouldn't happen if the window comes from the X server. 323 | /// 324 | /// [floating]: layout::Mode::Floating 325 | /// [`NonFloatingWindow` error]: CirculateWindowError::NonFloatingWindow 326 | pub async fn circulate_window( 327 | &self, 328 | state: &state::AquariWm, 329 | window: x11::Window, 330 | direction: Direction, 331 | ) -> Result<()> 332 | where 333 | Direction: TryInto, 334 | Direction::Error: Into, 335 | { 336 | match state.windows.get(&window) { 337 | // If it is a floating window... 338 | Some(state::WindowState { 339 | mode: layout::Mode::Floating, 340 | .. 341 | }) => { 342 | let direction: CirculateDirection = direction.try_into().map_err(|error| error.into())?; 343 | 344 | self.conn 345 | .circulate_window(direction.into(), window) 346 | .await 347 | .map_err(Error::from)? 348 | .check() 349 | .await 350 | .map_err(Error::from)?; 351 | 352 | // If we're moving the window to the bottom, then we have to move all the tiling windows 353 | // below it. 354 | if direction == CirculateDirection::MoveToBottom { 355 | let tiled_windows = state 356 | .windows 357 | .iter() 358 | .filter_map(|(window, state::WindowState { mode, .. })| match mode { 359 | layout::Mode::Tiled => Some(window), 360 | 361 | layout::Mode::Floating => None, 362 | }); 363 | 364 | // Move each tiled window to the bottom of the window stack. 365 | future::try_join_all(tiled_windows.map(|&window| async move { 366 | self.conn 367 | .circulate_window(CirculateDirection::MoveToBottom.into(), window) 368 | .await? 369 | .check() 370 | .await 371 | })) 372 | .await 373 | .map_err(Error::from)?; 374 | } 375 | 376 | Ok(()) 377 | }, 378 | 379 | // If it isn't a floating window... 380 | Some(_) => Err(Error::NonFloatingWindow(window)), 381 | 382 | // If the window manager is not tracking the window, panic. 383 | None => panic!("attempted to circulate a window which the window manager doesn't know about"), 384 | } 385 | } 386 | 387 | /// Honors a [configure window request] without modifying it. 388 | /// 389 | /// [configure window request]: x11::ConfigureRequestEvent 390 | pub async fn honor_configure_window(&self, request: &x11::ConfigureRequestEvent) -> Result<()> { 391 | self.conn 392 | .configure_window(request.window, &util::ConfigureValues::from(request).into()) 393 | .await?; 394 | 395 | Ok(()) 396 | } 397 | 398 | /// Registers for the `SUBSTRUCTURE_NOTIFY` and `SUBSTRUCTURE_REDIRECT` event masks on the root 399 | /// window; that is, register as a window manager. 400 | async fn register_window_manager(&self) -> Result<()> { 401 | let register_event_masks = self 402 | .conn 403 | .change_window_attributes( 404 | self.root, 405 | &Attributes::new().event_mask(EventMask::SUBSTRUCTURE_NOTIFY | EventMask::SUBSTRUCTURE_REDIRECT), 406 | ) 407 | .await?; 408 | 409 | register_event_masks.check().await?; 410 | Ok(()) 411 | } 412 | 413 | /// Queries the children of the `root` window and their [map states]. 414 | /// 415 | /// [map states]: state::MapState 416 | async fn query_windows(&self) -> Result> { 417 | let windows = self.conn.query_tree(self.root).await?.reply().await?.children; 418 | 419 | // Send GetWindowAttributes requests for each window. 420 | let cookies = 421 | future::try_join_all(windows.iter().map(|&window| self.conn.get_window_attributes(window))).await?; 422 | let replies = future::try_join_all(cookies.into_iter().map(|cookie| cookie.reply())).await?; 423 | 424 | let map_states = replies.into_iter().map(|reply| reply.map_state.try_into()); 425 | 426 | // Zip windows up with their map states. 427 | Ok(windows 428 | .into_iter() 429 | .zip(map_states) 430 | .map(|(window, map_state)| map_state.map(|map_state| (window, map_state))) 431 | .try_collect()?) 432 | } 433 | } 434 | 435 | #[derive(Debug, Error)] 436 | #[error("Failed to parse {value}")] 437 | pub struct ParseError { 438 | pub value: T, 439 | } 440 | 441 | impl TryFrom for state::MapState { 442 | type Error = ParseError; 443 | 444 | fn try_from(state: x11::MapState) -> std::result::Result { 445 | match state { 446 | x11::MapState::VIEWABLE | x11::MapState::UNVIEWABLE => Ok(state::MapState::Mapped), 447 | x11::MapState::UNMAPPED => Ok(state::MapState::Unmapped), 448 | 449 | other => Err(ParseError { value: u8::from(other) }), 450 | } 451 | } 452 | } 453 | 454 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] 455 | pub enum CirculateDirection { 456 | MoveToBottom, 457 | MoveToTop, 458 | } 459 | 460 | impl TryFrom for CirculateDirection { 461 | type Error = ParseError; 462 | 463 | fn try_from(direction: x11::Circulate) -> Result { 464 | match direction { 465 | x11::Circulate::LOWER_HIGHEST => Ok(Self::MoveToBottom), 466 | x11::Circulate::RAISE_LOWEST => Ok(Self::MoveToTop), 467 | 468 | other => Err(ParseError { value: other.into() }), 469 | } 470 | } 471 | } 472 | 473 | impl TryFrom for CirculateDirection { 474 | type Error = ParseError; 475 | 476 | fn try_from(direction: x11::Place) -> Result { 477 | match direction { 478 | x11::Place::ON_BOTTOM => Ok(Self::MoveToBottom), 479 | x11::Place::ON_TOP => Ok(Self::MoveToTop), 480 | 481 | other => Err(ParseError { value: other.into() }), 482 | } 483 | } 484 | } 485 | 486 | impl From for x11::Circulate { 487 | fn from(direction: CirculateDirection) -> Self { 488 | match direction { 489 | CirculateDirection::MoveToBottom => Self::LOWER_HIGHEST, 490 | CirculateDirection::MoveToTop => Self::RAISE_LOWEST, 491 | } 492 | } 493 | } 494 | -------------------------------------------------------------------------------- /src/display_server/x11/testing.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use std::{process, sync::mpsc, time::Duration}; 6 | 7 | use winit::{ 8 | event::{Event as WinitEvent, WindowEvent as WinitWindowEvent}, 9 | event_loop::EventLoopBuilder as WinitEventLoopBuilder, 10 | platform::x11::EventLoopBuilderExtX11, 11 | window::WindowBuilder as WinitWindowBuilder, 12 | }; 13 | 14 | use crate::display_server::x11::*; 15 | 16 | pub struct Xephyr(pub process::Child); 17 | 18 | impl Drop for Xephyr { 19 | fn drop(&mut self) { 20 | let Self(child) = self; 21 | 22 | child.kill().expect("Failed to kill Xephyr"); 23 | } 24 | } 25 | 26 | impl Xephyr { 27 | pub fn spawn() -> io::Result { 28 | const TESTING_DISPLAY: &str = ":1"; 29 | 30 | let (transmitter, receiver) = mpsc::channel(); 31 | 32 | // Create and run a `winit` window for `Xephyr` to use in another thread so it doesn't block the 33 | // main thread. 34 | // TODO: use tokio for this instead! 35 | thread::spawn(move || { 36 | event!(Level::DEBUG, "Initialising winit window"); 37 | 38 | let event_loop = WinitEventLoopBuilder::new().with_any_thread(true).build().unwrap(); 39 | let window = WinitWindowBuilder::new() 40 | .with_title(X11::title()) 41 | .build(&event_loop) 42 | .unwrap(); 43 | 44 | // Send the window's window ID back to the main thread so it can be supplied to `Xephyr`. 45 | transmitter.send(u64::from(window.id())).unwrap(); 46 | 47 | event_loop 48 | .run(move |event, target| { 49 | if let WinitEvent::WindowEvent { 50 | event: WinitWindowEvent::CloseRequested, 51 | .. 52 | } = event 53 | { 54 | target.exit() 55 | } 56 | }) 57 | .unwrap(); 58 | }); 59 | let window_id = receiver.recv().unwrap(); 60 | 61 | event!(Level::DEBUG, "Initialising Xephyr"); 62 | match process::Command::new("Xephyr") 63 | .arg("-resizeable") 64 | // Run `Xephyr` in the `winit` window. 65 | .args(["-parent", &window_id.to_string()]) 66 | .arg(TESTING_DISPLAY) 67 | .spawn() 68 | { 69 | Ok(process) => { 70 | // Set the `DISPLAY` env variable to `TESTING_DISPLAY`. 71 | env::set_var("DISPLAY", TESTING_DISPLAY); 72 | 73 | // Sleep for 1s to wait for Xephyr to launch. Not ideal. 74 | thread::sleep(Duration::from_secs(1)); 75 | 76 | // Spawn the `picom` compositor, if possible. 77 | let _ = process::Command::new("picom").spawn(); 78 | 79 | Ok(Self(process)) 80 | }, 81 | 82 | Err(error) => { 83 | event!(Level::ERROR, "Error while attempting to initialise Xephyr: {error}"); 84 | 85 | Err(error) 86 | }, 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/display_server/x11/util.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use x11rb_async::protocol::xproto as x11; 6 | 7 | /// Represents the values of a [`x11::ConfigureRequestEvent`] or [`x11::configure_window`] request 8 | /// as optional fields. 9 | /// 10 | /// Why this is not how they are represented in neither `xcb` nor `x11rb`, I cannot fathom. 11 | pub struct ConfigureValues { 12 | /// Configures the x-coordinate of the window. 13 | pub x: Option, 14 | /// Configures the y-coordinate of the window. 15 | pub y: Option, 16 | 17 | /// Configures the width of a window. 18 | pub width: Option, 19 | /// Configures the height of a window. 20 | pub height: Option, 21 | 22 | /// Configures the width of the window's border. 23 | pub border_width: Option, 24 | /// Configures the window's sibling. 25 | pub sibling: Option, 26 | /// Configures the window's [`StackMode`]. 27 | pub stack_mode: Option, 28 | } 29 | 30 | impl<'request> From<&'request x11::ConfigureRequestEvent> for ConfigureValues { 31 | fn from(request: &'request x11::ConfigureRequestEvent) -> Self { 32 | use x11::ConfigWindow as Mask; 33 | 34 | let mask = &request.value_mask; 35 | 36 | Self { 37 | x: mask.contains(Mask::X).then_some(request.x), 38 | y: mask.contains(Mask::Y).then_some(request.y), 39 | 40 | width: mask.contains(Mask::WIDTH).then_some(request.width), 41 | height: mask.contains(Mask::HEIGHT).then_some(request.height), 42 | 43 | border_width: mask.contains(Mask::BORDER_WIDTH).then_some(request.border_width), 44 | sibling: mask.contains(Mask::SIBLING).then_some(request.sibling), 45 | stack_mode: mask.contains(Mask::STACK_MODE).then_some(request.stack_mode), 46 | } 47 | } 48 | } 49 | 50 | impl<'values> From<&'values ConfigureValues> for x11::ConfigureWindowAux { 51 | fn from(values: &'values ConfigureValues) -> Self { 52 | Self { 53 | x: values.x.map(Into::into), 54 | y: values.y.map(Into::into), 55 | 56 | width: values.width.map(Into::into), 57 | height: values.height.map(Into::into), 58 | 59 | border_width: values.border_width.map(Into::into), 60 | sibling: values.sibling, 61 | stack_mode: values.stack_mode, 62 | } 63 | } 64 | } 65 | 66 | impl From for x11::ConfigureWindowAux { 67 | fn from(values: ConfigureValues) -> Self { 68 | Self { 69 | x: values.x.map(Into::into), 70 | y: values.y.map(Into::into), 71 | 72 | width: values.width.map(Into::into), 73 | height: values.height.map(Into::into), 74 | 75 | border_width: values.border_width.map(Into::into), 76 | sibling: values.sibling, 77 | stack_mode: values.stack_mode, 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/layout.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use std::{collections::VecDeque, fmt::Debug}; 6 | 7 | use derive_extras::builder; 8 | 9 | /// Contains `impl` blocks for types defined in [layout]. 10 | /// 11 | /// This is a separate module to keep the [layout] module file more readable. 12 | /// 13 | /// [layout]: self 14 | mod implementations; 15 | 16 | /// Default [layout managers] that come with AquariWM. 17 | /// 18 | /// [layout managers]: TilingLayoutManager 19 | pub mod managers; 20 | 21 | // This is a false positive: `derive_extras::Default` is not the same as `Default`. 22 | #[allow(unused_qualifications)] 23 | /// Controls settings used when [applying] a [tiling layout]. 24 | /// 25 | /// [applying]: GroupNode::apply_changes 26 | /// [tiling layout]: TilingLayout 27 | #[derive(Debug, PartialEq, Eq, Hash, Clone, derive_extras::Default, builder)] 28 | #[new] 29 | pub struct LayoutSettings { 30 | /// The gap between [nodes] in the [tiling layout]. 31 | /// 32 | /// [nodes]: Node 33 | /// [tiling layout]: TilingLayout 34 | #[default = 15] 35 | pub window_gap: u32, 36 | } 37 | 38 | /// Whether a window is [`Tiled`] or [`Floating`]. 39 | /// 40 | /// [`Tiled`]: Mode::Tiled 41 | /// [`Floating`]: Mode::Floating 42 | #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Default)] 43 | pub enum Mode { 44 | /// When a tiling layout is active, the window is tiled. 45 | /// 46 | /// This has no effect while no tiling layout is active, but will take effect when a tiling 47 | /// layout is activated, so it is still worth keeping track of. 48 | #[default] 49 | Tiled, 50 | 51 | /// The window is not tiled in a tiling layout, even if one is active. 52 | Floating, 53 | } 54 | 55 | /// AquariWM's current window layout manager. 56 | #[derive(Default)] 57 | pub enum CurrentLayout { 58 | /// AquariWM is currently using a tiling layout. 59 | Tiled(Box>), 60 | 61 | /// AquariWM is not currently using a tiling layout. 62 | #[default] 63 | Floating, 64 | } 65 | 66 | /// Represents the layout of [tiled] windows. 67 | /// 68 | /// The tiling layout is managed by a [layout manager]. 69 | /// 70 | /// [tiled]: Mode::Tiled 71 | /// [layout manager]: TilingLayoutManager 72 | pub struct TilingLayout { 73 | root: GroupNode, 74 | 75 | x: i32, 76 | y: i32, 77 | 78 | width: u32, 79 | height: u32, 80 | } 81 | 82 | #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] 83 | pub enum Orientation { 84 | /// [Nodes] are ordered [horizontally] from left to right. 85 | /// 86 | /// [Nodes]: Node 87 | /// [horizontally]: Axis::Horizontal 88 | LeftToRight, 89 | /// [Nodes] are ordered [vertically] from top to bottom. 90 | /// 91 | /// [Nodes]: Node 92 | /// [vertically]: Axis::Vertical 93 | TopToBottom, 94 | 95 | /// [Nodes][nodes] are ordered [horizontally] from right to left. 96 | /// 97 | /// This is a cheap way of reversing [nodes] without having to reverse the list of [nodes]. For 98 | /// all intents and purposes, a right-to-left orientation is equivalent to reversing a list of 99 | /// [nodes] that uses a [left-to-right orientation]. 100 | /// 101 | /// [nodes]: Node 102 | /// [horizontally]: Axis::Horizontal 103 | /// [left-to-right orientation]: Orientation::LeftToRight 104 | RightToLeft, 105 | /// [Nodes][nodes] are ordered [vertically] from bottom to top. 106 | /// 107 | /// This is a cheap way of reversing [nodes] without having to reverse the list of [nodes]. For 108 | /// all intents and purposes, a bottom-to-top orientation is equivalent to reversing a list of 109 | /// [nodes] that uses a [top-to-bottom orientation]. 110 | /// 111 | /// [nodes]: Node 112 | /// [vertically]: Axis::Vertical 113 | /// [top-to-bottom orientation]: Orientation::TopToBottom 114 | BottomToTop, 115 | } 116 | 117 | #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] 118 | pub enum Axis { 119 | Horizontal, 120 | Vertical, 121 | } 122 | 123 | /// Represents a node in a [layout] tree. 124 | /// 125 | /// This can either be a [group] or a [window]. 126 | /// 127 | /// [layout]: TilingLayout 128 | /// 129 | /// [group]: GroupNode 130 | /// [window]: Window 131 | #[derive(Debug, PartialEq, Eq, Hash, Clone)] 132 | pub enum Node { 133 | Group(GroupNode), 134 | Window(WindowNode), 135 | } 136 | 137 | /// Represents a group of [nodes] in a [layout] tree. 138 | /// 139 | /// [nodes]: Node 140 | /// [layout]: TilingLayout 141 | #[derive(Debug, PartialEq, Eq, Hash, Clone)] 142 | pub struct GroupNode { 143 | orientation: Orientation, 144 | 145 | children: VecDeque>, 146 | /// The total size of all nodes along the [axis] of the group. 147 | total_node_primary: u32, 148 | 149 | /// Additions to `nodes` made by the [layout manager] in the latest [`add_window`] or 150 | /// [`remove_window`] call. 151 | /// 152 | /// This is a sorted list of indexes. 153 | /// 154 | /// Additions are tracked so that nodes can be resized afterwards. This prevents multiple 155 | /// resizings per node, which is particularly important when it comes to the resized windows. 156 | /// 157 | /// [layout manager]: TilingLayoutManager 158 | /// 159 | /// [`add_window`]: TilingLayoutManager::add_window 160 | /// [`remove_window`]: TilingLayoutManager::remove_window 161 | additions: VecDeque, 162 | total_removed_primary: u32, 163 | 164 | /// The new [`orientation`] for the group set by the [layout manager] in the latest 165 | /// [`add_window`] or [`remove_window`] call. 166 | /// 167 | /// [`orientation`]: Self::orientation() 168 | /// [layout manager]: TilingLayoutManager 169 | /// 170 | /// [`add_window`]: TilingLayoutManager::add_window 171 | /// [`remove_window`]: TilingLayoutManager::remove_window 172 | new_orientation: Option, 173 | 174 | new_width: Option, 175 | new_height: Option, 176 | 177 | new_x: Option, 178 | new_y: Option, 179 | 180 | width: u32, 181 | height: u32, 182 | 183 | x: i32, 184 | y: i32, 185 | } 186 | 187 | /// Represents a [node] containing a window. 188 | /// 189 | /// [node]: Node 190 | #[derive(Debug, PartialEq, Eq, Hash, Clone)] 191 | pub struct WindowNode { 192 | window: Window, 193 | /// Whether the `window` was changed in the latest [`add_window`] or [`remove_window`] call. 194 | /// 195 | /// [`add_window`]: TilingLayoutManager::add_window 196 | /// [`remove_window`]: TilingLayoutManager::remove_window 197 | window_changed: bool, 198 | 199 | width: u32, 200 | height: u32, 201 | 202 | x: i32, 203 | y: i32, 204 | } 205 | 206 | /// Manages a [tiling layout], restructuring the layout when a window needs to be [added] or 207 | /// [removed]. 208 | /// 209 | /// `Window` is the type used to represent windows. It is a generic type parameter because different 210 | /// display server implementations use different window types. 211 | /// 212 | /// # Safety 213 | /// This is an unsafe trait because implementors must uphold guarantees regarding windows being 214 | /// added to and removed from the layout: 215 | /// - [`layout`] *must* return a shared reference to the [`TilingLayout`] managed by the layout 216 | /// manager. 217 | /// - [`layout_mut`] *must* return a mutable reference to the [`TilingLayout`] managed by the layout 218 | /// manager. 219 | /// - If [`add_window`] is called, that `window` *must* be added to the layout. 220 | /// - If [`remove_window`] is called, that `window` *must* be removed from the layout. 221 | /// - Windows *must only* be added to the layout as a result of [`add_window`] being called with 222 | /// that `window` as an argument. 223 | /// - Windows *must only* be removed from the layout as a result of [`remove_window`] being called 224 | /// with the `window` as an argument. 225 | /// 226 | /// Windows *may* be removed and then added back to the layout in the implementations of 227 | /// [`add_window`] and [`remove_window`] to restructure the layout. 228 | /// 229 | /// # Implementation notes 230 | /// This trait should be implemented for all possible window types so that it works on all AquariWM 231 | /// display server implementations. 232 | /// ``` 233 | /// # struct MyManager; 234 | /// # 235 | /// unsafe impl 236 | /// TilingLayoutManager for MyManager { 237 | /// // ... 238 | /// # fn orientation() -> Orientation 239 | /// # where 240 | /// # Self: Sized, 241 | /// # { Orientation::LeftToRight } 242 | /// # 243 | /// # fn init(layout: TilingLayout, windows: WindowsIter) -> Self 244 | /// # where 245 | /// # Self: Sized, 246 | /// # WindowsIter: IntoIterator, 247 | /// # WindowsIter::IntoIter: ExactSizeIterator, 248 | /// # { Self } 249 | /// # 250 | /// # fn layout(&self) -> &TilingLayout { unimplemented!() } 251 | /// # fn layout_mut(&self) -> &mut TilingLayout { unimplemented!() } 252 | /// # 253 | /// # fn add_window(&mut self, window: Window) {} 254 | /// # 255 | /// # fn remove_window(&mut self, window: &Window) {} 256 | /// } 257 | /// ``` 258 | /// 259 | /// [tiling layout]: TilingLayout 260 | /// 261 | /// [added]: Self::add_window 262 | /// [`add_window`]: Self::add_window 263 | /// 264 | /// [removed]: Self::remove_window 265 | /// [`remove_window`]: Self::remove_window 266 | pub unsafe trait TilingLayoutManager: Send + Sync + 'static { 267 | /// The default [orientation] for layouts created with this layout manager. 268 | /// 269 | /// [orientation]: Orientation 270 | fn orientation() -> Orientation 271 | where 272 | Self: Sized; 273 | 274 | fn init(layout: TilingLayout, windows: WindowsIter) -> Self 275 | where 276 | Self: Sized, 277 | WindowsIter: IntoIterator, 278 | WindowsIter::IntoIter: ExactSizeIterator; 279 | 280 | /// Returns a shared reference to the [layout] managed by the layout manager. 281 | /// 282 | /// [layout]: TilingLayout 283 | fn layout(&self) -> &TilingLayout; 284 | 285 | /// Returns a mutable reference to the [layout] managed by the layout manager. 286 | /// 287 | /// [layout]: TilingLayout 288 | fn layout_mut(&mut self) -> &mut TilingLayout; 289 | 290 | /// Add the given `window` to the layout. 291 | /// 292 | /// # Implementation notes 293 | /// The `window` *must* be added to the layout somewhere. The layout *may* be restructured in 294 | /// response to the new node being added to the layout. 295 | /// 296 | /// See the [trait documentation] for more information. 297 | /// 298 | /// [trait documentation]: Self 299 | fn add_window(&mut self, window: Window); 300 | 301 | /// Remove the given `window` from the layout. 302 | /// 303 | /// # Implementation notes 304 | /// The `window` *must* be removed from the layout. The layout *may* be restructured in response 305 | /// to that node being removed from the layout. 306 | /// 307 | /// See the [trait documentation] for more information. 308 | /// 309 | /// [trait documentation]: Self 310 | fn remove_window(&mut self, window: &Window); 311 | } 312 | -------------------------------------------------------------------------------- /src/layout/implementations.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use std::{ 6 | borrow::{Borrow, BorrowMut}, 7 | mem, 8 | ops::{Deref, DerefMut, Index, IndexMut}, 9 | }; 10 | 11 | use super::*; 12 | 13 | mod iter; 14 | mod node_changes; 15 | 16 | impl CurrentLayout { 17 | /// Creates a new [tiled layout] using the given layout `Manager` type parameter. 18 | /// 19 | /// [tiled layout]: Self::Tiled 20 | #[inline(always)] 21 | pub(crate) fn new_tiled(x: i32, y: i32, width: u32, height: u32, settings: &LayoutSettings) -> Self 22 | where 23 | Manager: TilingLayoutManager, 24 | { 25 | Self::tiled_with_windows::>(x, y, width, height, std::iter::empty(), settings) 26 | } 27 | 28 | /// Creates a new [tiled layout] using the given layout `Manager` type parameter containing the 29 | /// given `windows`. 30 | /// 31 | /// [tiled layout]: Self::Tiled 32 | #[inline] 33 | pub(crate) fn tiled_with_windows( 34 | x: i32, 35 | y: i32, 36 | width: u32, 37 | height: u32, 38 | windows: Windows, 39 | settings: &LayoutSettings, 40 | ) -> Self 41 | where 42 | Manager: TilingLayoutManager, 43 | Windows: IntoIterator, 44 | Windows::IntoIter: ExactSizeIterator, 45 | { 46 | let layout = TilingLayout::new(Manager::orientation(), x, y, width, height, settings); 47 | 48 | Self::Tiled(Box::new(Manager::init(layout, windows))) 49 | } 50 | } 51 | 52 | impl TilingLayout { 53 | /// Creates an empty layout of the given `orientation`. 54 | #[inline] 55 | pub(crate) const fn new( 56 | orientation: Orientation, 57 | x: i32, 58 | y: i32, 59 | width: u32, 60 | height: u32, 61 | settings: &LayoutSettings, 62 | ) -> Self { 63 | let padding = settings.window_gap; 64 | 65 | Self { 66 | x, 67 | y, 68 | 69 | width, 70 | height, 71 | 72 | root: GroupNode::with( 73 | orientation, 74 | x + (padding as i32), 75 | y + (padding as i32), 76 | width - (2 * padding), 77 | height - (2 * padding), 78 | ), 79 | } 80 | } 81 | 82 | /// Updates the tiling layout with the given `settings`. 83 | /// 84 | /// Please note that for the nodes in the layout to be updated, [state::AquariWm::apply_changes] 85 | #[cfg_attr(feature = "async", doc = "or [state::AquariWm::apply_changes_async]")] 86 | /// must be called. 87 | /// 88 | /// [state::AquariWm::apply]: crate::state::AquariWm::apply_changes 89 | #[cfg_attr( 90 | feature = "async", 91 | doc = "[state::AquariWm::apply_changes_async]: crate::state::AquariWm::apply_changes_async" 92 | )] 93 | pub(crate) fn update_settings(&mut self, settings: &LayoutSettings) { 94 | let padding = settings.window_gap; 95 | 96 | self.root.new_x = Some(self.x + (padding as i32)); 97 | self.root.new_y = Some(self.y + (padding as i32)); 98 | 99 | self.root.new_width = Some(self.width - (2 * padding)); 100 | self.root.new_height = Some(self.height - (2 * padding)); 101 | } 102 | } 103 | 104 | impl Borrow> for TilingLayout { 105 | #[inline(always)] 106 | fn borrow(&self) -> &GroupNode { 107 | self 108 | } 109 | } 110 | 111 | impl BorrowMut> for TilingLayout { 112 | #[inline(always)] 113 | fn borrow_mut(&mut self) -> &mut GroupNode { 114 | self 115 | } 116 | } 117 | 118 | impl Deref for TilingLayout { 119 | type Target = GroupNode; 120 | 121 | #[inline(always)] 122 | fn deref(&self) -> &Self::Target { 123 | &self.root 124 | } 125 | } 126 | 127 | impl DerefMut for TilingLayout { 128 | #[inline(always)] 129 | fn deref_mut(&mut self) -> &mut Self::Target { 130 | &mut self.root 131 | } 132 | } 133 | 134 | impl Node { 135 | #[inline(always)] 136 | pub const fn is_window(&self) -> bool { 137 | matches!(self, Self::Window(_)) 138 | } 139 | 140 | #[inline(always)] 141 | pub const fn is_group(&self) -> bool { 142 | matches!(self, Self::Group(_)) 143 | } 144 | 145 | #[inline] 146 | pub fn unwrap_window(self) -> WindowNode { 147 | match self { 148 | Self::Window(window) => window, 149 | _ => panic!("expected a window node"), 150 | } 151 | } 152 | 153 | #[inline] 154 | pub fn unwrap_window_ref(&self) -> &WindowNode { 155 | match self { 156 | Self::Window(window) => window, 157 | _ => panic!("expected a window node"), 158 | } 159 | } 160 | 161 | #[inline] 162 | pub fn unwrap_window_mut(&mut self) -> &mut WindowNode { 163 | match self { 164 | Self::Window(window) => window, 165 | _ => panic!("expected a window node"), 166 | } 167 | } 168 | 169 | #[inline] 170 | pub fn unwrap_group(self) -> GroupNode { 171 | match self { 172 | Self::Group(group) => group, 173 | _ => panic!("expected a group node"), 174 | } 175 | } 176 | 177 | #[inline] 178 | pub fn unwrap_group_ref(&self) -> &GroupNode { 179 | match self { 180 | Self::Group(group) => group, 181 | _ => panic!("expected a group node"), 182 | } 183 | } 184 | 185 | #[inline] 186 | pub fn unwrap_group_mut(&mut self) -> &mut GroupNode { 187 | match self { 188 | Self::Group(group) => group, 189 | _ => panic!("expected a group node"), 190 | } 191 | } 192 | 193 | /// Creates a new [`Node::Window`] with a [window node] wrapping the given `window`. 194 | /// 195 | /// This is a convenience function for creating a window node with 196 | /// Node::Window(WindowNode::[new]\(window)). 197 | /// 198 | /// [window node]: WindowNode 199 | /// [new]: WindowNode::new 200 | #[inline(always)] 201 | pub(crate) const fn new_window(window: Window) -> Self { 202 | Self::Window(WindowNode::new(window)) 203 | } 204 | 205 | /// Creates a new [`Node::Window`] with a [window node] wrapping the given `window` with the 206 | /// given coordinates and dimensions. 207 | /// 208 | /// This is a convenience function for creating a window node with 209 | /// [Node]::[Window]\([WindowNode]::[with]\(window, x, y, width, height)). 210 | /// 211 | /// [window node]: WindowNode 212 | /// [Window]: Self::Window 213 | /// [with]: WindowNode::with 214 | #[inline(always)] 215 | pub(crate) const fn new_window_with(window: Window, x: i32, y: i32, width: u32, height: u32) -> Self { 216 | Self::Window(WindowNode::with(window, x, y, width, height)) 217 | } 218 | 219 | /// Creates a new [`Node::Group`] with a [group node] of the given `orientation`. 220 | /// 221 | /// This is a convenience function for creating a group node with 222 | /// [Node]::[Group]\([GroupNode]::[new]\(orientation)). 223 | /// 224 | /// [group node]: GroupNode 225 | /// [Group]: Self::Group 226 | /// [new]: GroupNode::new 227 | #[inline(always)] 228 | pub(crate) const fn new_group(orientation: Orientation) -> Self { 229 | Self::Group(GroupNode::new(orientation)) 230 | } 231 | 232 | /// Creates a new [`Node::Group`] with a [group node] of the given `orientation` with the given 233 | /// coordinates and dimensions. 234 | /// 235 | /// This is a convenience function for creating a group node with 236 | /// [Node]::[Group]\([GroupNode]::[with]\(orientation, x, y, width, height)). 237 | /// 238 | /// [group node]: GroupNode 239 | /// [Group]: Self::Group 240 | /// [with]: GroupNode::with 241 | #[inline(always)] 242 | pub(crate) const fn new_group_with(orientation: Orientation, x: i32, y: i32, width: u32, height: u32) -> Self { 243 | Self::Group(GroupNode::with(orientation, x, y, width, height)) 244 | } 245 | 246 | #[inline] 247 | pub(crate) const fn x(&self) -> i32 { 248 | match self { 249 | Self::Window(node) => node.x, 250 | Self::Group(node) => node.x, 251 | } 252 | } 253 | 254 | pub(crate) const fn y(&self) -> i32 { 255 | match self { 256 | Self::Window(node) => node.y, 257 | Self::Group(node) => node.y, 258 | } 259 | } 260 | 261 | /// Returns the width of the node. 262 | #[inline] 263 | pub(crate) const fn width(&self) -> u32 { 264 | match self { 265 | Self::Window(node) => node.width, 266 | Self::Group(node) => node.width, 267 | } 268 | } 269 | 270 | /// Returns the height of the node. 271 | #[inline] 272 | pub(crate) const fn height(&self) -> u32 { 273 | match self { 274 | Self::Window(node) => node.height, 275 | Self::Group(node) => node.height, 276 | } 277 | } 278 | 279 | #[inline] 280 | pub(crate) fn set_x(&mut self, x: i32) { 281 | match self { 282 | Self::Window(node) => node.x = x, 283 | Self::Group(node) => node.x = x, 284 | } 285 | } 286 | 287 | #[inline] 288 | pub(crate) fn set_y(&mut self, y: i32) { 289 | match self { 290 | Self::Window(node) => node.y = y, 291 | Self::Group(node) => node.y = y, 292 | } 293 | } 294 | 295 | /// Sets the `width` of the node. 296 | #[inline] 297 | pub(crate) fn set_width(&mut self, width: u32) { 298 | match self { 299 | Self::Window(node) => node.width = width, 300 | Self::Group(node) => node.width = width, 301 | } 302 | } 303 | 304 | /// Sets the `height` of the node. 305 | #[inline] 306 | pub(crate) fn set_height(&mut self, height: u32) { 307 | match self { 308 | Self::Window(node) => node.height = height, 309 | Self::Group(node) => node.height = height, 310 | } 311 | } 312 | 313 | /// Sets the primary axis of the node. 314 | /// 315 | /// The primary axis is the one that affects the node's size within its group. 316 | #[inline] 317 | pub(crate) const fn primary_dimension(&self, axis: Axis) -> u32 { 318 | match axis { 319 | Axis::Horizontal => self.width(), 320 | Axis::Vertical => self.height(), 321 | } 322 | } 323 | 324 | /// Sets the secondary axis of the node. 325 | /// 326 | /// The secondary axis is the one that is only affected by the size of the node's group. 327 | #[inline] 328 | pub(crate) const fn secondary_dimension(&self, axis: Axis) -> u32 { 329 | match axis { 330 | Axis::Horizontal => self.height(), 331 | Axis::Vertical => self.width(), 332 | } 333 | } 334 | 335 | /// Sets the [`primary`] axis of the node. 336 | /// 337 | /// [`primary`]: Self::primary_dimension 338 | #[inline] 339 | pub(crate) fn set_primary_dimension(&mut self, primary: u32, axis: Axis) { 340 | match axis { 341 | Axis::Horizontal => self.set_width(primary), 342 | Axis::Vertical => self.set_height(primary), 343 | } 344 | } 345 | 346 | /// Sets the [`secondary`] axis of the node. 347 | /// 348 | /// [`secondary`]: Self::secondary_dimension 349 | #[inline] 350 | pub(crate) fn set_secondary_dimension(&mut self, secondary: u32, axis: Axis) { 351 | match axis { 352 | Axis::Horizontal => self.set_height(secondary), 353 | Axis::Vertical => self.set_width(secondary), 354 | } 355 | } 356 | 357 | #[inline] 358 | pub(crate) fn set_primary_coord(&mut self, primary: i32, axis: Axis) { 359 | match axis { 360 | Axis::Horizontal => self.set_x(primary), 361 | Axis::Vertical => self.set_y(primary), 362 | } 363 | } 364 | 365 | pub(crate) fn set_secondary_coord(&mut self, secondary: i32, axis: Axis) { 366 | match axis { 367 | Axis::Horizontal => self.set_y(secondary), 368 | Axis::Vertical => self.set_x(secondary), 369 | } 370 | } 371 | } 372 | 373 | impl WindowNode { 374 | /// Creates a window node of the given `window`. 375 | /// 376 | /// It is useful to create a window node with no coordinates or size if they are meant to be 377 | /// filled in later. 378 | #[inline(always)] 379 | pub(crate) const fn new(window: Window) -> Self { 380 | Self::with(window, 0, 0, 0, 0) 381 | } 382 | 383 | /// Creates a window node of the given `window` with the given coordinates and dimensions. 384 | #[inline(always)] 385 | pub(crate) const fn with(window: Window, x: i32, y: i32, width: u32, height: u32) -> Self { 386 | Self { 387 | window, 388 | window_changed: false, 389 | 390 | x, 391 | y, 392 | 393 | width, 394 | height, 395 | } 396 | } 397 | 398 | /// Returns a reference to the window node's window. 399 | #[inline(always)] 400 | pub const fn window(&self) -> &Window { 401 | &self.window 402 | } 403 | 404 | /// Sets the window node's window to the given `window`. 405 | #[inline] 406 | pub fn set_window(&mut self, window: Window) { 407 | self.window_changed = true; 408 | 409 | self.window = window; 410 | } 411 | 412 | /// Replaces the window node's window with the given `window`, returning the previous one. 413 | #[inline] 414 | pub fn replace_window(&mut self, window: Window) -> Window { 415 | self.window_changed = true; 416 | 417 | mem::replace(&mut self.window, window) 418 | } 419 | 420 | /// Returns the window node's window. 421 | #[inline(always)] 422 | pub fn into_window(self) -> Window { 423 | self.window 424 | } 425 | } 426 | 427 | impl Deref for WindowNode { 428 | type Target = Window; 429 | 430 | #[inline(always)] 431 | fn deref(&self) -> &Self::Target { 432 | &self.window 433 | } 434 | } 435 | 436 | impl DerefMut for WindowNode { 437 | #[inline(always)] 438 | fn deref_mut(&mut self) -> &mut Self::Target { 439 | &mut self.window 440 | } 441 | } 442 | 443 | impl Borrow for WindowNode { 444 | #[inline(always)] 445 | fn borrow(&self) -> &Window { 446 | self 447 | } 448 | } 449 | 450 | impl BorrowMut for WindowNode { 451 | #[inline(always)] 452 | fn borrow_mut(&mut self) -> &mut Window { 453 | self 454 | } 455 | } 456 | 457 | impl GroupNode { 458 | /// Creates an empty group of the given `orientation` and coordinates with dimensions of 0 by 0. 459 | /// 460 | /// It is useful to create a group with no size if that size is intended to be filled in later. 461 | #[inline(always)] 462 | pub(crate) const fn new(orientation: Orientation) -> Self { 463 | Self::with(orientation, 0, 0, 0, 0) 464 | } 465 | 466 | /// Creates an empty group of the given `orientation` and dimensions. 467 | #[inline] 468 | pub(crate) const fn with(orientation: Orientation, x: i32, y: i32, width: u32, height: u32) -> Self { 469 | Self { 470 | orientation, 471 | 472 | children: VecDeque::new(), 473 | total_node_primary: 0, 474 | 475 | additions: VecDeque::new(), 476 | total_removed_primary: 0, 477 | 478 | new_orientation: None, 479 | 480 | new_x: None, 481 | new_y: None, 482 | 483 | new_width: None, 484 | new_height: None, 485 | 486 | x, 487 | y, 488 | 489 | width, 490 | height, 491 | } 492 | } 493 | 494 | /// Returns the number of child [nodes] in the group. 495 | /// 496 | /// This does not include further descendents of the group; a group with a single child group 497 | /// that itself has children is still going to have a `len` of 1. 498 | /// 499 | /// [nodes]: Node 500 | #[inline(always)] 501 | pub fn len(&self) -> usize { 502 | self.children.len() 503 | } 504 | 505 | /// Returns [`true`] if there are no [nodes] in the group. 506 | /// 507 | /// [nodes]: Node 508 | #[inline(always)] 509 | pub fn is_empty(&self) -> bool { 510 | self.children.is_empty() 511 | } 512 | 513 | /// Returns the first [node], or [`None`] if the group is empty. 514 | /// 515 | /// [node]: Node 516 | #[inline] 517 | pub fn first(&self) -> Option<&Node> { 518 | match self.len() { 519 | 0 => None, 520 | _ => Some(&self[0]), 521 | } 522 | } 523 | 524 | /// Returns the last [node], or [`None`] if the group is empty. 525 | /// 526 | /// [node]: Node 527 | #[inline] 528 | pub fn last(&self) -> Option<&Node> { 529 | match self.len() { 530 | 0 => None, 531 | len => Some(&self[len - 1]), 532 | } 533 | } 534 | 535 | /// Returns a mutable reference to the first [node], or [`None`] if the group is empty. 536 | /// 537 | /// [node]: Node 538 | #[inline] 539 | pub fn first_mut(&mut self) -> Option<&mut Node> { 540 | match self.len() { 541 | 0 => None, 542 | _ => Some(&mut self[0]), 543 | } 544 | } 545 | 546 | /// Returns a mutable reference to the last [node], or [`None`] if the group is empty. 547 | /// 548 | /// [node]: Node 549 | #[inline] 550 | pub fn last_mut(&mut self) -> Option<&mut Node> { 551 | match self.len() { 552 | 0 => None, 553 | len => Some(&mut self[len - 1]), 554 | } 555 | } 556 | 557 | /// Returns the [node] at the given `index`, or [`None`] if the `index` is out of bounds. 558 | /// 559 | /// [node]: Node 560 | pub fn get(&self, index: usize) -> Option<&Node> { 561 | if index < self.children.len() { 562 | let index = if !self.orientation().reversed() { 563 | index 564 | } else { 565 | let last = self.children.len() - 1; 566 | last - index 567 | }; 568 | 569 | Some(&self.children[index]) 570 | } else { 571 | None 572 | } 573 | } 574 | 575 | /// Returns a mutable reference to the [node] at the given `index`, or [`None`] if the `index` 576 | /// is out of bounds. 577 | /// 578 | /// [node]: Node 579 | pub fn get_mut(&mut self, index: usize) -> Option<&mut Node> { 580 | if index < self.children.len() { 581 | let index = if !self.orientation().reversed() { 582 | index 583 | } else { 584 | let last = self.children.len() - 1; 585 | last - index 586 | }; 587 | 588 | Some(&mut self.children[index]) 589 | } else { 590 | None 591 | } 592 | } 593 | 594 | #[inline] 595 | pub(crate) const fn primary_coord(&self) -> i32 { 596 | match self.orientation().axis() { 597 | Axis::Horizontal => self.x, 598 | Axis::Vertical => self.y, 599 | } 600 | } 601 | 602 | #[inline] 603 | pub(crate) const fn secondary_coord(&self) -> i32 { 604 | match self.orientation().axis() { 605 | Axis::Horizontal => self.y, 606 | Axis::Vertical => self.x, 607 | } 608 | } 609 | 610 | #[inline] 611 | pub(crate) const fn primary_dimension(&self) -> u32 { 612 | match self.orientation().axis() { 613 | Axis::Horizontal => self.width, 614 | Axis::Vertical => self.height, 615 | } 616 | } 617 | 618 | #[inline] 619 | pub(crate) const fn secondary_dimension(&self) -> u32 { 620 | match self.orientation().axis() { 621 | Axis::Horizontal => self.height, 622 | Axis::Vertical => self.width, 623 | } 624 | } 625 | 626 | #[inline] 627 | pub(crate) fn set_width(&mut self, width: u32) { 628 | self.new_width = Some(width); 629 | } 630 | 631 | #[inline] 632 | pub(crate) fn set_height(&mut self, height: u32) { 633 | self.new_height = Some(height); 634 | } 635 | 636 | #[inline] 637 | pub(crate) fn set_primary(&mut self, primary: u32) { 638 | match self.orientation().axis() { 639 | Axis::Horizontal => self.set_width(primary), 640 | Axis::Vertical => self.set_height(primary), 641 | } 642 | } 643 | 644 | #[inline] 645 | pub(crate) fn set_secondary(&mut self, secondary: u32) { 646 | match self.orientation().axis() { 647 | Axis::Horizontal => self.set_height(secondary), 648 | Axis::Vertical => self.set_width(secondary), 649 | } 650 | } 651 | } 652 | 653 | impl Index for GroupNode { 654 | type Output = Node; 655 | 656 | fn index(&self, index: usize) -> &Self::Output { 657 | if !self.orientation().reversed() { 658 | &self.children[index] 659 | } else { 660 | let last = self.children.len() - 1; 661 | let index = last - index; 662 | 663 | &self.children[index] 664 | } 665 | } 666 | } 667 | 668 | impl IndexMut for GroupNode { 669 | fn index_mut(&mut self, index: usize) -> &mut Self::Output { 670 | if !self.orientation().reversed() { 671 | &mut self.children[index] 672 | } else { 673 | let last = self.children.len() - 1; 674 | let index = last - index; 675 | 676 | &mut self.children[index] 677 | } 678 | } 679 | } 680 | 681 | impl Orientation { 682 | /// Returns whether this orientation is *reversed*. 683 | /// 684 | /// A reversed orientation has the effect of flipping a [group] of [nodes] without having to 685 | /// reverse the actual list of nodes. In a reversed orientation, for example, swapping a [node] 686 | /// with the next node will actually swap that node with the previous [node in the list - this 687 | /// mimics the effect as if the list of [nodes] had been reversed and the node was swapped with 688 | /// the next node in the list. 689 | /// 690 | /// The reversed orientations are [right-to-left] and [bottom-to-top]. 691 | /// 692 | /// [nodes]: Node 693 | /// [node]: Node 694 | /// 695 | /// [right-to-left]: Orientation::RightToLeft 696 | /// [bottom-to-top]: Orientation::BottomToTop 697 | #[inline] 698 | pub const fn reversed(&self) -> bool { 699 | match self { 700 | Self::LeftToRight | Self::TopToBottom => false, 701 | Self::RightToLeft | Self::BottomToTop => true, 702 | } 703 | } 704 | 705 | /// Returns the [axis] of this orientation. 706 | /// 707 | /// [Left-to-right] and [right-to-left] orientations have a [`Horizontal` axis]. 708 | /// [Top-to-bottom] and [bottom-to-top] orientations have a [`Vertical` axis]. 709 | /// 710 | /// [axis]: Axis 711 | /// 712 | /// [Left-to-right]: Orientation::LeftToRight 713 | /// [right-to-left]: Orientation::RightToLeft 714 | /// 715 | /// [Top-to-bottom]: Orientation::TopToBottom 716 | /// [bottom-to-top]: Orientation::BottomToTop 717 | /// 718 | /// [`Horizontal` axis]: Axis::Horizontal 719 | /// [`Vertical` axis]: Axis::Vertical 720 | #[inline] 721 | pub const fn axis(&self) -> Axis { 722 | match self { 723 | Self::LeftToRight | Self::RightToLeft => Axis::Horizontal, 724 | Self::TopToBottom | Self::BottomToTop => Axis::Vertical, 725 | } 726 | } 727 | 728 | /// Returns this orientation rotated by the given number of `rotations`. 729 | /// 730 | /// A positive number of rotations will rotate the orientation clockwise, while a negative 731 | /// number of rotations will rotate the orientation counter-clockwise. 732 | pub fn rotated_by(&self, rotations: i32) -> Self { 733 | let current = match self { 734 | Orientation::LeftToRight => 0, 735 | Orientation::TopToBottom => 1, 736 | Orientation::RightToLeft => 2, 737 | Orientation::BottomToTop => 3, 738 | }; 739 | 740 | match (current + rotations).rem_euclid(4) { 741 | 0 => Orientation::LeftToRight, 742 | 1 => Orientation::TopToBottom, 743 | 2 => Orientation::RightToLeft, 744 | 3 => Orientation::BottomToTop, 745 | 746 | _ => unreachable!(".rem_euclid(4) returns a value within 0..4"), 747 | } 748 | } 749 | } 750 | 751 | impl Axis { 752 | /// Returns the other axis. 753 | /// 754 | /// For [`Horizontal`], [`Vertical`] is returned. For [`Vertical`], [`Horizontal`] is returned. 755 | /// 756 | /// [`Horizontal`]: Self::Horizontal 757 | /// [`Vertical`]: Self::Vertical 758 | #[inline] 759 | pub const fn flipped(&self) -> Axis { 760 | match self { 761 | Self::Horizontal => Self::Vertical, 762 | Self::Vertical => Self::Horizontal, 763 | } 764 | } 765 | } 766 | -------------------------------------------------------------------------------- /src/layout/implementations/iter.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use std::{collections::vec_deque, iter::FusedIterator}; 6 | 7 | use super::*; 8 | 9 | /// A wrapper around either `Iter` or [Rev]\. 10 | /// 11 | /// [Rev]: std::iter::Rev 12 | enum GroupIterator { 13 | Normal(Iter), 14 | Rev(std::iter::Rev), 15 | } 16 | 17 | /// A borrowing iterator over the children of a [group]. 18 | /// 19 | /// This is returned by [`GroupNode::iter()`]. 20 | /// 21 | /// [group]: GroupNode 22 | pub struct Iter<'group, Window> { 23 | iter: GroupIterator>>, 24 | } 25 | 26 | /// An owning iterator over the children of a [group]. 27 | /// 28 | /// This is returned by [`GroupNode::into_iter()`]. 29 | /// 30 | /// [group]: GroupNode 31 | pub struct IntoIter { 32 | into_iter: GroupIterator>>, 33 | } 34 | 35 | /// A mutably borrowing iterator over the children of a [group]. 36 | /// 37 | /// This is returned by [`GroupNode::iter_mut()`]. 38 | /// 39 | /// [group]: GroupNode 40 | pub struct IterMut<'group, Window> { 41 | iter_mut: GroupIterator>>, 42 | } 43 | 44 | macro_rules! impl_iterator { 45 | ( 46 | for $($Iter:ident<$($lt:lifetime $($mut:ident)?,)? $Window:ident> { $iter:ident }),+$(,)? 47 | $(; $($tt:tt)*)? 48 | ) => { 49 | impl_iterator! { 50 | $( 51 | for $Iter<$($lt $($mut)?,)? $Window> { $iter } { 52 | fn into_iter(self) -> Self::IntoIter; 53 | } 54 | )+ 55 | 56 | $($($tt)*)? 57 | } 58 | }; 59 | 60 | ( 61 | // $iter is both the iterator field name and the VecDeque iterator method, so those must match 62 | for $Iter:ident<$($lt:lifetime $($mut:ident)?,)? $Window:ident> { $iter:ident }$(,)? { 63 | $(#[$inner:meta])* 64 | fn $into_iter:ident(self) -> Self::$IntoIter:ident; 65 | } 66 | 67 | $($($tt:tt)+)? 68 | ) => { 69 | $(impl_iterator! { 70 | $($tt)+ 71 | })? 72 | 73 | //////////////////////////////////////////////////////////////////////////////////////////// 74 | // $Iter impls 75 | //////////////////////////////////////////////////////////////////////////////////////////// 76 | 77 | impl<$($lt,)? $Window> $Iter<$($lt,)? $Window> { 78 | fn new(group: $(&$lt $($mut)?)? GroupNode<$Window>) -> Self { 79 | Self { 80 | $iter: if !group.orientation().reversed() { 81 | GroupIterator::Normal(group.children.$iter()) 82 | } else { 83 | GroupIterator::Rev(group.children.$iter().rev()) 84 | }, 85 | } 86 | } 87 | } 88 | 89 | impl<$($lt,)? $Window> IntoIterator for $(&$lt $($mut)?)? GroupNode<$Window> { 90 | type Item = $(&$lt $($mut)?)? Node<$Window>; 91 | type $IntoIter = $Iter<$($lt,)? $Window>; 92 | 93 | $(#[$inner])* 94 | fn $into_iter(self) -> Self::$IntoIter { 95 | $Iter::new(self) 96 | } 97 | } 98 | 99 | //////////////////////////////////////////////////////////////////////////////////////////// 100 | // Iterator impls 101 | //////////////////////////////////////////////////////////////////////////////////////////// 102 | 103 | impl<$($lt,)? $Window> Iterator for $Iter<$($lt,)? $Window> { 104 | type Item = $(&$lt $($mut)?)? Node<$Window>; 105 | 106 | #[inline] 107 | fn next(&mut self) -> Option { 108 | match &mut self.$iter { 109 | GroupIterator::Normal($iter) => $iter.next(), 110 | GroupIterator::Rev($iter) => $iter.next(), 111 | } 112 | } 113 | 114 | #[inline] 115 | fn size_hint(&self) -> (usize, Option) { 116 | match &self.$iter { 117 | GroupIterator::Normal($iter) => $iter.size_hint(), 118 | GroupIterator::Rev($iter) => $iter.size_hint(), 119 | } 120 | } 121 | 122 | #[inline] 123 | fn fold(self, accum: Acc, f: F) -> Acc 124 | where 125 | F: FnMut(Acc, Self::Item) -> Acc, 126 | { 127 | match self.$iter { 128 | GroupIterator::Normal($iter) => $iter.fold(accum, f), 129 | GroupIterator::Rev($iter) => $iter.fold(accum, f), 130 | } 131 | } 132 | 133 | #[inline] 134 | fn last(self) -> Option { 135 | match self.$iter { 136 | GroupIterator::Normal($iter) => $iter.last(), 137 | GroupIterator::Rev($iter) => $iter.last(), 138 | } 139 | } 140 | } 141 | 142 | impl<$($lt,)? $Window> DoubleEndedIterator for $Iter<$($lt,)? $Window> { 143 | #[inline] 144 | fn next_back(&mut self) -> Option { 145 | match &mut self.$iter { 146 | GroupIterator::Normal($iter) => $iter.next_back(), 147 | GroupIterator::Rev($iter) => $iter.next_back(), 148 | } 149 | } 150 | 151 | #[inline] 152 | fn rfold(self, accum: Acc, f: F) -> Acc 153 | where 154 | F: FnMut(Acc, Self::Item) -> Acc, 155 | { 156 | match self.$iter { 157 | GroupIterator::Normal($iter) => $iter.rfold(accum, f), 158 | GroupIterator::Rev($iter) => $iter.rfold(accum, f), 159 | } 160 | } 161 | } 162 | 163 | impl<$($lt,)? $Window> ExactSizeIterator for $Iter<$($lt,)? $Window> { 164 | #[inline] 165 | fn len(&self) -> usize { 166 | match &self.$iter { 167 | GroupIterator::Normal($iter) => $iter.len(), 168 | GroupIterator::Rev($iter) => $iter.len(), 169 | } 170 | } 171 | } 172 | 173 | impl<$($lt,)? $Window> FusedIterator for $Iter<$($lt,)? $Window> {} 174 | }; 175 | } 176 | 177 | impl_iterator! { 178 | for Iter<'group, Window> { iter }; 179 | 180 | for IntoIter { into_iter } { 181 | /// Returns an owning iterator over the direct children of this group. 182 | fn into_iter(self) -> Self::IntoIter; 183 | } 184 | 185 | for IterMut<'group mut, Window> { iter_mut }; 186 | } 187 | 188 | impl GroupNode { 189 | /// Returns a borrowing iterator over the direct children of this group. 190 | pub fn iter(&self) -> <&Self as IntoIterator>::IntoIter { 191 | self.into_iter() 192 | } 193 | 194 | /// Returns a mutably borrowing iterator over the direct children of this group. 195 | pub fn iter_mut(&mut self) -> <&mut Self as IntoIterator>::IntoIter { 196 | self.into_iter() 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/layout/implementations/node_changes.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use std::mem; 6 | 7 | use truncate_integer::Shrink; 8 | 9 | use super::*; 10 | 11 | impl GroupNode { 12 | /// Rotates the group's [`orientation`] by the given number of `rotations`. 13 | /// 14 | /// A positive number of rotations will rotate the [`orientation`] clockwise, while a negative 15 | /// number of rotations will rotate the [`orientation`] counter-clockwise. 16 | /// 17 | /// # See also 18 | /// - [`set_orientation`](Self::set_orientation) 19 | /// 20 | /// [`orientation`]: Self::orientation() 21 | pub fn rotate_by(&mut self, rotations: i32) { 22 | self.set_orientation(self.orientation().rotated_by(rotations)); 23 | } 24 | 25 | /// Returns the group's [orientation]. 26 | /// 27 | /// # See also 28 | /// - [`set_orientation`](Self::set_orientation) 29 | /// - [`rotate_by`](Self::rotate_by) 30 | /// 31 | /// [orientation]: Orientation 32 | // NOTE: This will return the `new_orientation` if it is set - for the current orientation 33 | // before that is applied, use the `self.orientation` field. 34 | pub const fn orientation(&self) -> Orientation { 35 | if let Some(orientation) = self.new_orientation { 36 | orientation 37 | } else { 38 | self.orientation 39 | } 40 | } 41 | 42 | /// Sets the group's [`orientation`]. 43 | /// 44 | /// # See also 45 | /// - [`rotate_by`](Self::rotate_by) 46 | /// 47 | /// [`orientation`]: Self::orientation() 48 | pub fn set_orientation(&mut self, new: Orientation) { 49 | self.new_orientation = Some(new); 50 | } 51 | } 52 | 53 | impl GroupNode { 54 | /// Removes the [node] at the given `index` from the group. 55 | /// 56 | /// [node]: Node 57 | pub fn remove(&mut self, index: usize) -> Option> { 58 | if index < self.children.len() { 59 | let index = if !self.orientation.reversed() { 60 | index 61 | } else { 62 | let last = self.children.len() - 1; 63 | last - index 64 | }; 65 | 66 | let node = self.children.remove(index); 67 | 68 | if let Some(node) = &node { 69 | self.track_remove(index); 70 | 71 | self.total_removed_primary += node.primary_dimension(self.orientation.axis()); 72 | } 73 | 74 | node 75 | } else { 76 | None 77 | } 78 | } 79 | 80 | /// Removes the [node] at the end of the group. 81 | /// 82 | /// [node]: Node 83 | pub fn pop_back(&mut self) -> Option> { 84 | match self.children.len() { 85 | // `children` is empty 86 | 0 => None, 87 | // `children` is not empty 88 | _ => { 89 | if !self.orientation.reversed() { 90 | let node = self.children.pop_back(); 91 | 92 | if node.is_some() { 93 | self.track_pop_back(); 94 | } 95 | 96 | node 97 | } else { 98 | let node = self.children.pop_front(); 99 | 100 | if node.is_some() { 101 | self.track_pop_front(); 102 | } 103 | 104 | node 105 | } 106 | }, 107 | } 108 | } 109 | 110 | /// Removes the [node] at the front of the group. 111 | /// 112 | /// [node]: Node 113 | pub fn pop_front(&mut self) -> Option> { 114 | match self.children.len() { 115 | // `children` is empty 116 | 0 => None, 117 | // `children` is not empty 118 | _ => { 119 | if !self.orientation.reversed() { 120 | let node = self.children.pop_front(); 121 | 122 | if node.is_some() { 123 | self.track_pop_front(); 124 | } 125 | 126 | node 127 | } else { 128 | let node = self.children.pop_back(); 129 | 130 | if node.is_some() { 131 | self.track_pop_back(); 132 | } 133 | 134 | node 135 | } 136 | }, 137 | } 138 | } 139 | 140 | /// Pushes a new [window node] with the given `window` to the end of the group. 141 | /// 142 | /// [window node]: WindowNode 143 | #[inline] 144 | pub fn push_window_back(&mut self, window: Window) { 145 | self.push_node_back(Node::new_window(window)); 146 | } 147 | 148 | /// Pushes new [window nodes] of the given `windows` to the end of the group. 149 | /// 150 | /// [window nodes]: WindowNode 151 | #[inline] 152 | pub fn push_windows_back(&mut self, windows: impl IntoIterator) { 153 | self.push_nodes_back(windows.into_iter().map(Node::new_window)); 154 | } 155 | 156 | /// Pushes a new [window node] with the given `window` to the beginning of the group. 157 | /// 158 | /// [window node]: WindowNode 159 | #[inline] 160 | pub fn push_window_front(&mut self, window: Window) { 161 | self.push_node_front(Node::new_window(window)); 162 | } 163 | 164 | /// Pushes new [window nodes] of the given `windows` to the beginning of the group. 165 | /// 166 | /// [window nodes]: WindowNode 167 | #[inline] 168 | pub fn push_windows_front(&mut self, windows: impl IntoIterator) { 169 | self.push_nodes_front(windows.into_iter().map(Node::new_window)); 170 | } 171 | 172 | /// Inserts a new [window node] with the given `window` at the given `index` in the group. 173 | /// 174 | /// [window node]: WindowNode 175 | #[inline] 176 | pub fn insert_window(&mut self, index: usize, window: Window) { 177 | self.insert_node(index, Node::new_window(window)); 178 | } 179 | 180 | /// Inserts new [window nodes] of the given `windows` at the given `index` in the group. 181 | /// 182 | /// [window nodes]: WindowNode 183 | #[inline] 184 | pub fn insert_windows(&mut self, index: usize, windows: impl IntoIterator) { 185 | self.insert_nodes(index, windows.into_iter().map(Node::new_window)); 186 | } 187 | 188 | /// Pushes a new [group node] of the given `orientation` to the end of the group. 189 | /// 190 | /// [group node]: GroupNode 191 | #[inline] 192 | pub fn push_group_back(&mut self, orientation: Orientation) { 193 | self.push_node_back(Node::new_group(orientation)); 194 | } 195 | 196 | /// Pushes a new [group node] of the given `orientation` to the end of the group, then 197 | /// initialises it with the given `init` function. 198 | /// 199 | /// [group node]: GroupNode 200 | #[inline] 201 | pub fn push_group_back_with(&mut self, orientation: Orientation, init: impl FnOnce(&mut GroupNode)) { 202 | let index = self.push_node_back(Node::new_group(orientation)); 203 | 204 | match &mut self.children[index] { 205 | Node::Group(group) => init(group), 206 | Node::Window(_) => unreachable!("we know the this node is a group, because we just added it"), 207 | } 208 | } 209 | 210 | /// Pushes new [group nodes] of the given `orientations` to the end of the group. 211 | /// 212 | /// [group nodes]: GroupNode 213 | #[inline] 214 | pub fn push_groups_back(&mut self, orientations: impl IntoIterator) { 215 | self.push_nodes_back(orientations.into_iter().map(Node::new_group)); 216 | } 217 | 218 | /// Pushes a new [group node] of the given `orientation` to the beginning of the group. 219 | /// 220 | /// [group node]: GroupNode 221 | #[inline] 222 | pub fn push_group_front(&mut self, orientation: Orientation) { 223 | self.push_node_front(Node::new_group(orientation)); 224 | } 225 | 226 | /// Pushes a new [group node] of the given `orientation` to the beginning of the group, then 227 | /// initialises it with the given `init` function. 228 | /// 229 | /// [group node]: GroupNode 230 | #[inline] 231 | pub fn push_group_front_with(&mut self, orientation: Orientation, init: impl FnOnce(&mut GroupNode)) { 232 | let index = self.push_node_front(Node::new_group(orientation)); 233 | 234 | match &mut self.children[index] { 235 | Node::Group(group) => init(group), 236 | Node::Window(_) => unreachable!("we know the this node is a group, because we just added it"), 237 | } 238 | } 239 | 240 | /// Pushes new [group nodes] of the given `orientations` to the beginning of the group. 241 | /// 242 | /// [group nodes]: GroupNode 243 | #[inline] 244 | pub fn push_groups_front(&mut self, orientations: impl IntoIterator) { 245 | self.push_nodes_front(orientations.into_iter().map(Node::new_group)) 246 | } 247 | 248 | /// Inserts a new [group node] of the given `orientation` at the given `index` in the group. 249 | /// 250 | /// [group node]: GroupNode 251 | #[inline] 252 | pub fn insert_group(&mut self, index: usize, orientation: Orientation) { 253 | self.insert_node(index, Node::new_group(orientation)); 254 | } 255 | 256 | /// Inserts a new [group node] of the given `orientation` at the given `index` in the group, 257 | /// then initialises it with the given `init` function. 258 | /// 259 | /// [group node]: GroupNode 260 | #[inline] 261 | pub fn insert_group_with( 262 | &mut self, 263 | index: usize, 264 | orientation: Orientation, 265 | init: impl FnOnce(&mut GroupNode), 266 | ) { 267 | let index = self.insert_node(index, Node::new_group(orientation)); 268 | 269 | match &mut self.children[index] { 270 | Node::Group(group) => init(group), 271 | Node::Window(_) => unreachable!("we know this node is a group, because we just added it"), 272 | } 273 | } 274 | 275 | /// Inserts new [group nodes] of the given `orientations` at the given `index` in the group. 276 | /// 277 | /// [group nodes]: GroupNode 278 | #[inline] 279 | pub fn insert_groups(&mut self, index: usize, orientations: impl IntoIterator) { 280 | self.insert_nodes(index, orientations.into_iter().map(Node::new_group)); 281 | } 282 | 283 | /// Push the given `node` to the list, and return the index it was pushed to. 284 | /// 285 | /// The index is affected by whether this group is [reversed] or not. 286 | /// 287 | /// [reversed]: Orientation::reversed 288 | fn push_node_back(&mut self, node: Node) -> usize { 289 | if !self.orientation().reversed() { 290 | // The orientation is not reversed; we push to the end of the list as usual. 291 | 292 | let index = self.children.len(); 293 | 294 | self.children.push_back(node); 295 | self.track_push_back(); 296 | 297 | index 298 | } else { 299 | // The orientation is reversed; we push to the front of the list to give the impression 300 | // we are pushing to the back in the non-reversed orientation equivalent. 301 | 302 | const INDEX: usize = 0; 303 | 304 | self.children.push_front(node); 305 | self.track_push_front(); 306 | 307 | INDEX 308 | } 309 | } 310 | 311 | fn push_node_front(&mut self, node: Node) -> usize { 312 | if !self.orientation().reversed() { 313 | const INDEX: usize = 0; 314 | 315 | self.children.push_front(node); 316 | self.track_push_front(); 317 | 318 | INDEX 319 | } else { 320 | let index = self.children.len(); 321 | 322 | self.children.push_back(node); 323 | self.track_push_back(); 324 | 325 | index 326 | } 327 | } 328 | 329 | fn push_nodes_back(&mut self, nodes: impl IntoIterator>) { 330 | let nodes = nodes.into_iter(); 331 | 332 | let (min_nodes, _) = nodes.size_hint(); 333 | self.children.reserve(min_nodes); 334 | 335 | if !self.orientation().reversed() { 336 | for node in nodes { 337 | self.children.push_back(node); 338 | self.track_push_back(); 339 | } 340 | } else { 341 | for node in nodes { 342 | self.children.push_front(node); 343 | self.track_push_back(); 344 | } 345 | } 346 | } 347 | 348 | fn push_nodes_front(&mut self, nodes: impl IntoIterator>) { 349 | let nodes = nodes.into_iter(); 350 | 351 | let (min_nodes, _) = nodes.size_hint(); 352 | self.children.reserve(min_nodes); 353 | 354 | if !self.orientation().reversed() { 355 | for node in nodes { 356 | self.children.push_front(node); 357 | self.track_push_front(); 358 | } 359 | } else { 360 | for node in nodes { 361 | self.children.push_back(node); 362 | self.track_push_front(); 363 | } 364 | } 365 | } 366 | 367 | /// Insert the given `node` to the list, and return the index it was pushed to. 368 | /// 369 | /// The index is affected by whether this group is [reversed] or not. 370 | /// 371 | /// [reversed]: Orientation::reversed 372 | fn insert_node(&mut self, index: usize, node: Node) -> usize { 373 | if !self.orientation().reversed() { 374 | // The orientation is not reversed; we insert as usual. 375 | 376 | self.children.insert(index, node); 377 | self.track_insert(index); 378 | 379 | index 380 | } else { 381 | // The orientation is reversed; we insert at the `index` _counting back from the end_ to give the 382 | // impression we are inserting at `index` counting from the front in the non-reversed orientation 383 | // equivalent. 384 | 385 | let last = self.children.len() - 1; 386 | let index = last - index; 387 | 388 | self.children.insert(index, node); 389 | self.track_insert(index); 390 | 391 | index 392 | } 393 | } 394 | 395 | fn insert_nodes(&mut self, index: usize, nodes: impl IntoIterator>) { 396 | let nodes = nodes.into_iter(); 397 | 398 | let (min_nodes, _) = nodes.size_hint(); 399 | self.children.reserve(min_nodes); 400 | 401 | if !self.orientation().reversed() { 402 | for (index, node) in nodes.enumerate().map(|(i, node)| (index + i, node)) { 403 | self.children.insert(index, node); 404 | self.track_insert(index); 405 | } 406 | } else { 407 | let last = self.children.len() - 1; 408 | let index = last - index; 409 | 410 | for node in nodes { 411 | self.children.insert(index, node); 412 | self.track_insert(index); 413 | } 414 | } 415 | } 416 | 417 | /// Update `additions` to reflect a node being inserted at `index`. 418 | fn track_insert(&mut self, index: usize) { 419 | let insertion_point = self.additions.partition_point(|&i| i < index); 420 | self.additions.insert(insertion_point, index); 421 | 422 | // Move following additions over by 1. 423 | for addition in &mut self.additions.make_contiguous()[(insertion_point + 1)..] { 424 | *addition += 1; 425 | } 426 | } 427 | 428 | /// Update `additions` to reflect a node being pushed to the end of `nodes`. 429 | #[inline] 430 | fn track_push_back(&mut self) { 431 | let index = self.children.len() - 1; 432 | 433 | // If the node has been pushed to the end, then it must have the greatest index. 434 | self.additions.push_back(index); 435 | 436 | // There will be no additions following it to move over, as it was pushed to the end. 437 | } 438 | 439 | fn track_push_front(&mut self) { 440 | // Move every existing addition over by 1; if the node has been pushed to the front, it has the 441 | // lowest index. 442 | for addition in &mut self.additions { 443 | *addition += 1; 444 | } 445 | 446 | // Push the addition to the front. 447 | self.additions.push_front(0); 448 | } 449 | 450 | /// Update `additions` to reflect the removal of a node at `index`. 451 | fn track_remove(&mut self, index: usize) { 452 | let shifted_additions = match self.additions.binary_search(&index) { 453 | // An addition we were tracking was removed. 454 | Ok(addition) => { 455 | self.additions.remove(addition); 456 | 457 | addition.. 458 | }, 459 | 460 | // The removed node was not an addition we were tracking. 461 | Err(removal_point) => removal_point.., 462 | }; 463 | 464 | // Move following additions back by 1. 465 | for addition in &mut self.additions.make_contiguous()[shifted_additions] { 466 | *addition -= 1; 467 | } 468 | } 469 | 470 | #[inline] 471 | fn track_pop_back(&mut self) { 472 | if !self.additions.is_empty() { 473 | // The index that the node was popped from. 474 | let index = self.children.len(); 475 | 476 | // If it was one of our own additions, pop that addition. 477 | if self.additions[self.additions.len() - 1] == index { 478 | self.additions.pop_back(); 479 | } 480 | } 481 | } 482 | 483 | fn track_pop_front(&mut self) { 484 | if !self.additions.is_empty() { 485 | // The index that the node was popped from. 486 | const INDEX: usize = 0; 487 | 488 | // If it was one of our own additions, pop that addition. 489 | if self.additions[0] == INDEX { 490 | self.additions.pop_front(); 491 | } 492 | } 493 | 494 | // Move all the additions back by one. 495 | for addition in &mut self.additions { 496 | *addition -= 1; 497 | } 498 | } 499 | } 500 | 501 | impl GroupNode { 502 | /// Returns whether any changes have been made by the [layout manager] to this group (directly 503 | /// or indirectly). 504 | /// 505 | /// [layout manager]: TilingLayoutManager 506 | fn changes_made(&self) -> bool { 507 | !self.additions.is_empty() 508 | || self.total_removed_primary != 0 509 | || self.new_orientation.is_some() 510 | || self.new_width.is_some() 511 | || self.new_height.is_some() 512 | || self.new_x.is_some() 513 | || self.new_y.is_some() 514 | } 515 | 516 | /// Applies the changes made by the [layout manager]. 517 | /// 518 | /// `resize_window` is a function that resizes the given window based on the given x and y 519 | /// coordinates and width and height (in that order). 520 | /// 521 | /// [layout manager]: TilingLayoutManager 522 | /// 523 | /// [primary]: Node::primary_dimension 524 | /// [secondary]: Node::secondary_dimension 525 | pub(crate) fn apply_changes( 526 | &mut self, 527 | reconfigure_window: &mut impl FnMut(&Window, i32, i32, u32, u32) -> Result<(), Error>, 528 | settings: &LayoutSettings, 529 | ) -> Result<(), Error> { 530 | // FIXME: need to also determine whether changes have been made to the layout settings. 531 | // If no changes have been made to this group, apply all the child groups' changes and return. 532 | if !self.changes_made() { 533 | for node in self { 534 | match node { 535 | Node::Group(group) => group.apply_changes(reconfigure_window, settings)?, 536 | 537 | Node::Window(WindowNode { 538 | window, 539 | window_changed, 540 | x, 541 | y, 542 | width, 543 | height, 544 | }) => { 545 | if mem::take(window_changed) { 546 | reconfigure_window(window, *x, *y, *width, *height)? 547 | } 548 | }, 549 | } 550 | } 551 | 552 | return Ok(()); 553 | } 554 | 555 | let additions = mem::take(&mut self.additions); 556 | let total_removed_primary = mem::take(&mut self.total_removed_primary); 557 | 558 | let new_orientation = mem::take(&mut self.new_orientation); 559 | 560 | let new_width = mem::take(&mut self.new_width); 561 | let new_height = mem::take(&mut self.new_height); 562 | 563 | let new_x = mem::take(&mut self.new_x); 564 | let new_y = mem::take(&mut self.new_y); 565 | 566 | // The old axis of the group, before any orientation change. 567 | let old_axis = self.orientation.axis(); 568 | 569 | // Apply the change in orientation, if it is to be changed. 570 | if let Some(orientation) = new_orientation { 571 | self.orientation = orientation; 572 | } 573 | // Apply the change in width, if any. 574 | if let Some(width) = new_width { 575 | self.width = width; 576 | } 577 | // Apply the change in height, if any. 578 | if let Some(height) = new_height { 579 | self.height = height; 580 | } 581 | if let Some(x) = new_x { 582 | self.x = x; 583 | } 584 | if let Some(y) = new_y { 585 | self.y = y; 586 | } 587 | 588 | let new_axis = self.orientation.axis(); 589 | 590 | // `u64` is used because we will be multiplying two 'u32' values, and `u64::MAX` is 591 | // `u32::MAX * u32::MAX`. 592 | let old_total_node_primary = (self.total_node_primary - total_removed_primary) as u64; 593 | 594 | // The order of dimensions used for nodes depends on the orientation of the group. The first 595 | // dimension, `primary`, is the dimension that is affected by the node's size within the 596 | // group, while the second dimension, `secondary`, is the dimension that is only affected by 597 | // the group's size. 598 | // 599 | // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ 600 | // ┃ Dimensions (primary, secondary) ┃ 601 | // ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┯━━━━━━━━━━━━━━━━━━━━━┩ 602 | // │ Horizontal │ Vertical │ 603 | // ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┤ 604 | // │ │ ⟵secondary⟶ │ 605 | // │ │ ↑ ╔═════════╗ │ 606 | // │ │ primary ║ Node ║ │ 607 | // │ ⟵primary⟶ │ ↓ ╚═════════╝ │ 608 | // │ ↑ ╔═══════╗╔════╗╔════╗ │ ╔═════════╗ │ 609 | // │ secondary ║ Node ║║ ║║ ║ │ ║ ║ │ 610 | // │ ↓ ╚═══════╝╚════╝╚════╝ │ ╚═════════╝ │ 611 | // │ │ ╔═════════╗ │ 612 | // │ │ ║ ║ │ 613 | // │ │ ╚═════════╝ │ 614 | // └─────────────────────────────────┴─────────────────────┘ 615 | 616 | let (group_primary, group_secondary) = (self.primary_dimension(), self.secondary_dimension()); 617 | let (group_primary_coord, group_secondary_coord) = (self.primary_coord(), self.secondary_coord()); 618 | // Set a node's dimensions and call `reconfigure_window` if it is a window. 619 | let mut configure_node = |node: &mut Node, mut primary_coord, primary_dimension| { 620 | // If the orientation is reversed, then reverse the coordinates. 621 | if self.orientation.reversed() { 622 | primary_coord = (group_primary as i32) - primary_coord - (primary_dimension as i32); 623 | } 624 | 625 | node.set_primary_coord(group_primary_coord + primary_coord, new_axis); 626 | node.set_secondary_coord(group_secondary_coord, new_axis); 627 | 628 | node.set_primary_dimension(primary_dimension, new_axis); 629 | node.set_secondary_dimension(group_secondary, new_axis); 630 | 631 | match node { 632 | Node::Group(group) => group.apply_changes(reconfigure_window, settings), 633 | 634 | Node::Window(WindowNode { 635 | window, 636 | window_changed, 637 | x, 638 | y, 639 | width, 640 | height, 641 | }) => { 642 | *window_changed = false; 643 | reconfigure_window(window, *x, *y, *width, *height) 644 | }, 645 | } 646 | }; 647 | 648 | let current_nodes_len = self.children.len(); 649 | let new_nodes_len = (current_nodes_len + self.additions.len()) as u32; 650 | let total_gap = if new_nodes_len == 0 { 651 | 0 652 | } else { 653 | (new_nodes_len - 1) * settings.window_gap 654 | }; 655 | // The size of new additions. 656 | let new_primary = if new_nodes_len == 0 { 657 | group_primary 658 | } else { 659 | (group_primary - total_gap) / new_nodes_len 660 | }; 661 | let mut new_total_node_primary = 0; 662 | // The new total size for the existing nodes to be resized to fit within. 663 | // 664 | // `u64` is used because we will be multiplying two 'u32' values, and `u64::MAX` is 665 | // `u32::MAX * u32::MAX`. 666 | let rescaling_primary = (group_primary - (new_primary * (additions.len() as u32)) - total_gap) as u64; 667 | 668 | let mut additions = additions.into_iter(); 669 | let mut next_addition = additions.next(); 670 | 671 | // Resize all the nodes appropriately. 672 | for (index, node) in self.children.iter_mut().enumerate() { 673 | let gap = (settings.window_gap as i32) * (index as i32); 674 | let coord = (new_total_node_primary as i32) + gap; 675 | 676 | // If `node` is an addition, resize it with the new size. 677 | if let Some(addition) = next_addition { 678 | if index == addition { 679 | configure_node(node, coord, new_primary)?; 680 | 681 | next_addition = additions.next(); 682 | 683 | new_total_node_primary += new_primary; 684 | continue; 685 | } 686 | } 687 | 688 | // `node` is not an addition: rescale it. 689 | 690 | // `u64` is used because we will be multiplying two 'u32' values, and `u64::MAX` is 691 | // `u32::MAX * u32::MAX`. 692 | let old_primary = node.primary_dimension(old_axis) as u64; 693 | // Determine the rescaled size. 694 | // 695 | // This is `shrink`ed back into a `u32` value (a value `> u32::MAX` will be clipped to 696 | // `u32::MAX`), though in practice it almost certainly will never get anywhere near that 697 | // large - monitors don't tend to be millions of pixels in width or height. 698 | let rescaled_primary: u32 = ((old_primary * rescaling_primary) / old_total_node_primary).shrink(); 699 | 700 | configure_node(node, coord, rescaled_primary)?; 701 | 702 | new_total_node_primary += rescaled_primary; 703 | } 704 | 705 | self.total_node_primary = new_total_node_primary; 706 | 707 | Ok(()) 708 | } 709 | } 710 | 711 | #[cfg(test)] 712 | mod tests { 713 | use super::*; 714 | 715 | /// No-op resize_window function to pass to [`apply_resizes`]. 716 | /// 717 | /// [`apply_resizes`]: GroupNode::apply_changes 718 | const fn resize_window(_window: &Window, _x: i32, _y: i32, _width: u32, _height: u32) -> Result<(), ()> { 719 | Ok(()) 720 | } 721 | 722 | #[test] 723 | fn group_orientations() { 724 | const INITIAL_ORIENTATION: Orientation = Orientation::LeftToRight; 725 | const ROTATIONS: i32 = -6; 726 | const NEW_ORIENTATION: Orientation = Orientation::RightToLeft; 727 | 728 | let mut group: GroupNode<()> = GroupNode::with(INITIAL_ORIENTATION, 0, 0, 0, 0); 729 | 730 | assert_eq!(group.orientation, INITIAL_ORIENTATION); 731 | assert_eq!(group.new_orientation, None); 732 | assert_eq!(group.orientation(), INITIAL_ORIENTATION); 733 | 734 | group.rotate_by(ROTATIONS); 735 | 736 | assert_eq!(group.orientation, INITIAL_ORIENTATION); 737 | assert_eq!(group.new_orientation, Some(NEW_ORIENTATION)); 738 | assert_eq!(group.orientation(), NEW_ORIENTATION); 739 | 740 | // Apply the change in orientation. 741 | group 742 | .apply_changes(&mut resize_window, &LayoutSettings::default()) 743 | .unwrap(); 744 | 745 | assert_eq!(group.orientation, NEW_ORIENTATION); 746 | assert_eq!(group.new_orientation, None); 747 | assert_eq!(group.orientation(), NEW_ORIENTATION); 748 | } 749 | 750 | #[test] 751 | fn push_windows() { 752 | const WINDOWS: [u32; 5] = [1, 2, 3, 4, 5]; 753 | 754 | let non_reversed_nodes = VecDeque::from(WINDOWS.map(Node::new_window)); 755 | let reversed_nodes: VecDeque<_> = non_reversed_nodes.iter().cloned().rev().collect(); 756 | 757 | // Test a non-reversed group. 758 | let mut non_reversed_group: GroupNode = GroupNode::new(Orientation::LeftToRight); 759 | assert_eq!(non_reversed_group.children, VecDeque::new()); 760 | 761 | non_reversed_group.push_windows_back(WINDOWS); 762 | assert_eq!(non_reversed_group.children, non_reversed_nodes); 763 | 764 | // Test a reversed group. 765 | let mut reversed_group: GroupNode = GroupNode::new(Orientation::RightToLeft); 766 | 767 | reversed_group.push_windows_back(WINDOWS); 768 | assert_eq!(reversed_group.children, reversed_nodes); 769 | } 770 | 771 | /// Tests [`apply_changes`] in response to adding and removing windows and changing the group 772 | /// [`orientation`]. 773 | /// 774 | /// [`apply_changes`]: GroupNode::apply_changes 775 | /// [`orientation`]: GroupNode::orientation() 776 | #[test] 777 | fn resizing() { 778 | const GROUP_WIDTH: u32 = 3000; 779 | const GROUP_HEIGHT: u32 = 1000; 780 | 781 | let settings = LayoutSettings::new().window_gap(0); 782 | 783 | // The width of each node after three nodes have been added. 784 | const THREE_NODES_WIDTH: u32 = GROUP_WIDTH / 3; 785 | // The width of each node after two nodes have been added. 786 | const TWO_NODES_WIDTH: u32 = GROUP_WIDTH / 2; 787 | 788 | // The height of each node after two nodes have been added and the axis has been set to 789 | // vertical. 790 | const TWO_NODES_HEIGHT: u32 = GROUP_HEIGHT / 2; 791 | 792 | let mut group: GroupNode = GroupNode::with(Orientation::LeftToRight, 0, 0, GROUP_WIDTH, GROUP_HEIGHT); 793 | 794 | group.push_window_back(1); 795 | 796 | assert!( 797 | matches!( 798 | &group[0], 799 | Node::Window(WindowNode { 800 | width: 0, 801 | height: 0, 802 | .. 803 | }), 804 | ), 805 | "node = {:?}", 806 | &group[0], 807 | ); 808 | 809 | // Resize the added window. 810 | group.apply_changes(&mut resize_window, &settings).unwrap(); 811 | 812 | assert!( 813 | matches!( 814 | &group[0], 815 | Node::Window(WindowNode { 816 | width: GROUP_WIDTH, 817 | height: GROUP_HEIGHT, 818 | .. 819 | }), 820 | ), 821 | "node = {:?}", 822 | &group[0], 823 | ); 824 | 825 | group.push_windows_back([2, 3]); 826 | 827 | // Resize the existing window and two added windows. 828 | group.apply_changes(&mut resize_window, &settings).unwrap(); 829 | 830 | for node in &group { 831 | assert!( 832 | matches!( 833 | node, 834 | Node::Window(WindowNode { 835 | width: THREE_NODES_WIDTH, 836 | height: GROUP_HEIGHT, 837 | .. 838 | }), 839 | ), 840 | "node = {:?}", 841 | node, 842 | ); 843 | } 844 | 845 | // Remove the first node. 846 | group.remove(0); 847 | 848 | // Resize the two remaining windows. 849 | group.apply_changes(&mut resize_window, &settings).unwrap(); 850 | 851 | for node in &group { 852 | assert!( 853 | matches!( 854 | node, 855 | Node::Window(WindowNode { 856 | width: TWO_NODES_WIDTH, 857 | height: GROUP_HEIGHT, 858 | .. 859 | }), 860 | ), 861 | "node = {:?}", 862 | node, 863 | ); 864 | } 865 | 866 | let mut clone = group.clone(); 867 | 868 | // Add a window and immediately remove it. 869 | clone.push_window_front(1); 870 | clone.remove(0); 871 | // Apply the changes (of which there should be none). 872 | clone.apply_changes(&mut resize_window, &settings).unwrap(); 873 | 874 | assert_eq!(group, clone); 875 | 876 | group.set_orientation(Orientation::TopToBottom); 877 | // Apply the orientation change. 878 | group.apply_changes(&mut resize_window, &settings).unwrap(); 879 | 880 | for node in &group { 881 | assert!( 882 | matches!( 883 | node, 884 | Node::Window(WindowNode { 885 | width: GROUP_WIDTH, 886 | height: TWO_NODES_HEIGHT, 887 | .. 888 | }), 889 | ), 890 | "node = {:?}", 891 | node 892 | ); 893 | } 894 | } 895 | } 896 | -------------------------------------------------------------------------------- /src/layout/managers.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use super::*; 6 | 7 | pub struct Stack { 8 | layout: TilingLayout, 9 | } 10 | 11 | #[allow(unused)] 12 | impl Stack { 13 | /// Returns a shared reference to the main window, if there is one. 14 | fn main(&self) -> Option<&WindowNode> { 15 | self.layout.first().and_then(|node| match node { 16 | Node::Group(_) => None, 17 | Node::Window(node) => Some(node), 18 | }) 19 | } 20 | 21 | /// Returns a shared reference to the stack, if there is one. 22 | fn stack(&self) -> Option<&GroupNode> { 23 | self.layout.get(1).and_then(|node| match node { 24 | Node::Group(node) => Some(node), 25 | Node::Window(_) => None, 26 | }) 27 | } 28 | 29 | /// Returns a mutable reference to the main window, if there is one. 30 | fn main_mut(&mut self) -> Option<&mut WindowNode> { 31 | self.layout.first_mut().and_then(|node| match node { 32 | Node::Group(_) => None, 33 | Node::Window(node) => Some(node), 34 | }) 35 | } 36 | 37 | /// Returns a mutable reference to the stack, if there is one. 38 | fn stack_mut(&mut self) -> Option<&mut GroupNode> { 39 | self.layout.get_mut(1).and_then(|node| match node { 40 | Node::Group(node) => Some(node), 41 | Node::Window(_) => None, 42 | }) 43 | } 44 | } 45 | 46 | unsafe impl TilingLayoutManager for Stack 47 | where 48 | Window: Send + Sync + PartialEq + 'static, 49 | { 50 | #[inline(always)] 51 | fn orientation() -> Orientation 52 | where 53 | Self: Sized, 54 | { 55 | Orientation::LeftToRight 56 | } 57 | 58 | fn init(layout: TilingLayout, windows: WindowsIter) -> Self 59 | where 60 | Self: Sized, 61 | WindowsIter: IntoIterator, 62 | WindowsIter::IntoIter: ExactSizeIterator, 63 | { 64 | let mut stack = Self { layout }; 65 | 66 | let mut windows = windows.into_iter(); 67 | 68 | if let Some(main) = windows.next() { 69 | // Push the main window. 70 | stack.layout.push_window_back(main); 71 | 72 | // If there are more windows, then add them in a stack. 73 | if windows.len() > 0 { 74 | stack 75 | .layout 76 | .push_group_back_with(Orientation::TopToBottom, |stack| stack.push_windows_back(windows)); 77 | } 78 | } 79 | 80 | stack 81 | } 82 | 83 | #[inline(always)] 84 | fn layout(&self) -> &TilingLayout { 85 | &self.layout 86 | } 87 | 88 | #[inline(always)] 89 | fn layout_mut(&mut self) -> &mut TilingLayout { 90 | &mut self.layout 91 | } 92 | 93 | fn add_window(&mut self, window: Window) { 94 | if self.layout.is_empty() { 95 | // No main, no stack. 96 | 97 | // Add the window as a main. 98 | self.layout.push_window_back(window); 99 | } else if let Some(stack) = self.stack_mut() { 100 | // Main and stack. 101 | 102 | // Add the window to the stack. 103 | stack.push_window_back(window); 104 | } else { 105 | // Main, no stack. 106 | 107 | // Add the window to a new stack. 108 | self.layout 109 | .push_group_back_with(Orientation::TopToBottom, |stack| stack.push_window_back(window)); 110 | } 111 | } 112 | 113 | fn remove_window(&mut self, window: &Window) { 114 | if let Some(main) = self.main() { 115 | if main.window() == window { 116 | if let Some(Node::Window(new_main)) = self.stack_mut().and_then(|stack| stack.remove(0)) { 117 | // If there is a window to replace the main window with, do that. 118 | self.main_mut() 119 | .expect("We've already established `main` is present.") 120 | .set_window(new_main.into_window()); 121 | } else { 122 | // Otherwise, if there is no window to replace the main window with, remove the 123 | // node. 124 | self.layout.pop_front(); 125 | } 126 | 127 | return; 128 | } 129 | } 130 | 131 | // Otherwise, if the main window does not match... 132 | 133 | // If there is a stack... 134 | if let Some(stack) = self.stack_mut() { 135 | let window_nodes = stack.iter_mut().enumerate().filter_map(|(i, node)| match node { 136 | Node::Group(_) => None, 137 | Node::Window(window_node) => Some((i, window_node)), 138 | }); 139 | 140 | // For every window node in the stack... 141 | for (i, node) in window_nodes { 142 | // If the window matches, remove it and return. 143 | if node.window() == window { 144 | if stack.len() > 1 { 145 | // If it is not the last window in the stack, remove the node. 146 | stack.remove(i); 147 | } else { 148 | // Otherwise, if it is the last window in the stack, remove the stack. 149 | self.layout.pop_back(); 150 | } 151 | 152 | return; 153 | } 154 | } 155 | } 156 | } 157 | } 158 | 159 | pub struct Spiral { 160 | layout: TilingLayout, 161 | } 162 | 163 | unsafe impl TilingLayoutManager for Spiral 164 | where 165 | Window: Send + Sync + PartialEq + 'static, 166 | { 167 | #[inline(always)] 168 | fn orientation() -> Orientation 169 | where 170 | Self: Sized, 171 | { 172 | Orientation::LeftToRight 173 | } 174 | 175 | fn init(layout: TilingLayout, windows: WindowsIter) -> Self 176 | where 177 | Self: Sized, 178 | WindowsIter: IntoIterator, 179 | WindowsIter::IntoIter: ExactSizeIterator, 180 | { 181 | let mut spiral = Self { layout }; 182 | 183 | let mut target_group: Option<&mut GroupNode<_>> = Some(&mut spiral.layout); 184 | 185 | for window in windows { 186 | match target_group.take() { 187 | Some(target) => { 188 | target.push_group_back(target.orientation.rotated_by(1)); 189 | 190 | if let Node::Group(group) = &mut target[1] { 191 | group.push_window_front(window); 192 | target_group = Some(group); 193 | } 194 | }, 195 | 196 | // TODO: see if there is a way to avoid using `Option` here (its usage avoids 197 | // : multiple mutable borrows issues) 198 | None => unreachable!(), 199 | } 200 | } 201 | 202 | spiral 203 | } 204 | 205 | #[inline(always)] 206 | fn layout(&self) -> &TilingLayout { 207 | &self.layout 208 | } 209 | 210 | #[inline(always)] 211 | fn layout_mut(&mut self) -> &mut TilingLayout { 212 | &mut self.layout 213 | } 214 | 215 | fn add_window(&mut self, window: Window) { 216 | let group = { 217 | // TODO: avoid using `Option` if possible (only used to avoid issues with multiple 218 | // : mutable borrows, which using `take()` solves) 219 | let mut target_group: Option<&mut GroupNode<_>> = Some(&mut self.layout); 220 | 221 | while let Some(Node::Group(group)) = target_group.take().unwrap().get_mut(1) { 222 | target_group = Some(group); 223 | } 224 | 225 | target_group.unwrap() 226 | }; 227 | 228 | group.push_group_back_with(group.orientation(), |group| { 229 | group.push_window_front(window); 230 | }); 231 | } 232 | 233 | fn remove_window(&mut self, window: &Window) { 234 | // If the first window matches, remove it and return. 235 | if let Some(Node::Window(window_node)) = self.layout.get(0) { 236 | if window_node.window() == window { 237 | if self.layout.get(1).is_some() { 238 | // If there are more groups, move the windows up to replace the first window. 239 | 240 | Self::move_window_up(&mut self.layout); 241 | } else { 242 | // Otherwise, remove the first window node. 243 | 244 | self.layout.pop_front(); 245 | } 246 | 247 | return; 248 | } 249 | } 250 | 251 | let mut target_group: Option<&mut GroupNode<_>> = Some(&mut self.layout); 252 | 253 | while let Some(target) = target_group.take() { 254 | if let Some(Node::Group(group)) = target.get_mut(1) { 255 | if group[0].unwrap_window_ref().window() == window { 256 | // Window matches. 257 | 258 | match group.get_mut(1) { 259 | // Has a child group: shift all the windows up to replace the removed one. 260 | Some(Node::Group(group)) => { 261 | Self::move_window_up(group); 262 | }, 263 | 264 | // Doesn't have a child group: remove the node. 265 | _ => { 266 | // FIXME: Why doesn't this work?! It's fine if you remove the `else` 267 | // : branch below, but it is so obviously not accessible because 268 | // : this if branch ends in `return`. The compiler should 269 | // : absolutely be able to work that out. 270 | // target.remove(1); 271 | }, 272 | } 273 | 274 | return; 275 | } else { 276 | // Window doesn't match; move onto the next group. 277 | 278 | target_group = Some(group); 279 | } 280 | } 281 | } 282 | } 283 | } 284 | 285 | impl Spiral { 286 | fn move_window_up(group: &mut GroupNode) -> Window { 287 | let window = if let Some(Node::Group(group)) = group[1].unwrap_group_mut().get_mut(1) { 288 | Self::move_window_up(group) 289 | } else { 290 | group.remove(1).unwrap().unwrap_window().into_window() 291 | }; 292 | 293 | group[0].unwrap_window_mut().replace_window(window) 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | #![warn(clippy::missing_const_for_fn)] 6 | // Feature flags 7 | #![feature(impl_trait_in_assoc_type)] 8 | #![feature(iterator_try_collect)] 9 | #![feature(doc_cfg)] 10 | 11 | use std::{env, ffi::OsString, io, process}; 12 | 13 | use clap::Parser; 14 | use thiserror::Error; 15 | 16 | use crate::{display_server::DisplayServer, layout::LayoutSettings}; 17 | 18 | mod cli; 19 | pub mod display_server; 20 | pub mod layout; 21 | pub mod state; 22 | 23 | #[cfg(not(any(feature = "wayland", feature = "x11")))] 24 | compile_error!("At least one display server feature must be enabled for AquariWM to function."); 25 | 26 | #[derive(Debug, Error)] 27 | pub enum Error { 28 | #[cfg(feature = "wayland")] 29 | #[error(transparent)] 30 | Wayland(#[from] display_server::wayland::Error), 31 | 32 | #[cfg(feature = "x11")] 33 | #[error(transparent)] 34 | X11(#[from] display_server::x11::Error), 35 | } 36 | 37 | pub type Result = std::result::Result; 38 | 39 | fn main() -> Result<()> { 40 | // Initiate `tracing_subscriber` for formatting logs. 41 | match tracing_subscriber::EnvFilter::try_from_default_env() { 42 | Ok(env_filter) => tracing_subscriber::fmt().with_env_filter(env_filter).init(), 43 | 44 | Err(_) => tracing_subscriber::fmt().init(), 45 | } 46 | 47 | // Parse command line subcommand and options. 48 | let args = cli::Cli::parse(); 49 | // Whether testing is enabled. 50 | let testing = args.testing(); 51 | 52 | let settings = match args.window_gap { 53 | Some(window_gap) => LayoutSettings::new().window_gap(window_gap), 54 | None => LayoutSettings::default(), 55 | }; 56 | 57 | match &args.subcommand { 58 | #[cfg(feature = "wayland")] 59 | cli::Subcommand::Wayland => Ok(display_server::Wayland::run(testing, settings)?), 60 | 61 | #[cfg(feature = "x11")] 62 | cli::Subcommand::X11 => Ok(tokio::runtime::Builder::new_multi_thread() 63 | .enable_all() 64 | .build() 65 | .unwrap() 66 | .block_on(async { display_server::X11::run(testing, settings).await })?), 67 | } 68 | } 69 | 70 | /// An error returned by [`launch_terminal`]. 71 | #[derive(Debug, Error)] 72 | pub enum LaunchTerminalError { 73 | /// The `TERM` environment variable was not set to any terminal. 74 | #[error("the `TERM` environment variable is not set")] 75 | VarNotPresent, 76 | 77 | /// An IO error occurred trying to launch the `TERM` terminal. 78 | #[error(transparent)] 79 | Io(#[from] io::Error), 80 | } 81 | 82 | /// Attempts to launch the terminal set in the `TERM` environment variable. 83 | /// 84 | /// If successful, returns the launched terminal process and the contents of the `TERM` environment 85 | /// variable launched. 86 | pub fn launch_terminal() -> Result<(OsString, process::Child), LaunchTerminalError> { 87 | match env::var_os("TERM") { 88 | // `TERM` is present. 89 | Some(terminal) => { 90 | let process = process::Command::new(&terminal).spawn()?; 91 | 92 | Ok((terminal, process)) 93 | }, 94 | 95 | // `TERM` is not present. 96 | None => Err(LaunchTerminalError::VarNotPresent), 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/state.rs: -------------------------------------------------------------------------------- 1 | // This Source Code Form is subject to the terms of the Mozilla Public 2 | // License, v. 2.0. If a copy of the MPL was not distributed with this 3 | // file, You can obtain one at https://mozilla.org/MPL/2.0/. 4 | 5 | use std::{collections::HashMap, hash::Hash}; 6 | 7 | #[cfg(feature = "async")] 8 | use {futures::future, std::future::Future}; 9 | 10 | use crate::layout::{self, CurrentLayout, LayoutSettings}; 11 | 12 | #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] 13 | pub enum MapState { 14 | Mapped, 15 | Unmapped, 16 | } 17 | 18 | #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] 19 | pub struct WindowState { 20 | pub mode: layout::Mode, 21 | pub mapped: MapState, 22 | } 23 | 24 | impl WindowState { 25 | #[inline] 26 | pub fn new(mapped: MapState) -> Self { 27 | Self { 28 | mode: layout::Mode::default(), 29 | mapped, 30 | } 31 | } 32 | 33 | #[inline] 34 | pub const fn with_layout_mode(mode: layout::Mode, mapped: MapState) -> Self { 35 | Self { mode, mapped } 36 | } 37 | 38 | #[inline] 39 | pub fn set_floating(&mut self) { 40 | self.mode = layout::Mode::Floating; 41 | } 42 | 43 | #[inline] 44 | pub fn set_tiled(&mut self) { 45 | self.mode = layout::Mode::Tiled; 46 | } 47 | 48 | #[inline] 49 | pub fn set_unmapped(&mut self) { 50 | self.mapped = MapState::Unmapped; 51 | } 52 | 53 | #[inline] 54 | pub fn set_mapped(&mut self) { 55 | self.mapped = MapState::Mapped; 56 | } 57 | } 58 | 59 | pub struct AquariWm { 60 | /// The current window layout. 61 | pub layout: CurrentLayout, 62 | pub settings: LayoutSettings, 63 | 64 | /// A [`HashMap`] of windows and their current [`WindowState`s]. 65 | /// 66 | /// [`WindowState`s]: WindowState 67 | pub windows: HashMap, 68 | } 69 | 70 | impl Default for AquariWm { 71 | #[inline] 72 | fn default() -> Self { 73 | Self { 74 | layout: Default::default(), 75 | settings: Default::default(), 76 | windows: Default::default(), 77 | } 78 | } 79 | } 80 | 81 | impl AquariWm { 82 | /// Creates a new AquariWM state struct with the default [`CurrentLayout`] and no windows. 83 | #[inline] 84 | pub fn new(settings: LayoutSettings) -> Self { 85 | Self { 86 | settings, 87 | 88 | ..Default::default() 89 | } 90 | } 91 | 92 | /// Creates a new AquariWM state struct with the given `layout` and no windows. 93 | #[inline] 94 | pub fn with_tiling_layout(x: i32, y: i32, width: u32, height: u32, settings: LayoutSettings) -> Self 95 | where 96 | Manager: layout::TilingLayoutManager, 97 | { 98 | Self { 99 | layout: CurrentLayout::new_tiled::(x, y, width, height, &settings), 100 | settings, 101 | 102 | windows: HashMap::new(), 103 | } 104 | } 105 | 106 | /// Creates a new AquariWM state struct with the default [`CurrentLayout`] and the given 107 | /// `windows`. 108 | pub fn with_windows(windows: impl IntoIterator, settings: LayoutSettings) -> Self { 109 | let mut aquariwm = Self { 110 | layout: CurrentLayout::default(), 111 | settings, 112 | 113 | windows: HashMap::new(), 114 | }; 115 | 116 | aquariwm.add_windows(windows); 117 | 118 | aquariwm 119 | } 120 | 121 | /// Creates a new AquariWM state struct with the given `layout` and `windows`. 122 | pub fn with_tiling_layout_and_windows( 123 | x: i32, 124 | y: i32, 125 | width: u32, 126 | height: u32, 127 | windows: impl IntoIterator, 128 | settings: LayoutSettings, 129 | ) -> Self 130 | where 131 | Manager: layout::TilingLayoutManager, 132 | { 133 | let mut aquariwm = Self { 134 | layout: CurrentLayout::new_tiled::(x, y, width, height, &settings), 135 | settings, 136 | 137 | windows: HashMap::new(), 138 | }; 139 | 140 | aquariwm.add_windows(windows); 141 | 142 | aquariwm 143 | } 144 | 145 | pub fn add_window(&mut self, window: Window, mapped: MapState) { 146 | let state = WindowState::new(mapped); 147 | 148 | if state.mode == layout::Mode::Tiled && state.mapped == MapState::Mapped { 149 | if let CurrentLayout::Tiled(manager) = &mut self.layout { 150 | manager.add_window(window.clone()); 151 | } 152 | } 153 | 154 | self.windows.insert(window, state); 155 | } 156 | 157 | #[inline] 158 | pub fn add_windows(&mut self, windows: impl IntoIterator) { 159 | for (window, mapped) in windows { 160 | self.add_window(window, mapped); 161 | } 162 | } 163 | 164 | /// Updates AquariWM's state to reflect the given `window` being destroyed. 165 | /// 166 | /// In order to apply any changes that may have been made to the tiling layout, 167 | /// [`apply_changes`] 168 | #[cfg_attr(feature = "async", doc = "or [`apply_changes_async`](Self::apply_changes_async)")] 169 | /// must be called. 170 | /// 171 | /// [`apply_changes`]: Self::apply_changes 172 | pub fn remove_window(&mut self, window: &Window) { 173 | let state = self.windows.remove(window); 174 | 175 | // Remove the window from the tiling layout if needed. 176 | if let Some(state) = state { 177 | if state.mode == layout::Mode::Tiled && state.mapped == MapState::Mapped { 178 | if let CurrentLayout::Tiled(manager) = &mut self.layout { 179 | manager.remove_window(window); 180 | } 181 | } 182 | } 183 | } 184 | 185 | /// Updates AquariWM's state to reflect the given `window` being [mapped]. 186 | /// 187 | /// In order to apply any changes that may have been made to the tiling layout, 188 | /// [`apply_changes`] 189 | #[cfg_attr(feature = "async", doc = "or [`apply_changes_async`](Self::apply_changes_async)")] 190 | /// must be called. 191 | /// 192 | /// [mapped]: MapState::Mapped 193 | /// [`apply_changes`]: Self::apply_changes 194 | pub fn map_window(&mut self, window: &Window) { 195 | let state = self 196 | .windows 197 | .get_mut(window) 198 | .expect("the window we are attempting to map is not tracked"); 199 | 200 | if state.mode == layout::Mode::Tiled && state.mapped == MapState::Unmapped { 201 | if let CurrentLayout::Tiled(manager) = &mut self.layout { 202 | manager.add_window(window.clone()); 203 | } 204 | } 205 | 206 | state.set_mapped(); 207 | } 208 | 209 | /// Updates AquariWM's state to reflect the given `window` being [unmapped]. 210 | /// 211 | /// In order to apply any changes that may have been made to the tiling layout, 212 | /// [`apply_changes`] 213 | #[cfg_attr(feature = "async", doc = "or [`apply_changes_async`](Self::apply_changes_async)")] 214 | /// must be called. 215 | /// 216 | /// [unmapped]: MapState::Unmapped 217 | /// [`apply_changes`]: Self::apply_changes 218 | pub fn unmap_window(&mut self, window: &Window) { 219 | let state = self 220 | .windows 221 | .get_mut(window) 222 | .expect("the window we are attempting to unmap is not tracked"); 223 | 224 | if state.mode == layout::Mode::Tiled && state.mapped == MapState::Mapped { 225 | if let CurrentLayout::Tiled(manager) = &mut self.layout { 226 | manager.remove_window(window); 227 | } 228 | } 229 | 230 | state.set_unmapped(); 231 | } 232 | 233 | /// Applies changes made by the [layout manager] by calling [`apply_resizes`] with the given 234 | /// `resize_window` function. 235 | /// 236 | /// [layout manager]: layout::TilingLayoutManager 237 | /// [`apply_resizes`]: layout::GroupNode::apply_changes 238 | #[cfg_attr( 239 | feature = "async", 240 | doc = "", 241 | doc = " # See also", 242 | doc = "[`apply_changes_async`] allows using a `resize_window` function that returns a", 243 | doc = "[future].", 244 | doc = "", 245 | doc = "[`apply_changes_async`]: Self::apply_changes_async", 246 | doc = "[future]: Future" 247 | )] 248 | pub fn apply_changes( 249 | &mut self, 250 | mut reconfigure_window: impl FnMut(&Window, i32, i32, u32, u32) -> Result<(), Error>, 251 | ) -> Result<(), Error> { 252 | if let CurrentLayout::Tiled(manager) = &mut self.layout { 253 | manager 254 | .layout_mut() 255 | .apply_changes(&mut reconfigure_window, &self.settings)?; 256 | } 257 | 258 | Ok(()) 259 | } 260 | 261 | #[doc(cfg(feature = "async"))] 262 | /// Applies changes made by the [layout manager] by calling `apply_changes` with the given 263 | /// `resize_window` function. 264 | /// 265 | /// # See also 266 | /// [`apply_changes`] allows using a `resize_window` function that doesn't return a [future]. 267 | /// 268 | /// [layout manager]: layout::TilingLayoutManager 269 | /// [future]: Future 270 | /// 271 | /// [`apply_changes`]: Self::apply_changes 272 | #[cfg(feature = "async")] 273 | pub async fn apply_changes_async( 274 | &mut self, 275 | mut reconfigure_window: impl FnMut(&Window, i32, i32, u32, u32) -> ResizeWindowFuture, 276 | ) -> Result<(), Error> 277 | where 278 | ResizeWindowFuture: Future>, 279 | { 280 | if let CurrentLayout::Tiled(manager) = &mut self.layout { 281 | // Add all the `resize_window` futures to this list... 282 | let mut futures = Vec::new(); 283 | 284 | manager.layout_mut().apply_changes( 285 | &mut |window, x, y, width, height| -> Result<(), Error> { 286 | futures.push(reconfigure_window(window, x, y, width, height)); 287 | 288 | Ok(()) 289 | }, 290 | &self.settings, 291 | )?; 292 | 293 | // Await all the `resize_window` futures. 294 | future::try_join_all(futures).await?; 295 | } 296 | 297 | Ok(()) 298 | } 299 | } 300 | --------------------------------------------------------------------------------