Integrate native Rime and stabilize input, output, and rendering
- Add optional modifier-only Super binding whose tap is cancelled by other input, focus changes, grabs, locks, and session teardown. - Ship the native ferese-rime user service, Rime defaults, installer inventory, package dependencies, session environment, and docs. - Vendor Smithay 8a27023 with the SHM texture-cache format fix and add GPU regressions for format changes and resize snapshots. - Release reused CRTC owners before staging output modes, then restore changed outputs before former owners when a transaction fails. - Wait for resize-capture GPU completion, expand filtered surface damage, and preserve IME popup origin under fractional scaling. - Mark weekends like holidays in both calendar layouts.
This commit is contained in:
1 parent
a2be421974
commit
993bc0f88f
391 files changed
+130908
-76
No files matched your search
@@ -0,0 +1,21 @@
|
||||
name: Bug Report
|
||||
description: File a bug report
|
||||
type: bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
Please don't use generative AI to generate the text of this issue.
|
||||
See our [AI Policy](https://github.com/Smithay/smithay/blob/master/AI.md) for more information.
|
||||
- type: checkboxes
|
||||
id: terms
|
||||
attributes:
|
||||
label: AI Policy
|
||||
options:
|
||||
- label: I have read the [AI policy](https://github.com/Smithay/smithay/blob/master/AI.md).
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Issue description
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
## Description
|
||||
<!-- Please include a summary of the change -->
|
||||
<!-- Any details that you think are important to review this PR? -->
|
||||
<!-- Are there issues / other PRs related to this one? -->
|
||||
|
||||
<!-- If your work contains any usage of generative AI, please read our Policy (https://github.com/Smithay/smithay/blob/master/AI.md) and consider alternatives before submitting this PR. -->
|
||||
|
||||
## Checklist
|
||||
<!-- You need to set this checkbox, for your PR to be considered. -->
|
||||
* [ ] I agree to smithay's [Developer Certificate of Origin](https://github.com/Smithay/smithay/blob/master/DCO.md).
|
||||
+441
@@ -0,0 +1,441 @@
|
||||
name: Continuous Integration
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
format:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- name: Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
- name: Cargo cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-rust_stable-${{ hashFiles('**/Cargo.toml') }}
|
||||
- name: Format
|
||||
run: cargo fmt --all -- --check
|
||||
|
||||
clippy-check:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- name: Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy
|
||||
- name: Get date for registry cache
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
||||
- name: Cargo registry cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-registry-${{ steps.date.outputs.date }}
|
||||
restore-keys: ${{ runner.os }}-cargo-registry-
|
||||
- name: Get cargo-cache latest version
|
||||
id: cargocacheversion
|
||||
run: echo "::set-output name=version::$(cargo search 'cargo-cache' --limit 1 | head -n 1 | cut -d ' ' -f 3 | cut -d '"' -f 2)"
|
||||
- name: Cargo binaries cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/cargo-cache
|
||||
~/.cargo/.crates.toml
|
||||
~/.cargo/.crates2.json
|
||||
key: ${{ runner.os }}-cargo-binaries-${{ steps.cargocacheversion.outputs.version }}
|
||||
- name: Install cargo-cache
|
||||
run: cargo install cargo-cache --version ${{ steps.cargocacheversion.outputs.version }}
|
||||
- name: Clean cargo cache of old items
|
||||
run: cargo cache clean-unref
|
||||
- name: System dependencies
|
||||
run: sudo apt-get update; sudo apt-get install -y libdrm-dev libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev libdisplay-info-dev
|
||||
- name: Clippy Smithay
|
||||
run: cargo clippy --features "test_all_features" -- -D warnings
|
||||
- name: Clippy Anvil
|
||||
run: cargo clippy --manifest-path "./anvil/Cargo.toml" --features "test_all_features" --no-deps -- -D warnings
|
||||
- name: Clippy Smallvil
|
||||
run: cargo clippy --manifest-path "./smallvil/Cargo.toml" --no-deps -- -D warnings
|
||||
- name: Clippy drm-extras
|
||||
run: cargo clippy --manifest-path "./smithay-drm-extras/Cargo.toml" --no-deps -- -D warnings
|
||||
|
||||
check-msrv:
|
||||
env:
|
||||
RUSTFLAGS: "-D warnings"
|
||||
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- smithay-check-features
|
||||
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- name: Rust toolchain
|
||||
uses: dtolnay/[email protected]
|
||||
- name: Get date for registry cache
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
||||
- name: Cargo registry cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-registry-${{ steps.date.outputs.date }}
|
||||
restore-keys: ${{ runner.os }}-cargo-registry-
|
||||
- name: System dependencies
|
||||
run: sudo apt-get update; sudo apt-get install -y libdrm-dev libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev
|
||||
- name: Check
|
||||
run: cargo check --features "test_all_features"
|
||||
|
||||
check-minimal:
|
||||
env:
|
||||
RUSTFLAGS: "-D warnings"
|
||||
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- smithay-check-features
|
||||
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
- name: Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
- name: Get date for registry cache
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
||||
- name: Cargo registry cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-registry-${{ steps.date.outputs.date }}
|
||||
restore-keys: ${{ runner.os }}-cargo-registry-
|
||||
- name: System dependencies
|
||||
run: sudo apt-get update; sudo apt-get install -y libdrm-dev libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev
|
||||
- name: Downgrade to minimal dependencies
|
||||
run: cargo -Z minimal-versions generate-lockfile
|
||||
# Needed to convert url -> string in winit
|
||||
- name: Selectively upgrade url
|
||||
run: cargo update -p [email protected] --precise 2.3.0
|
||||
# Needed to fix WlTouch trait bounds
|
||||
- name: Selectively upgrade client-toolkit
|
||||
run: cargo update -p [email protected] --precise 0.21.1
|
||||
- name: Rust toolchain
|
||||
uses: dtolnay/[email protected]
|
||||
- name: Check
|
||||
run: cargo check --features "test_all_features"
|
||||
|
||||
smithay-check-features:
|
||||
env:
|
||||
RUSTFLAGS: "-D warnings"
|
||||
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Get date for registry cache
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
||||
- name: Cargo registry cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-registry-${{ steps.date.outputs.date }}
|
||||
restore-keys: ${{ runner.os }}-cargo-registry-
|
||||
|
||||
- name: Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@cargo-hack
|
||||
|
||||
- name: System dependencies
|
||||
run: sudo apt-get update; sudo apt-get install -y libdrm-dev libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev
|
||||
|
||||
- name: Test features
|
||||
env:
|
||||
RUST_BACKTRACE: full
|
||||
run: cargo hack check --each-feature --no-dev-deps --exclude-features use_bindgen
|
||||
|
||||
smithay-tests:
|
||||
needs:
|
||||
- smithay-check-features
|
||||
|
||||
env:
|
||||
RUSTFLAGS: "-D warnings"
|
||||
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Get date for registry cache
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
||||
- name: Cargo registry cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-registry-${{ steps.date.outputs.date }}
|
||||
restore-keys: ${{ runner.os }}-cargo-registry-
|
||||
|
||||
- name: Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: System dependencies
|
||||
run: sudo apt-get update; sudo apt-get install -y libdrm-dev libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev libpixman-1-dev
|
||||
|
||||
- name: Run tests
|
||||
env:
|
||||
RUST_BACKTRACE: full
|
||||
run: cargo test --features "test_all_features"
|
||||
|
||||
smallvil-check:
|
||||
needs:
|
||||
- smithay-check-features
|
||||
|
||||
env:
|
||||
RUSTFLAGS: "-D warnings"
|
||||
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Get date for registry cache
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
||||
- name: Cargo registry cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-registry-${{ steps.date.outputs.date }}
|
||||
restore-keys: ${{ runner.os }}-cargo-registry-
|
||||
|
||||
- name: Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: System dependencies
|
||||
run: sudo apt-get update; sudo apt-get install -y libxkbcommon-dev libegl1-mesa-dev libwayland-dev
|
||||
|
||||
- name: Test smallvil
|
||||
env:
|
||||
RUST_BACKTRACE: full
|
||||
run: cargo check --manifest-path "./smallvil/Cargo.toml"
|
||||
|
||||
anvil-check-features:
|
||||
needs:
|
||||
- smithay-check-features
|
||||
|
||||
env:
|
||||
RUSTFLAGS: "-D warnings"
|
||||
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Get date for registry cache
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
||||
- name: Cargo registry cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-registry-${{ steps.date.outputs.date }}
|
||||
restore-keys: ${{ runner.os }}-cargo-registry-
|
||||
|
||||
- name: Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@cargo-hack
|
||||
|
||||
- name: System dependencies
|
||||
run: sudo apt-get update; sudo apt-get install -y libdrm-dev libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev libdisplay-info-dev
|
||||
|
||||
- name: Test features
|
||||
env:
|
||||
RUST_BACKTRACE: full
|
||||
run: cargo hack check --each-feature --manifest-path "./anvil/Cargo.toml" --exclude-features profile-with-puffin,profile-with-tracy,profile-with-tracy-mem
|
||||
|
||||
anvil-wlcs:
|
||||
needs:
|
||||
- anvil-check-features
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- job_name: "Core tests"
|
||||
gtest_filter: "SelfTest*:FrameSubmission*"
|
||||
flag: core
|
||||
- job_name: "Output tests"
|
||||
gtest_filter: "XdgOutputV1Test*"
|
||||
flag: output
|
||||
- job_name: "Pointer input tests"
|
||||
gtest_filter: "*/SurfacePointerMotionTest*:RelativePointer*"
|
||||
flag: pointer-input
|
||||
- job_name: "Bad Buffer Test"
|
||||
gtest_filter: "BadBufferTest*"
|
||||
flag: buffer
|
||||
|
||||
name: "WLCS: ${{ matrix.job_name }}"
|
||||
|
||||
env:
|
||||
RUSTFLAGS: "-D warnings"
|
||||
GRCOV_VERSION: "0.8.10"
|
||||
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Get date for registry cache
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
||||
- name: Cargo registry cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-registry-${{ steps.date.outputs.date }}
|
||||
restore-keys: ${{ runner.os }}-cargo-registry-
|
||||
|
||||
- name: Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: llvm-tools-preview
|
||||
|
||||
- name: grcov cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/grcov
|
||||
~/.cargo/.crates.toml
|
||||
~/.cargo/.crates2.json
|
||||
key: ${{ runner.os }}-grcov-${{ env.GRCOV_VERSION }}
|
||||
|
||||
- name: Install grcov
|
||||
run: cargo install grcov --version ${{ env.GRCOV_VERSION }}
|
||||
|
||||
- name: System dependencies
|
||||
run: sudo apt-get update; sudo apt-get install -y cmake libgtest-dev google-mock libboost-dev libxkbcommon-dev libwayland-dev libdisplay-info-dev
|
||||
|
||||
- name: WLCS build cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: wlcs
|
||||
key: ${{ runner.os }}-build-wlcs-${{ hashFiles('./compile_wlcs.sh') }}
|
||||
|
||||
- name: Build WLCS
|
||||
run: ./compile_wlcs.sh
|
||||
|
||||
- name: Build anvil WLCS plugin
|
||||
env:
|
||||
RUST_BACKTRACE: full
|
||||
RUSTFLAGS: "-Cinstrument-coverage --cfg coverage"
|
||||
run: cargo build --manifest-path "./wlcs_anvil/Cargo.toml"
|
||||
|
||||
- name: Run test suite
|
||||
run: ./wlcs/wlcs ./target/debug/libwlcs_anvil.so --gtest_filter="${{ matrix.gtest_filter }}"
|
||||
env:
|
||||
LLVM_PROFILE_FILE: "anvil-wlcs-%p-%m.profraw"
|
||||
|
||||
- name: Coverage
|
||||
run: grcov . --binary-path ./target/debug -s . -t lcov --branch --llvm --ignore-not-existing --ignore '*/examples/*' --ignore '*/tests/*' --ignore '*/.cargo/registry/*' --excl-br-start "mod tests \{" --excl-start "mod tests \{" --excl-br-line "#\[derive\(" --excl-line "#\[derive\(" -o lcov.info
|
||||
|
||||
- name: Upload to codecov.io
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
flags: wlcs-${{ matrix.flag }}
|
||||
|
||||
doc:
|
||||
name: Documentation on Github Pages
|
||||
runs-on: ubuntu-24.04
|
||||
needs:
|
||||
- smithay-check-features
|
||||
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Get date for registry cache
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
||||
- name: Cargo registry cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index
|
||||
~/.cargo/registry/cache
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-registry-${{ steps.date.outputs.date }}
|
||||
restore-keys: ${{ runner.os }}-cargo-registry-
|
||||
|
||||
- name: Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: nightly-2025-09-26
|
||||
|
||||
- name: System dependencies
|
||||
run: sudo apt-get update; sudo apt-get install -y libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev
|
||||
|
||||
- name: Build Documentation
|
||||
env:
|
||||
RUSTDOCFLAGS: --cfg=docsrs -D warnings
|
||||
run: cargo doc --no-deps --features "test_all_features" -p smithay -p calloop:0.14 -p drm -p gbm -p input -p udev -p wayland-server -p wayland-backend -p wayland-protocols -p winit -p x11rb -p tracing
|
||||
|
||||
- name: Setup index
|
||||
run: cp ./doc_index.html ./target/doc/index.html
|
||||
|
||||
- name: Upload static files as artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: ./target/doc
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
uses: actions/deploy-pages@v4
|
||||
|
||||
permissions:
|
||||
pages: write
|
||||
id-token: write
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: check_typos
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
branches:
|
||||
- '**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: typos-action
|
||||
uses: crate-ci/[email protected]
|
||||
@@ -0,0 +1,6 @@
|
||||
target
|
||||
Cargo.lock
|
||||
*.bk
|
||||
.vscode
|
||||
.vagga
|
||||
/wlcs
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
max_width = 110
|
||||
Vendored
+30
@@ -0,0 +1,30 @@
|
||||
The following policy applies if you make a contribution to Smithay's
|
||||
code, documentation, issues, comments, artwork or any other type of resource.
|
||||
|
||||
It is your responsibility to ensure that:
|
||||
- You fully understand how the code in your contribution works.
|
||||
- Your change is accurately described in the commit message.
|
||||
- Your contribution is tested and working as described.
|
||||
- To the best of your knowledge, you are not introducing bugs; any exceptions to this rule must be presented with clear reasoning.
|
||||
- You are not violating copyright (such as software licenses, creative commons or any other). (See also our [DCO](https://github.com/Smithay/smithay/blob/master/DCO.md).)
|
||||
- Use of any AI generated code or documentation is clearly indicated in your commit messages.
|
||||
|
||||
If you repeatedly fail to meet these standards, your contributions will no longer
|
||||
be considered by Smithay's maintainers and possibly removed without further notice.
|
||||
|
||||
Due to the requirements listed above, we strongly advise against using large
|
||||
language models or other probabilistic tools such as Copilot and ChatGPT.
|
||||
Code produced by these systems is usually more difficult to reason about
|
||||
and fails to express important technical design decisions in a concise form.
|
||||
This results in additional maintainer burden, draining the (already limited)
|
||||
time and energy of our reviewers. Additionally, the legal circumstances of the training sets these
|
||||
tools are based on are subject of active legal proceedings, which puts the copyright of generated
|
||||
contributions into question.
|
||||
|
||||
Last but not least, we ask you to not use generative AI to write comments or descriptions
|
||||
on our issue tracker, discussions and pull requests. In our experience these tools tend to generate
|
||||
very inconcise text and more often incorrect statements about the cause of underlying issues or code.
|
||||
|
||||
If you rely on such tools to make up for a language barrier, we strongly encourage you to use
|
||||
(ML-based or otherwise) translation tools as our maintainers much prefer to deal with
|
||||
imperfect english than generated walls of text. Many of us aren't native english speakers as well.
|
||||
Vendored
+841
@@ -0,0 +1,841 @@
|
||||
# Smithay Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Breaking changes
|
||||
|
||||
`wayland::virtual_keyboard` no longer sends key events to focused clients on its
|
||||
own. A virtual keyboard is now an regular input device:
|
||||
`VirtualKeyboardManagerState::new` requires the state to implement the new
|
||||
`VirtualKeyboardHandler`, which receives `InputEvent<VirtualKeyboardBackend>`s
|
||||
for the compositor to handle like real devices (so special keys, compositor
|
||||
bindings, idle handling and focus works properly). The compositor has to
|
||||
activate the device's keymap (`VirtualKeyboardDevice::keymap`) before processing
|
||||
its key events. Modifiers set with the `modifiers` request are not reset when
|
||||
the device is destroyed (the masks are absolute, so that would also clear state
|
||||
not set by the device, like locks set by other devices on a shared keymap). The
|
||||
compositor is responsible for reconciling the modifier state on `DeviceRemoved`
|
||||
(e.g., by tracking the bits set by it). `VirtualKeyboardUserData` is no longer
|
||||
generic over the state type.
|
||||
|
||||
`crate::wayland::selection::data_device::start_dnd` was removed in favor of exposing the
|
||||
underlying `DnDGrab` and associated types to make it possible to write external Drag&Drop sources
|
||||
and targets (e.g. for other shell implementations). See the `Additions`-section for more info.
|
||||
|
||||
This also means the `ServerDndGrabHandler` was removed. The `ClientDndGrabHandler` was split into
|
||||
a generic `DndGrabHandler` and a specific `WaylandDndGrabHandler`. `WaylandDndGrabHandler::started` needs to
|
||||
be implemented and explicitly start a `DnDGrab` for wayland DnD-operations to work.
|
||||
|
||||
`X11WM::start_wm` now needs a `DisplayHandle`-reference and adds requirements for the State type to
|
||||
implement `DndGrabHandler` and the `SeatHandler`s `PointerFocus` and `TouchFocus`-types to implement
|
||||
the new `DndFocus` trait.
|
||||
|
||||
`GbmFramebufferExporter::new` now accepts a `NodeFilter` as import node, which
|
||||
enables accepting dmabufs originating from any node.
|
||||
|
||||
```diff
|
||||
-GbmFramebufferExporter::new(gbm, node);
|
||||
+GbmFramebufferExporter::new(gbm, node.into());
|
||||
|
||||
-GbmFramebufferExporter::new(gbm, None);
|
||||
+GbmFramebufferExporter::new(gbm, NodeFilter::None);
|
||||
|
||||
-GbmFramebufferExporter::new(gbm, Some(node));
|
||||
+GbmFramebufferExporter::new(gbm, node.into());
|
||||
|
||||
+GbmFramebufferExporter::new(gbm, NodeFilter::All);
|
||||
```
|
||||
|
||||
`DrmOutputManager` does now provide an explicit `lock`-method returning a [`LockedDrmOutputManager`].
|
||||
All other methods have moved to this new type, allowing compositors to take the same lock across multiple
|
||||
operations.
|
||||
|
||||
xdg_shell, layer_shell, and session_lock surfaces now track `last_acked` state for each commit.
|
||||
You can find it in the `last_acked` field of the corresponding `...CachedState` structs.
|
||||
|
||||
The "current state" and "current serial" attribute fields were removed; use `last_acked` from the new `with_committed_state()` accessors instead (and verify that you didn't actually want `with_pending_state()` instead where you used the "current state").
|
||||
|
||||
```diff
|
||||
-ToplevelSurface::current_state();
|
||||
+ToplevelSurface::with_committed_state(|state| {
|
||||
+ // ...
|
||||
+});
|
||||
|
||||
-PopupSurface::current_state();
|
||||
+PopupSurface::with_committed_state(|state| {
|
||||
+ // ...
|
||||
+});
|
||||
|
||||
-LayerSurface::current_state();
|
||||
+LayerSurface::with_committed_state(|state| {
|
||||
+ // ...
|
||||
+});
|
||||
|
||||
-LockSurface::current_state();
|
||||
+LockSurface::with_committed_state(|state| {
|
||||
+ // ...
|
||||
+});
|
||||
|
||||
struct XdgToplevelSurfaceRoleAttributes {
|
||||
- configured: bool,
|
||||
- configure_serial: Option<Serial>,
|
||||
- current: ToplevelState,
|
||||
- current_serial: Option<Serial>,
|
||||
- last_acked: Option<ToplevelState>,
|
||||
+ last_acked: Option<ToplevelConfigure>,
|
||||
// ...
|
||||
}
|
||||
|
||||
struct XdgPopupSurfaceRoleAttributes {
|
||||
- configured: bool,
|
||||
- configure_serial: Option<Serial>,
|
||||
- current: PopupState,
|
||||
- current_serial: Option<Serial>,
|
||||
- committed: bool,
|
||||
- last_acked: Option<PopupState>,
|
||||
+ last_acked: Option<PopupConfigure>,
|
||||
// ...
|
||||
}
|
||||
|
||||
struct LayerSurfaceAttributes {
|
||||
- configured: bool,
|
||||
- configure_serial: Option<Serial>,
|
||||
- current: LayerSurfaceState,
|
||||
- last_acked: Option<LayerSurfaceState>,
|
||||
+ last_acked: Option<LayerSurfaceConfigure>,
|
||||
// ...
|
||||
}
|
||||
|
||||
-impl Copy for LayerSurfaceCachedState;
|
||||
```
|
||||
|
||||
The following methods are no longer needed as Smithay does them automatically now:
|
||||
```diff
|
||||
-ToplevelSurface::reset_initial_configure_sent();
|
||||
-PopupSurface::reset_initial_configure_sent();
|
||||
```
|
||||
|
||||
You also no longer need to manually set `LayerSurfaceAttributes::initial_configure_sent`, Smithay handles it automatically.
|
||||
|
||||
The `delegate_*!` macros have been replaced with a single `delegate_dispatch2!` macro, which implements dispatch in terms
|
||||
of new `Dipsatch2` and `GlobalDispatch2` traits. These will replace `Dispatch` and `GlobalDispatch` in a future version of
|
||||
`wayland_server`.
|
||||
|
||||
The `SpaceElement::geometry` impl for `X11Surface` is no longer equivalent to its bbox. For clients that set
|
||||
`_GTK_FRAME_EXTENTS`, the shadow extents are subtracted from the bbox to give the geometry.
|
||||
|
||||
`X11Surface::geometry` has been renamed to `X11Surface::last_configure`. `X11Surface::geometry` now returns the bounding
|
||||
box minus the surface's frame extents.
|
||||
|
||||
`SessionLockHandler::unlock` will no longer be called if the `ext_session_lock_v1` instance that sent the
|
||||
`unlock_and_destroy` request is not the same instance as the one that successfully locked the session.
|
||||
|
||||
`SessionLockHandler::new_surface` can be called for multiple `SessionLocker` instances that could be in-flight at the same
|
||||
time. `LockSurface::ext_session_lock` has been added to allow the compositor to disambiguate, and associate lock surfaces
|
||||
with the correct lock instance.
|
||||
|
||||
`backend::input` and `input` now use `backend::input::InputTime` for timestamps instead of `u32` or `u64`. `InputTime::now()`
|
||||
is used for timestamps for synthesized events.
|
||||
|
||||
`TextInputActivation` must be implemented. It allows for hooks to be run when a text input is `activated` and `deactivated`.
|
||||
|
||||
`KeyboardHandle::input_intercept()` now has a `source` argument and early-returns `None` when the key was already pressed/released, matching the behavior of `KeyboardHandle::input()`.
|
||||
|
||||
```diff
|
||||
-let (res, mods_changed) = keyboard.input_intercept(data, keycode, state, filter);
|
||||
+let Some((res, mods_changed)) = keyboard.input_intercept(
|
||||
+ KeyboardSource::MAIN,
|
||||
+ data,
|
||||
+ keycode,
|
||||
+ state,
|
||||
+ filter,
|
||||
+) else {
|
||||
+ return;
|
||||
+};
|
||||
```
|
||||
|
||||
### Additions
|
||||
|
||||
- Add `WmWindowProperty::Other` to forward unrecognized X11 property changes to the compositor.
|
||||
|
||||
|
||||
- The wlr virtual pointer protocol is now available in `smithay::wayland::virtual_pointer`.
|
||||
|
||||
- `Xkb::keymap_as_string` returns the active keymap as `XKB_KEYMAP_FORMAT_TEXT_V1`.
|
||||
|
||||
- `XkbContext::set_modifier_mask` applies raw xkb modifier masks to a keyboard
|
||||
for clients that report modifier state directly instead of through key presses
|
||||
(e.g., `zwp_virtual_keyboard_v1.modifiers`).
|
||||
|
||||
- `KeyboardHandle::input_forward_bypassing_grab` sends a key skipping the active
|
||||
grab, which can be used by compositors to prevent infinite loops when a client
|
||||
with a grab forwards a key through its own virtual keyboard, which can be
|
||||
tracked with `InputMethodHandle::keyboard_grab_client` and
|
||||
`VirtualKeyboardDevice::client`.
|
||||
|
||||
- `VirtualKeyboardDevice::source` gives each virtual keyboard its own
|
||||
`KeyboardSource` so keys pressed on multiple devices are released when the
|
||||
last one is released.
|
||||
|
||||
- ExtBackgroundEffect protocol is now available in `smithay::wayland::background_effect` module.
|
||||
|
||||
`crate::input::dnd` was introduced to enable implementation of Drag&Drop operations on custom types.
|
||||
Internally the same types and traits are used to implement `wayland::data_device` dnd-operations and XDND
|
||||
operations (see below).
|
||||
|
||||
`DnDGrab` is the new entry-point for DnD operations. It requires `SeatHandler::PointerFocus` or
|
||||
`SeatHandler::TouchFocus` respectively to implement `DndFocus`, which is used to send events to
|
||||
types, which can be targets of DnD operations. `Source` is a new trait for types that represent
|
||||
sources of data for DnD operations.
|
||||
|
||||
`Source::accepted` is called when a target accepts a mime-type, or rejects the offer with `None`.
|
||||
Sources that proxy an upstream offer need it: a nested compositor forwarding a drag to its own
|
||||
clients only receives `drop` from the host compositor once it has itself accepted on the upstream
|
||||
offer, and previously had no way to know when a nested client had done so. It has a default no-op
|
||||
implementation, so sources that originate their own data are unaffected.
|
||||
|
||||
The Xwayland WM can now handle XDND operations and bridge them over to the new generic DnD interface
|
||||
allowing DnD operations between X11 and Wayland clients (both directions).
|
||||
|
||||
`X11Surface` now has a new `surface_under`-method, which is also internally used by `SpaceElement::is_in_input_region` and `crate::desktop::Window::surface_under`. Any direct usage of `under_from_surface_tree` on the underlying `wl_surface` of an `X11Surface` should be replaced with this method for XDND to work correctly.
|
||||
|
||||
`X11Surface` now parses and exposes the Motif WM hints via `motif_hints()`.
|
||||
|
||||
`X11Wm` allows you to remove and clear XSETTINGS settings.
|
||||
|
||||
xdg_shell and layer_shell now enforce the client acking a configure before committing a buffer, as required by the protocols.
|
||||
|
||||
```rs
|
||||
struct ToplevelCachedState {
|
||||
/// Configure last acknowledged by the client at the time of the commit.
|
||||
last_acked: Option<ToplevelConfigure>,
|
||||
}
|
||||
|
||||
/// Provides access to the current committed cached state.
|
||||
fn ToplevelSurface::with_cached_state<T>(&self, f: impl FnOnce(&ToplevelCachedState) -> T) -> T;
|
||||
/// Provides access to the current committed state.
|
||||
fn ToplevelSurface::with_committed_state<T>(&self, f: impl FnOnce(Option<&ToplevelState>) -> T) -> T;
|
||||
|
||||
struct PopupCachedState {
|
||||
/// Configure last acknowledged by the client at the time of the commit.
|
||||
last_acked: Option<ToplevelConfigure>,
|
||||
}
|
||||
|
||||
/// Provides access to the current committed cached state.
|
||||
fn PopupSurface::with_cached_state<T>(&self, f: impl FnOnce(&PopupCachedState) -> T) -> T;
|
||||
/// Provides access to the current committed state.
|
||||
fn PopupSurface::with_committed_state<T>(&self, f: impl FnOnce(Option<&PopupState>) -> T) -> T;
|
||||
|
||||
struct LayerSurfaceCachedState {
|
||||
/// Configure last acknowledged by the client at the time of the commit.
|
||||
last_acked: Option<LayerSurfaceConfigure>,
|
||||
// ...
|
||||
}
|
||||
|
||||
/// Provides access to the current committed cached state.
|
||||
fn LayerSurface::with_cached_state<T>(&self, f: impl FnOnce(&LayerSurfaceCachedState) -> T) -> T;
|
||||
/// Provides access to the current committed state.
|
||||
fn LayerSurface::with_committed_state<T>(&self, f: impl FnOnce(Option<&LayerSurfaceState>) -> T) -> T;
|
||||
|
||||
struct LockSurfaceCachedState {
|
||||
/// Configure last acknowledged by the client at the time of the commit.
|
||||
last_acked: Option<LockSurfaceConfigure>,
|
||||
}
|
||||
|
||||
/// Provides access to the current committed cached state.
|
||||
fn LockSurface::with_cached_state<T>(&self, f: impl FnOnce(&LockSurfaceCachedState) -> T) -> T;
|
||||
/// Provides access to the current committed state.
|
||||
fn LockSurface::with_committed_state<T>(&self, f: impl FnOnce(Option<&LockSurfaceState>) -> T) -> T;
|
||||
|
||||
struct LockSurfaceAttributes {
|
||||
server_pending: Option<LockSurfaceState>,
|
||||
pending_configures: Vec<LockSurfaceConfigure>,
|
||||
last_acked: Option<LockSurfaceConfigure>,
|
||||
}
|
||||
|
||||
type LockSurfaceData = Mutex<LockSurfaceAttributes>;
|
||||
|
||||
struct LockSurfaceConfigure {
|
||||
state: LockSurfaceState,
|
||||
serial: Serial,
|
||||
}
|
||||
```
|
||||
|
||||
Added `Element::is_framebuffer_effect` and `RenderElement::capture_framebuffer` to better support render elements,
|
||||
that take the current framebuffers contents and modify them, e.g. blurring the backgrounds of windows.
|
||||
See the documentation for these functions for how to make use of them, but note, that they provide
|
||||
default implementations, which result in skipping the new functionality. As such any custom `RenderElements` wrappers
|
||||
(not using `crate::render_elements!`) need to be updated.
|
||||
|
||||
`Space` has new functionality that allows for more fine-grained control over window stacking:
|
||||
|
||||
- `map_element_above()`: maps a new element directly above an existing one.
|
||||
- `raise_element_above()`: same as above, but moves an already-mapped element.
|
||||
- `lower_element()`: lowers an element to the bottom of the stack, respecting its z-index group.
|
||||
- `relocate_element()`: moves an element to a new location in the space without changing the stacking order.
|
||||
|
||||
`Output` now has `owns_xdg_output()` which allows you to match an XDG output protocol object with an `Output`.
|
||||
|
||||
Added `Renderer::invalidate_caches`, which unconditionally drops all renderer-internal caches,
|
||||
including entries `Renderer::cleanup_texture_cache` retains while their source buffers are still
|
||||
alive.
|
||||
|
||||
Added `GpuManager::cleanup_texture_cache` and `GpuManager::invalidate_caches`, which apply the
|
||||
corresponding `Renderer` method to every enumerated device. `GpuManager::invalidate_caches`
|
||||
additionally drops the buffers cached for copying between a render and a target node, which no
|
||||
`Renderer` owns.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
`input_method_v2`: a second input method binding a seat that already has one is now the object told
|
||||
it is `unavailable`, per the protocol, leaving the existing instance in place. Previously the event
|
||||
went to the existing instance and the newcomer was dropped untracked. Destroying such an inert
|
||||
newcomer no longer clears the active instance, and its requests no longer drive the seat.
|
||||
|
||||
`DrmSurface::use_vrr(false)` now succeeds as a no-op when using legacy DRM. Attempts to enable VRR
|
||||
on legacy DRM still return `UnknownProperty`.
|
||||
|
||||
The `xdg_toplevel` request handler now posts an `invalid_size` error when a client performs a `set_max_size` or `set_min_size` request with a negative width or height.
|
||||
|
||||
`DrmDeviceFd::new` no longer tries to become DRM master on a render node, which the kernel always
|
||||
refuses, so it no longer warns about it.
|
||||
|
||||
`SimpleCrtcMapper` (in `smithay-drm-extras`) now releases the CRTC reservation of any connector that
|
||||
is no longer connected, including connectors that have disappeared from the resource list entirely
|
||||
rather than being reported as disconnected. Previously such connectors (for example DP-MST sink
|
||||
connectors when a dock is unplugged, or across suspend/resume) leaked their CRTC reservation
|
||||
indefinitely, which could accumulate until a newly connected output could no longer be assigned a
|
||||
CRTC.
|
||||
|
||||
XWayland now honors WM_HINTS request to map the window in IconicState(minimized). Compositors should
|
||||
check X11Surface::is_hidden() at mapping time and minimize the window appropriately.
|
||||
|
||||
Space::refresh() now clears stale outputs before emitting any enter events so no wl_surface.enter events
|
||||
are emitted with a stale wl_output anymore.
|
||||
|
||||
The XWayland WM implementation previously incorrectly prefixed large transfers from X11 windows with
|
||||
the four INCR bytes.
|
||||
|
||||
`MultiRenderer::cleanup_texture_cache` now also cleans up the devices other than the render and
|
||||
target device. Buffers that cannot be imported on the render node directly are imported on their
|
||||
source device, so those devices accumulate cached imports that previously were never released.
|
||||
Cleanup is now also attempted on every device even if it fails on one of them, with every failure
|
||||
logged and the first error returned.
|
||||
|
||||
`send_frames_surface_tree` (and `Window::send_frame`, `LayerSurface::send_frame`) no longer sends frame
|
||||
callbacks to surfaces whose renderer state has no buffer, or to their subsurfaces.
|
||||
|
||||
`GlesRenderer::cleanup_texture_cache` and `GlesRenderer::invalidate_caches` now only make their EGL
|
||||
context current when resources are queued for destruction. This avoids waking idle GPUs and
|
||||
blocking the compositor while they resume.
|
||||
|
||||
`ext_image_copy_capture_session_v1` now raises the `duplicate_frame` protocol error when a client
|
||||
calls `create_frame` while the session still has an active frame, as the protocol requires. Previously
|
||||
the request was accepted and the extra frame was silently added to the session.
|
||||
|
||||
## 0.7.0
|
||||
|
||||
### Breaking changes
|
||||
`DrmSyncobjHandler::drm_syncobj_state` is now Optional.
|
||||
Whenever state is None `ImportTimeline` request will result in `InvalidTimeline` protocol error.
|
||||
This allows one to destroy DrmSyncobjState by calling `DrmSyncobjState::into_global()`
|
||||
```diff
|
||||
-fn DrmSyncobjHandler::drm_syncobj_state(&mut self) -> &mut DrmSyncobjState
|
||||
+fn DrmSyncobjHandler::drm_syncobj_state(&mut self) -> Option<&mut DrmSyncobjState>
|
||||
```
|
||||
|
||||
`DrmTimeline` constructor now accepts OwnedFd:
|
||||
```diff
|
||||
-fn DrmTimeline::new(device: &DrmDeviceFd, fd: BorrowedFd<'_>) -> io::Result<Self>
|
||||
+fn DrmTimeline::new(device: &DrmDeviceFd, fd: OwnedFd) -> io::Result<Self>
|
||||
```
|
||||
`DrmSyncPoint::eventfd` now returns `Arc<OwnedFd>`:
|
||||
```diff
|
||||
-fn DrmSyncPoint::eventfd(&self) -> io::Result<OwnedFd>
|
||||
+fn DrmSyncPoint::eventfd(&self) -> io::Result<Arc<OwnedFd>>
|
||||
```
|
||||
`GbmFramebufferExporter` constructor now accepts `import_node`,
|
||||
it will be used to filter DMA-BUFs to only those originating from a specific device before considering them for direct scanout.
|
||||
If `import_node` is `None` direct-scanout of client-buffers won't be used.
|
||||
```diff
|
||||
-fn GbmFramebufferExporter::new(gbm: Device<A>) -> Self
|
||||
+fn GbmFramebufferExporter::new(gbm: Device<A>, import_node: Option<DrmNode>) -> Self
|
||||
```
|
||||
|
||||
### Additions
|
||||
|
||||
#### WeakDrmDeviceFd
|
||||
```rs
|
||||
/// Returns a weak reference to the underlying device
|
||||
fn DrmDeviceFd::downgrade(&self) -> WeakDrmDeviceFd;
|
||||
/// Construct an empty Weak reference, that will never upgrade successfully
|
||||
fn WeakDrmDeviceFd::new() -> Self;
|
||||
/// Try to upgrade to a strong reference
|
||||
fn WeakDrmDeviceFd::upgrade(&self) -> Option<DrmDeviceFd>;
|
||||
```
|
||||
|
||||
#### KeyboardHandle::set_modifier_state
|
||||
```rs
|
||||
/// Set the modifiers state.
|
||||
fn KeyboardHandle::set_modifier_state(&self, mods_state: ModifiersState) -> u32;
|
||||
/// Serialize modifier state back to be sent to xkb.
|
||||
fn ModifiersState::serialize_back(&self, state: &xkb::State) -> SerializedMods;
|
||||
```
|
||||
|
||||
#### XWaylandKeyboardGrab
|
||||
```rs
|
||||
/// Get the `zwp_xwayland_keyboard_grab_v1` object that created the grab
|
||||
fn XWaylandKeyboardGrab::grab(&self) -> &ZwpXwaylandKeyboardGrabV1;
|
||||
/// Grab is now cloneable
|
||||
impl Clone for XWaylandKeyboardGrab;
|
||||
```
|
||||
|
||||
#### X11Surface
|
||||
```rs
|
||||
/// Returns the hints for the underlying X11 window
|
||||
fn X11Surface::hints(&self) -> Option<x11rb::properties::WmHints>;
|
||||
/// Get the client PID associated with the X11 window.
|
||||
fn X11Surface::get_client_pid(&self) -> Result<u32, Box<dyn Error>>;
|
||||
```
|
||||
|
||||
#### DrmSyncobjState
|
||||
```rs
|
||||
/// Destroys the state and returns the `GlobalId` for compositors to disable/destroy.
|
||||
fn DrmSyncobjState::into_global(self) -> GlobalId;
|
||||
/// Sets a new `import_device` to import the syncobj fds and wait on them.
|
||||
fn DrmSyncobjState::update_device(&mut self, import_device: DrmDeviceFd);
|
||||
```
|
||||
|
||||
#### xdg-toplevel-tag-v1
|
||||
XdgToplevelTag protocol is now available in `smithay::wayland::xdg_toplevel_tag` module.
|
||||
|
||||
#### cursor-shape-v1
|
||||
CursorShape is now updated to version `2`
|
||||
|
||||
|
||||
## 0.6.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
`RenderContext::draw` callback now accepts a mutable reference
|
||||
```diff
|
||||
-fn smithay::backend::renderer::element::texture::RenderContext::draw(&mut self, f: impl FnOnce(&T))
|
||||
+fn smithay::backend::renderer::element::texture::RenderContext::draw(&mut self, f: impl FnOnce(&mut T))
|
||||
```
|
||||
|
||||
Framebuffer now requires `Texture` implementation
|
||||
```rs
|
||||
type smithay::backend::renderer::RendererSuper::Framebuffer: smithay::backend::renderer::Texture
|
||||
```
|
||||
|
||||
`Output::client_outputs` no longer returns a Vec
|
||||
```diff
|
||||
-fn smithay::output::Output::client_outputs(&self, client: &Client) -> Vec<WlOutput>;
|
||||
+fn smithay::output::Output::client_outputs(&self, client: &Client) -> impl Iterator<Item = WlOutput>;
|
||||
```
|
||||
DamageBag/DamageSnapshot damage getters got renamed
|
||||
```diff
|
||||
-fn smithay::backend::renderer::utils::DamageBag::damage(&self) -> impl Iterator<Item = impl Iterator<Item = &Rectangle>>
|
||||
+fn smithay::backend::renderer::utils::DamageBag::raw(&self) -> impl Iterator<Item = impl Iterator<Item = &Rectangle>>
|
||||
-fn smithay::backend::renderer::utils::DamageSnapshot::damage(&self) -> impl Iterator<Item = impl Iterator<Item = &Rectangle>>
|
||||
+fn smithay::backend::renderer::utils::DamageSnapshot::raw(&self) -> impl Iterator<Item = impl Iterator<Item = &Rectangle>>
|
||||
```
|
||||
RendererSurfaceState::damage now returns a DamageSnapshot
|
||||
```diff
|
||||
-fn smithay::backend::renderer::utils::RendererSurfaceState::damage(&self) -> impl core::iter::traits::iterator::Iterator<Item = impl core::iter::traits::iterator::Iterator<Item = &smithay::utils::Rectangle<i32, smithay::utils::Buffer>>>
|
||||
+fn smithay::backend::renderer::utils::RendererSurfaceState::damage(&self) -> smithay::backend::renderer::utils::DamageSnapshot<i32, smithay::utils::Buffer>
|
||||
```
|
||||
Client scale can now be fractional
|
||||
```diff
|
||||
-fn smithay::wayland::compositor::CompositorClientState::client_scale(&self) -> u32
|
||||
+fn smithay::wayland::compositor::CompositorClientState::client_scale(&self) -> f64
|
||||
-fn smithay::wayland::compositor::CompositorClientState::set_client_scale(&self, new_scale: u32)
|
||||
+fn smithay::wayland::compositor::CompositorClientState::set_client_scale(&self, new_scale: f64)
|
||||
```
|
||||
|
||||
Raw `renderer_id` got replaced with new `smithay::backend::renderer::ContextId` newtype
|
||||
```diff
|
||||
-fn smithay::backend::renderer::gles::GlesFrame::id(&self) -> usize;
|
||||
+fn smithay::backend::renderer::gles::GlesFrame::context_id(&self) -> ContextId;
|
||||
-fn smithay::backend::renderer::gles::GlesRenderer::id(&self) -> usize;
|
||||
+fn smithay::backend::renderer::gles::GlesRenderer::context_id(&self) -> ContextId;
|
||||
-fn smithay::backend::renderer::glow::GlowFrame::id(&self) -> usize;
|
||||
+fn smithay::backend::renderer::glow::GlowFrame::context_id(&self) -> ContextId;
|
||||
-fn smithay::backend::renderer::glow::GlowRenderer::id(&self) -> usize;
|
||||
+fn smithay::backend::renderer::glow::GlowRenderer::context_id(&self) -> ContextId;
|
||||
-fn smithay::backend::renderer::multigpu::MultiFrame::id(&self) -> usize;
|
||||
+fn smithay::backend::renderer::multigpu::MultiFrame::context_id(&self) -> ContextId;
|
||||
-fn smithay::backend::renderer::multigpu::MultiRenderer::id(&self) -> usize;
|
||||
+fn smithay::backend::renderer::multigpu::MultiRenderer::context_id(&self) -> ContextId;
|
||||
-fn smithay::backend::renderer::pixman::PixmanFrame::id(&self) -> usize;
|
||||
+fn smithay::backend::renderer::pixman::PixmanFrame::context_id(&self) -> ContextId;
|
||||
-fn smithay::backend::renderer::pixman::PixmanRenderer::id(&self) -> usize;
|
||||
+fn smithay::backend::renderer::pixman::PixmanRenderer::context_id(&self) -> ContextId;
|
||||
-fn smithay::backend::renderer::test::DummyFrame::id(&self) -> usize;
|
||||
+fn smithay::backend::renderer::test::DummyFrame::context_id(&self) -> ContextId;
|
||||
-fn smithay::backend::renderer::test::DummyRenderer::id(&self) -> usize;
|
||||
+fn smithay::backend::renderer::test::DummyRenderer::context_id(&self) -> ContextId;
|
||||
-fn smithay::backend::renderer::Frame::id(&self) -> usize;
|
||||
+fn smithay::backend::renderer::Frame::context_id(&self) -> ContextId;
|
||||
-fn smithay::backend::renderer::Renderer::id(&self) -> usize;
|
||||
+fn smithay::backend::renderer::Renderer::context_id(&self) -> ContextId;
|
||||
-fn smithay::backend::renderer::element::texture::TextureRenderElement::from_static_texture(id: Id, renderer_id: usize, ...) -> Self;
|
||||
+fn smithay::backend::renderer::element::texture::TextureRenderElement::from_static_texture(id: Id, context_id: ContextId, ...) -> Self;
|
||||
-fn smithay::backend::renderer::element::texture::TextureRenderElement::from_texture_with_damage(id: Id, renderer_id: usize, ...) -> Self;
|
||||
+fn smithay::backend::renderer::element::texture::TextureRenderElement::from_texture_with_damage(id: Id, context_id: ContextId, ...) -> Self;
|
||||
-fn smithay::backend::renderer::utils::RendererSurfaceState::texture<R>(&self, id: usize) -> Option<&TextureId>;
|
||||
+fn smithay::backend::renderer::utils::RendererSurfaceState::texture<R>(&self, id: &ContextId) -> Option<&TextureId>;
|
||||
```
|
||||
|
||||
`CursorShapeDeviceUserData` now has an additional generic argument
|
||||
```diff
|
||||
-struct smithay::wayland::cursor_shape::CursorShapeDeviceUserData;
|
||||
+struct smithay::wayland::cursor_shape::CursorShapeDeviceUserData<D: SeatHandler>;
|
||||
```
|
||||
|
||||
The explicit frame buffers got introduced, but for the sake of my sanity those changes are not described here, you can look at: https://github.com/Smithay/smithay/commit/df08c6f29eb6ebfa2fce6fc374590483bcbaf21a
|
||||
|
||||
### API Additions
|
||||
|
||||
It is now possible to check if the OpenGL context is shared with another.
|
||||
```rs
|
||||
fn smithay::backend::egl::context::EGLContext::is_shared();
|
||||
```
|
||||
It is now possible to check that there are no other references to the underlying GL texture.
|
||||
```rs
|
||||
fn smithay::backend::renderer::gles::GlesTexture::is_unique_reference();
|
||||
```
|
||||
|
||||
There is a new gles capability for support of fencing and exporting to EGL
|
||||
```rs
|
||||
smithay::backend::renderer::gles::Capability::ExportFence;
|
||||
```
|
||||
|
||||
There is a new BlitFrame trait for frames that support blitting contents from/to the current framebuffer to/from another.
|
||||
```rs
|
||||
trait smithay::backend::renderer::BlitFrame;
|
||||
impl BlitFrame for smithay::backend::renderer::gles::GlesFrame;
|
||||
impl BlitFrame for smithay::backend::renderer::glow::GlowFrame;
|
||||
impl BlitFrame for smithay::backend::renderer::multigpu::MultiFrame;
|
||||
```
|
||||
|
||||
It is now possible to iterate over all known tokens and their associated data
|
||||
```rs
|
||||
fn smithay::wayland::xdg_activation::XdgActivationState::tokens() -> impl Iterator<Item = (&XdgActivationToken, &XdgActivationTokenData)>;
|
||||
```
|
||||
|
||||
There are new errors for missing DRM crtc/connector/plane mapping
|
||||
```rs
|
||||
smithay::backend::drm::DrmError::{UnknownConnector, UnknownCrtc, UnknownPlane};
|
||||
```
|
||||
|
||||
Texture has a few new implementations
|
||||
```rs
|
||||
impl Texture for smithay::backend::renderer::gles::GlesTarget;
|
||||
impl Texture for smithay::backend::renderer::multigpu:MultiFramebuffer;
|
||||
impl Texture for smithay::backend::renderer::pixman::PixmanTarget;
|
||||
impl Texture for smithay::backend::renderer::test::DummyFramebuffer
|
||||
```
|
||||
|
||||
It is now possible to access WlKeyboard/WlPointer instances
|
||||
```rs
|
||||
fn smithay::input::keyboard::KeyboardHandle::client_keyboards(&self, client: &Client) -> impl Iterator<Item = WlKeyboard>;
|
||||
fn smithay::input::pointer::PointerHandle::client_pointers(&self, client: &:Client) -> impl Iterator<Item = WlPointer>;
|
||||
```
|
||||
|
||||
New APIs for X11 randr output management
|
||||
```rs
|
||||
enum smithay::xwayland::xwm::PrimaryOutputError { OutputUnknown, X11Error(x11rb::errors::ReplyError) };
|
||||
|
||||
impl From<x11rb::errors::ConnectionError> for smithay::xwayland::xwm::PrimaryOutputError;
|
||||
fn smithay::xwayland::xwm::PrimaryOutputError::from(value: x11rb::errors::ConnectionError) -> Self;
|
||||
fn smithay::xwayland::xwm::X11Wm::get_randr_primary_output(&self) -> Result<Option<String>, x11rb::errors::ReplyError>;
|
||||
fn smithay::xwayland::xwm::X11Wm::set_randr_primary_output(&mut self, output: Option<&smithay::output::Output>) -> Result<(), smithay::xwayland::xwm::PrimaryOutputError>;
|
||||
fn smithay::xwayland::xwm::XwmHandler::randr_primary_output_change(&mut self, xwm: smithay::xwayland::xwm::XwmId, output_name: Option<String>);
|
||||
```
|
||||
|
||||
It is now possible to get the DrmNode of the device the buffer was allocated on
|
||||
```rs
|
||||
fn smithay::backend::allocator::gbm::GbmBuffer::device_node(&self) -> Option<drm::node::DrmNode>;
|
||||
```
|
||||
|
||||
It is now possible to create a `GbmBuffer` from an existing `BufferObject` explicitly defining the device node
|
||||
```rs
|
||||
fn smithay::backend::allocator::gbm::GbmBuffer::from_bo_with_node(bo: gbm::buffer_object::BufferObject<()>, implicit: bool, drm_node: core::option::Option<drm::node::DrmNode>) -> Self;
|
||||
```
|
||||
|
||||
It is now possible to access the `Allocator` of this output manager
|
||||
```rs
|
||||
fn smithay::backend::drm::output::DrmOutputManager::allocator(&self) -> &Allocator;
|
||||
```
|
||||
|
||||
Is is now possible to check if EGLDevice is backed by actual device node or is it a software device.
|
||||
```rs
|
||||
fn smithay::backend::egl::EGLDevice::is_software(&self) -> bool
|
||||
```
|
||||
|
||||
This adds a way to query next deadline of a commit timing barrier.
|
||||
Allows a compositor to schedule re-evaluating commit timers without
|
||||
busy looping.
|
||||
```rs
|
||||
fn smithay::wayland::commit_timing::CommitTimerBarrierState::next_deadline(&self) -> Option<smithay::wayland::commit_timing::Timestamp>;
|
||||
```
|
||||
|
||||
Support for casting `Timestamp` back to `Time`
|
||||
This might be useful to compare the next deadline with a monotonic time
|
||||
from the presentation clock
|
||||
```rs
|
||||
impl From<smithay::wayland::commit_timing::Timestamp> for smithay::utils::Time;
|
||||
```
|
||||
|
||||
Support for creating a weak reference to a `Seat`
|
||||
```rs
|
||||
fn smithay::input::Seat::downgrade(&self) -> smithay::input::WeakSeat;
|
||||
|
||||
fn smithay::input::WeakSeat::is_alive(&self) -> bool;
|
||||
pub fn smithay::input::WeakSeat::upgrade(&self) -> Option<smithay::input::Seat>;
|
||||
```
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### API Changes
|
||||
Items either removed or deprecated from the public API
|
||||
```rs
|
||||
/// Use Clone instead
|
||||
impl Copy for smithay::utils::HookId;
|
||||
/// Use `from_extremities` instead
|
||||
fn smithay::utils::Rectangle::from_extemities(topleft, bottomright) -> Self;
|
||||
```
|
||||
|
||||
Items added to the public API
|
||||
```rs
|
||||
/// Replaces deprecated `from_extemities`
|
||||
fn smithay::utils::Rectangle::from_extremities(topleft, bottomright) -> Self;
|
||||
/// Access the active text-input instance for the currently focused surface.
|
||||
fn smithay::wayland::text_input::TextInputHandle::with_active_text_input(&self, f);
|
||||
/// Just a new protocol
|
||||
mod smithay::wayland::selection::ext_data_control;
|
||||
```
|
||||
|
||||
Items changed in the public API
|
||||
```diff
|
||||
# create_external_token now accepts `XdgActivationTokenData` instead of `String`
|
||||
-fn smithay::wayland::xdg_activation::XdgActivationState::create_external_token(&mut self, app_id: impl Into<Option<String>>);
|
||||
+fn smithay::wayland::xdg_activation::XdgActivationState::create_external_token(&mut self, data: impl Into<Option<XdgActivationTokenData>>);
|
||||
```
|
||||
|
||||
### New protocols
|
||||
* Introduce ext data control protocol by @PolyMeilex in https://github.com/Smithay/smithay/pull/1577
|
||||
* Update idle notify to version 2 by @PolyMeilex in https://github.com/Smithay/smithay/pull/1618
|
||||
|
||||
### TextInput improvements
|
||||
* Revert "Fix repeated key input issue in Chrome with multiple windows" by @Drakulix in https://github.com/Smithay/smithay/pull/1647
|
||||
* text-input: fix active instance tracking by @kchibisov in https://github.com/Smithay/smithay/pull/1648
|
||||
* text-input: properly handle double buffered state by @kchibisov in https://github.com/Smithay/smithay/pull/1649
|
||||
|
||||
### Miscellaneous
|
||||
* clock: Fix current monotonic time in millis u32 overflow panic by @YaLTeR in https://github.com/Smithay/smithay/pull/1645
|
||||
* xwm: Update override-redirect flag on map request by @Ottatop in https://github.com/Smithay/smithay/pull/1656
|
||||
* utils: Rework `HookId` recycle logic by @Paraworker in https://github.com/Smithay/smithay/pull/1657
|
||||
* rename Rectangle::from_extemities to Rectangle::from_extremities by @m4rch3n1ng in https://github.com/Smithay/smithay/pull/1646
|
||||
* xdg_activation: Allow passing all data in XdgActivationState::create_external_token by @bbb651 in https://github.com/Smithay/smithay/pull/1658
|
||||
|
||||
## 0.4.0
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
**`wayland-server` was updated to 0.30:**
|
||||
- Most of the wayland frontend API is changed to follow the new request dispatching mechanism built around the `Dispatch` trait from `wayland-server`
|
||||
- Modules that provide handlers for Wayland globals now provide `DelegateDispatch` implementations, as well as macros to simplify the dispatching from your main state
|
||||
|
||||
#### Clients & Protocols
|
||||
|
||||
- Remove `xdg-shell-unstable-v6` backwards compatibility
|
||||
- `XdgPositionerState` moved to `XdgPopupState` and added to `XdgRequest::NewPopup`
|
||||
- `PopupSurface::send_configure` now checks the protocol version and returns an `Result`
|
||||
- `KeyboardHandle::input` filter closure now receives a `KeysymHandle` instead of a `Keysym` and returns a `FilterResult`.
|
||||
- `PointerButtonEvent::button` now returns an `Option<MouseButton>`.
|
||||
- `MouseButton` is now non-exhaustive.
|
||||
- Remove `Other` and add `Forward` and `Back` variants to `MouseButton`. Use the new `PointerButtonEvent::button_code` in place of `Other`.
|
||||
- `GrabStartData` has been renamed to `PointerGrabStartData`
|
||||
- The `slot` method on touch events no longer returns an `Option` and multi-touch capability is thus opaque to the compositor
|
||||
- `wayland::output::Output` now is created separately from it's `Global` as reflected by [`Output::new`] and the new [`Output::create_global] method.
|
||||
- `PointerHandle` no longer sends an implicit motion event when a grab is set, `time` has been replaced by an explicit `focus` parameter in [`PointerHandle::set_grab`]
|
||||
- `ToplevelSurface::send_configure`/`PopupSurface::send_configure`/`LayerSurface::send_configure` now always send a configure event regardless of changes and return
|
||||
the serial of the configure event. `send_pending_configure` can be used to only send a configure event on pending changes.
|
||||
|
||||
#### Backends
|
||||
|
||||
- Rename `WinitInputBacked` to `WinitEventLoop`.
|
||||
- Rename `WinitInputError` to `WinitError`;
|
||||
- `WinitInputBackend` no longer implements `InputBackend`. Input events are now received from the `WinitEvent::Input` variant.
|
||||
- All winit backend internal event types now use `WinitInput` as the backend type.
|
||||
- `WinitEventLoop::dispatch_new_events` is now used to receive some `WinitEvent`s.
|
||||
- Added `TabletToolType::Unknown` as an option for tablet events
|
||||
- `render_texture` was removed from `Frame`, use `render_texture_at` or `render_texture_from_to` instead or use `Gles2Renderer::render_texture` as a direct replacement.
|
||||
- Remove `InputBackend::dispatch_new_events`, turning `InputBackend` into a definition of backend event types. Future input backends should be a `calloop::EventSource`.
|
||||
- Remove `InputBackend::EventError` associated type as it is unneeded since `dispatch_new_events` was removed.
|
||||
- `Swapchain` does not have a generic Userdata-parameter anymore, but utilizes `UserDataMap` instead
|
||||
- `GbmBufferedSurface::next_buffer` now additionally returns the age of the buffer
|
||||
- `Present` was merged into the `X11Surface`
|
||||
- `X11Surface::buffer` now additionally returns the age of the buffer
|
||||
- `X11Surface` now has an explicit `submit` function
|
||||
- `X11Surface` is now multi-window capable.
|
||||
- `Renderer::clear` now expects a second argument to optionally only clear parts of the buffer/surface
|
||||
- `Transform::transform_size` now takes a `Size` instead of two `u32`
|
||||
- `Gles2Renderer` now automatically flips the `render` result to account for OpenGLs coordinate system
|
||||
- `Frame::clear`, `Frame::render_texture_at` and `Frame::render_texture_from_to` now have an additional damage argument
|
||||
- `EGLNativeSurface` implementations overriding `swap_buffers` now receive and additional `damage` attribute to be used with `eglSwapBuffersWithDamageEXT` if desired
|
||||
- `EGLSurface::swap_buffers` now accepts an optional `damage` parameter
|
||||
- `WinitGraphicsBackend` does no longer provide a `render`-method and exposes its `Renderer` directly instead including new functions `bind` and `submit` to handle swapping buffers.
|
||||
- `ImportShm` was renamed to `ImportMem`
|
||||
- `ImportMem` and `ImportDma` were split and do now have accompanying traits `ImportMemWl` and `ImportDmaWl` to import wayland buffers.
|
||||
- Added `EGLSurface::get_size`
|
||||
- `EGLDisplay::get_extensions` was renamed to `extensions` and now returns a `&[String]`.
|
||||
- Added gesture input events, which are supported with the libinput backend.
|
||||
|
||||
### Additions
|
||||
|
||||
#### Clients & Protocols
|
||||
|
||||
- `xdg_activation_v1` support
|
||||
- `wlr-layer-shell-unstable-v1` support
|
||||
- Added public api constants for the roles of `wl_shell_surface`, `zxdg_toplevel` and `xdg_toplevel`. See the
|
||||
`shell::legacy` and `shell::xdg` modules for these constants.
|
||||
- Whether a surface is toplevel equivalent can be determined with the new function `shell::is_toplevel_equivalent`.
|
||||
- Setting the parent of a toplevel surface is now possible with the `xdg::ToplevelSurface::set_parent` function.
|
||||
- Add support for the zxdg-foreign-v2 protocol.
|
||||
- Support for `xdg_wm_base` protocol version 3
|
||||
- Added the option to initialize the dmabuf global with a client filter
|
||||
- `wayland::output::Output` now has user data attached to it and more functions to query its properties
|
||||
- Added a `KeyboardGrab` similar to the existing `PointerGrab`
|
||||
- `wayland::output::Output` now has a `current_scale` method to quickly retrieve its set scale.
|
||||
- `wayland::shell::wlr_layer::KeyboardInteractivity` now implements `PartialEq` and `Eq`.
|
||||
- Added `TouchHandle` for Wayland client touch support (see `Seat::get_touch`)
|
||||
- `wayland::output::Scale` was introduced to handle fractional scale values better
|
||||
- Support for `wl_output` global version 4
|
||||
- Support for `wl_seat` global version 7
|
||||
- Support for `wl_compositor` global version 5
|
||||
- Support for the `wp_viewporter` protocol
|
||||
- Support for the `zwp_input_method_v2` protocol
|
||||
- Support for the `zwp_text_input_v3` protocol
|
||||
|
||||
#### Backends
|
||||
|
||||
- New `x11` backend to run the compositor as an X11 client. Enabled through the `backend_x11` feature.
|
||||
- `x11rb` event source integration used in anvil's XWayland implementation is now part of smithay at `utils::x11rb`. Enabled through the `x11rb_event_source` feature.
|
||||
- `KeyState`, `MouseButton`, `ButtonState` and `Axis` in `backend::input` now derive `Hash`.
|
||||
- New `DrmNode` type in drm backend. This is primarily for use a backend which needs to run as client inside another session.
|
||||
- The button code for a `PointerButtonEvent` may now be obtained using `PointerButtonEvent::button_code`.
|
||||
- `Renderer` now allows texture filtering methods to be set.
|
||||
- `backend::renderer` has a new `utils`-module that can take care of client buffer management for you.
|
||||
- `EGLSurface::buffer_age` can be used to query the surface buffer age.
|
||||
- `GbmBufferedSurface::reset_buffers` can now be used to reset underlying buffers.
|
||||
- Added new `Offscreen` trait to create offscreen surfaces for `Renderer`s
|
||||
- Added functions to `ImportMem` to upload bitmaps from memory
|
||||
- Added `ExportDma` trait to export framebuffers and textures into dmabufs
|
||||
- Added `ExportMem` trait to copy framebuffers and textures into memory
|
||||
- Added `multigpu`-module to the renderer, which makes handling multi-gpu setups easier!
|
||||
- Added `backend::renderer::utils::import_surface_tree` to be able to import buffers before rendering
|
||||
- Added `EGLContext::display` to allow getting the underlying display of some context.
|
||||
- Make `EGLContext::dmabuf_render_formats` and `EGLContext::dmabuf_texture_formats` also accessible from `EGLDisplay`.
|
||||
|
||||
#### Desktop
|
||||
|
||||
- New `desktop` module to handle window placement, tracks popups, layer surface and various rendering helpers including automatic damage-tracking! (+so much more)
|
||||
|
||||
#### Utils
|
||||
|
||||
- `Rectangle` can now also be converted from f64 to i32 variants
|
||||
- `Rectangle::contains_rect` can be used to check if a rectangle is contained within another
|
||||
- `Coordinate` is now part of the public api, so it can be used for coordinate agnositic functions outside of the utils module or even out-of-tree
|
||||
|
||||
### Bugfixes
|
||||
|
||||
#### Clients & Protocols
|
||||
|
||||
- `Multicache::has()` now correctly does what is expected of it
|
||||
- `xdg_shell` had an issue where it was possible that configured state gets overwritten before it was acked/committed.
|
||||
- `wl_keyboard` rewind the `keymap` file before passing it to the client
|
||||
- `wl_shm` properly validates parameters when creating a `wl_buffer`.
|
||||
- `ServerDnDGrab` and `DnDGrab` now correctly send data device `leave` event on button release
|
||||
- Client are now allowed to reassign the same role to a surface
|
||||
- `xdg_output` now applies the output transforms to the reported logical size
|
||||
|
||||
#### Backends
|
||||
|
||||
- EGLBufferReader now checks if buffers are alive before using them.
|
||||
- LibSeat no longer panics on seat disable event.
|
||||
- X11 backend will report an error when trying to present a dmabuf fails.
|
||||
|
||||
### Anvil
|
||||
|
||||
- Anvil now implements the x11 backend in smithay. Run by passing `--x11` into the arguments when launching.
|
||||
- Passing `ANVIL_MUTEX_LOG` in environment variables now uses the slower `Mutex` logging drain.
|
||||
- Only toplevel surfaces now get implicit keyboard focus
|
||||
- Fix popup drawing for fullscreen windows
|
||||
|
||||
## version 0.3.0 (2021-07-25)
|
||||
|
||||
Large parts of Smithay were changed with numerous API changes. It is thus recommended to
|
||||
approach version 0.3 as if it was a new crate altogether compared to 0.2.
|
||||
|
||||
The most notable changes are:
|
||||
|
||||
- Deep refactor of the graphics backends around a workflows centered on allocating graphics buffers,
|
||||
and a Gles2-based renderer abstraction is provided.
|
||||
- Support for DRM atomic modesetting as well as client-provided DMABUF
|
||||
- Most backends are now `calloop` event sources generating events. The recommended organization for
|
||||
your smithay-based compositor is thus to centralize most of your logic on a global state struct,
|
||||
and delegate event handling to it via the shared data mechanism of `calloop`. Most of the callbacks
|
||||
you provide to Smithay are given mutable access to this shared data.
|
||||
- The `wayland::compositor` handling logic now automatically handles state tracking and delayed commit
|
||||
for wayland surfaces.
|
||||
|
||||
Many thanks to the new contributors to Smithay, who contributed the following:
|
||||
|
||||
- Support for [`libseat`](https://sr.ht/~kennylevinsen/seatd/) as a session backend, by
|
||||
@PolyMeilex
|
||||
- Support for graphics tablets via the `tablet` protocol extension, by @PolyMeilex
|
||||
- Support for running Smithay on `aarch64` architectures, by @cmeissl
|
||||
- A rework of the `xdg-shell` handlers to better fit the protocol logic and correctly track configure
|
||||
events, by @cmeissl
|
||||
- Basic Xwayland support, by @psychon
|
||||
|
||||
## version 0.2.0 (2019-01-03)
|
||||
|
||||
### General
|
||||
|
||||
- **[Breaking]** Upgrade to wayland-rs 0.21
|
||||
- **[Breaking]** Moving the public dependencies to a `reexports` module
|
||||
- Migrate the codebase to Rust 2018
|
||||
|
||||
### Backends
|
||||
|
||||
- **[Breaking]** WinitBackend: Upgrade to winit 0.18
|
||||
- **[Breaking]** Global refactor of the DRM & Session backends
|
||||
- **[Breaking]** Restructuration of the backends around the `calloop` event-loop
|
||||
|
||||
### Clients & Protocol
|
||||
|
||||
- Basic XWayland support
|
||||
- Data device & Drag'n'Drop support
|
||||
- Custom client pointers support
|
||||
|
||||
## version 0.1.0 (2017-10-01)
|
||||
|
||||
### Protocol handling
|
||||
|
||||
- Low-level handling routines for several wayland globals:
|
||||
- `wayland::shm` handles `wl_shm`
|
||||
- `wayland::compositor` handles `wl_compositor` and `wl_subcompositor`
|
||||
- `wayland::shell` handles `wl_shell` and `xdg_shell`
|
||||
- `wayland::seat` handles `wl_seat`
|
||||
- `wayland::output` handles `wl_output`
|
||||
|
||||
### Backend
|
||||
|
||||
- Winit backend (EGL context & input)
|
||||
- DRM backend
|
||||
- libinput backend
|
||||
- glium integration
|
||||
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
# Contributing
|
||||
|
||||
Smithay is open to contributions from anyone. Here are a few tips to get started if you want to participate.
|
||||
|
||||
## Coordination
|
||||
|
||||
Most discussion about features and their implementations takes place on github.
|
||||
If you have questions, suggestions, ideas, you can open an issue to discuss it, or add your message in an already existing issue
|
||||
if it fits its scope.
|
||||
|
||||
If you want a more realtime discussion I (@vberger) have a Matrix room dedicated to Smithay and
|
||||
my other wayland crates: [#smithay:matrix.org](https://matrix.to/#/#smithay:matrix.org). If you don't want to
|
||||
use matrix, this room is also bridged to libera.chat IRC on #smithay.
|
||||
|
||||
## Scope
|
||||
|
||||
Smithay attempts to be as generic and un-opinionated as possible. As such, if you have an idea of a feature that would be useful
|
||||
for your compositor project and would like it to be integrated in Smithay, please consider whether it is in its scope:
|
||||
|
||||
- If this is a very generic feature that probably many different projects would find useful, it can be integrated in Smithay
|
||||
- If it is a rather specific feature, but can be framed as a special case of a more general feature, this general feature is
|
||||
likely worth adding to Smithay
|
||||
- If this feature is really specific to your use-case, it is out of scope for Smithay
|
||||
|
||||
## Structure
|
||||
|
||||
Smithay aims to be a modular hierarchical library:
|
||||
|
||||
- Functionalities should be split into independent modules as much as possible
|
||||
- There can be dependencies in functionalities
|
||||
- Even if most people would directly use a high-level functionality, the lower level abstractions it is built on should
|
||||
still be exposed independently if possible
|
||||
|
||||
The goal is for Smithay to be a "use what you want" library, and features that are not used should have no impact on the
|
||||
application built with Smithay.
|
||||
Vendored
+150
@@ -0,0 +1,150 @@
|
||||
[package]
|
||||
name = "smithay"
|
||||
version = "0.7.0"
|
||||
authors = ["Victor Berger <[email protected]>", "Drakulix (Victoria Brekenfeld)"]
|
||||
license = "MIT"
|
||||
description = "Smithay is a library for writing wayland compositors."
|
||||
repository = "https://github.com/Smithay/smithay"
|
||||
edition = "2024"
|
||||
readme = "README.md"
|
||||
homepage = "https://smithay.github.io/"
|
||||
keywords = ["wayland", "compositor", "graphics", "server"]
|
||||
categories = ["gui"]
|
||||
rust-version = "1.87"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["test_all_features", "libseat/docs_rs"]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[workspace]
|
||||
resolver = "3"
|
||||
members = [
|
||||
"smithay-drm-extras",
|
||||
"smallvil",
|
||||
"anvil",
|
||||
"wlcs_anvil",
|
||||
"test_clients",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
appendlist = "1.4"
|
||||
ash = { version = "0.38.0", optional = true }
|
||||
bitflags = "2.2.1"
|
||||
calloop = "0.14.5"
|
||||
cursor-icon = "1.2.0"
|
||||
downcast-rs = "1.2.0"
|
||||
drm-fourcc = "^2.2.0"
|
||||
drm = { version = "0.14.0", optional = true }
|
||||
drm-ffi = { version = "0.9.1", optional = true }
|
||||
drm-sys = { version = "0.8.1", optional = true }
|
||||
errno = "0.3.5"
|
||||
gbm = { version = "0.18.0", optional = true, default-features = false, features = ["drm-support"] }
|
||||
glow = { version = "0.16", optional = true }
|
||||
input = { version = "0.10.0", default-features = false, features=["libinput_1_19"], optional = true }
|
||||
indexmap = "2.11"
|
||||
libc = "0.2.103"
|
||||
libseat = { version = "0.2.3", optional = true, default-features = false }
|
||||
libloading = { version="0.8.0", optional = true }
|
||||
rustix = { version = "1.0.7", features = ["event", "fs", "mm", "net", "pipe", "process", "shm", "time", "param"] }
|
||||
rand = "0.10.0"
|
||||
scopeguard = { version = "1.1.0", optional = true }
|
||||
siphasher = { version = "1.0.3", optional = true }
|
||||
tracing = "0.1.37"
|
||||
tempfile = { version = "3.0", optional = true }
|
||||
thiserror = "2.0.12"
|
||||
udev = { version = "0.9.0", optional = true }
|
||||
# Require never `wayland-client`/`wayland-cursor` than winit uses to fix `-Z minimal-versions`
|
||||
# due to issue in older version.
|
||||
wayland-client = { version = "0.31.14", optional = true }
|
||||
wayland-cursor = { version = "0.31.14", optional = true }
|
||||
wayland-egl = { version = "0.32.11", optional = true }
|
||||
wayland-protocols = { version = "0.32.13", features = ["unstable", "staging", "server"], optional = true }
|
||||
wayland-protocols-wlr = { version = "0.3.12", features = ["server"], optional = true }
|
||||
wayland-protocols-misc = { version = "0.3.12", features = ["server"], optional = true }
|
||||
wayland-server = { version = "0.31.13", optional = true }
|
||||
wayland-sys = { version = "0.31.11", optional = true }
|
||||
wayland-backend = { version = "0.3.15", optional = true }
|
||||
winit = { version = "=0.31.0-beta.3", default-features = false, features = ["wayland", "wayland-dlopen", "x11"], optional = true }
|
||||
x11rb = { version = "0.13.0", optional = true, features = ["res", "sync"]}
|
||||
xkbcommon = { version = "0.9.0", features = ["wayland"]}
|
||||
encoding_rs = { version = "0.8.33", optional = true }
|
||||
profiling = "1.0.13"
|
||||
smallvec = "1.11"
|
||||
pixman = { version = "0.2.1", features = ["drm-fourcc", "sync"], optional = true }
|
||||
aliasable = { version = "0.1.3", optional = true }
|
||||
portable-atomic = { version = "1", features = ["float"] }
|
||||
tracy-client = { version = "0.18.4", default-features = false, optional = true }
|
||||
reis = { version = "0.7.0", features = ["calloop"] }
|
||||
glam = { version = "0.33.2", default-features = false, features = ["std"] }
|
||||
|
||||
[dev-dependencies]
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
criterion = { version = "0.5" }
|
||||
image = "0.25"
|
||||
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
||||
|
||||
[build-dependencies]
|
||||
gl_generator = { version = "0.14", optional = true }
|
||||
pkg-config = { version = "0.3.17", optional = true }
|
||||
cc = { version = "1.0.79", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["backend_drm", "backend_gbm", "backend_libinput", "backend_udev", "backend_session_libseat", "backend_x11", "backend_winit", "desktop", "renderer_gl", "renderer_pixman", "renderer_multi", "xwayland", "wayland_frontend", "backend_vulkan", "backend_libei"]
|
||||
backend_winit = ["winit", "backend_egl", "wayland-client", "wayland-cursor", "wayland-egl", "renderer_gl"]
|
||||
backend_x11 = ["x11rb", "x11rb/dri3", "x11rb/xfixes", "x11rb/xinput", "x11rb/present", "x11rb_event_source", "backend_gbm", "backend_drm", "backend_egl"]
|
||||
backend_drm = ["drm", "drm-ffi", "drm-sys"]
|
||||
backend_gbm = ["gbm", "cc", "pkg-config", "backend_drm"]
|
||||
backend_gbm_has_fd_for_plane = []
|
||||
backend_gbm_has_create_with_modifiers2 = []
|
||||
backend_egl = ["gl_generator", "libloading"]
|
||||
backend_libinput = ["input"]
|
||||
backend_session = []
|
||||
backend_udev = ["udev", "input/udev"]
|
||||
backend_vulkan = ["ash", "scopeguard"]
|
||||
backend_session_libseat = ["backend_session", "libseat"]
|
||||
backend_libei = ["wayland_frontend"]
|
||||
desktop = []
|
||||
renderer_gl = ["gl_generator", "backend_egl"]
|
||||
renderer_glow = ["renderer_gl", "glow"]
|
||||
renderer_multi = ["backend_drm", "aliasable"]
|
||||
renderer_pixman = ["pixman"]
|
||||
renderer_test = []
|
||||
tracy_gpu_profiling = ["renderer_gl", "tracy-client"]
|
||||
use_system_lib = ["wayland_frontend", "wayland-backend/server_system", "wayland-sys", "gbm?/import-wayland"]
|
||||
use_bindgen = ["drm-ffi/use_bindgen", "gbm/use_bindgen", "input/use_bindgen"]
|
||||
wayland_frontend = ["wayland-server", "wayland-protocols", "wayland-protocols-wlr", "wayland-protocols-misc", "tempfile", "siphasher"]
|
||||
x11rb_event_source = ["x11rb"]
|
||||
xwayland = ["encoding_rs", "wayland_frontend", "x11rb/composite", "x11rb/xfixes", "x11rb/randr", "x11rb_event_source", "scopeguard"]
|
||||
test_all_features = ["default", "use_system_lib", "renderer_glow", "renderer_test"]
|
||||
|
||||
[[example]]
|
||||
name = "minimal"
|
||||
required-features = ["backend_winit", "renderer_gl", "wayland_frontend"]
|
||||
|
||||
[[example]]
|
||||
name = "seat"
|
||||
required-features = ["wayland_frontend"]
|
||||
|
||||
[[example]]
|
||||
name = "compositor"
|
||||
required-features = ["wayland_frontend"]
|
||||
|
||||
[[example]]
|
||||
name = "vulkan"
|
||||
required-features = ["backend_vulkan"]
|
||||
|
||||
[[example]]
|
||||
name = "buffer_test"
|
||||
required-features = ["backend_drm", "backend_gbm", "backend_egl", "backend_vulkan", "renderer_gl"]
|
||||
|
||||
[[bench]]
|
||||
name = "benchmark"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "geometry"
|
||||
harness = false
|
||||
|
||||
[profile.release-with-debug]
|
||||
inherits = "release"
|
||||
debug = true
|
||||
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
Developer Certificate of Origin
|
||||
Version 1.1
|
||||
|
||||
Copyright (C) 2026 Smithay maintainers
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
|
||||
Developer's Certificate of Origin 1.1
|
||||
|
||||
By making a contribution to this project, I certify that:
|
||||
|
||||
(a) The contribution was created in whole or in part by me and I
|
||||
have the right to submit it under the open source license
|
||||
indicated in the file; or
|
||||
|
||||
(b) The contribution is based upon previous work that, to the best
|
||||
of my knowledge, is covered under an appropriate open source
|
||||
license and I have the right under that license to submit that
|
||||
work with modifications, whether created in whole or in part
|
||||
by me, under the same open source license (unless I am
|
||||
permitted to submit under a different license), as indicated
|
||||
in the file; or
|
||||
|
||||
(c) The contribution was provided directly to me by some other
|
||||
person who certified (a), (b) or (c) and I have not modified
|
||||
it.
|
||||
|
||||
(d) I understand and agree that this project and the contribution
|
||||
are public and that a record of the contribution (including all
|
||||
personal information I submit with it, including my sign-off) is
|
||||
maintained indefinitely and may be redistributed consistent with
|
||||
this project or the open source license(s) involved.
|
||||
Vendored
+53
@@ -0,0 +1,53 @@
|
||||
# Getting started
|
||||
|
||||
So you want to write a new Wayland compositor with Smithay, but don't know where to start?
|
||||
|
||||
## Wayland itself
|
||||
|
||||
Wayland is just a set of XML files describing the protocol by which a compositor and client communicate.
|
||||
To work on a compositor, you need to establish some understanding of the state mandated by the protocol,
|
||||
most importantly the lifetime of objects defined by it.
|
||||
|
||||
We recommend the official [wayland-docs](https://wayland.freedesktop.org/docs/html/) to get started, in particular [Chapter 4](https://wayland.freedesktop.org/docs/html/ch04.html),
|
||||
which talks about the protocol.
|
||||
|
||||
Additionally it can help to familiarize yourself with the viewpoint of clients. The [wayland-book](https://wayland-book.com/) is a good resource here.
|
||||
|
||||
There is also an (unfortunately stalled - at the time of writing this document -) effort to write a [smithay book](https://smithay.github.io/book/) to
|
||||
cover similar topics, but focused on [wayland-rs](https://github.com/Smithay/wayland-rs) (the underlying Rust-based Wayland implementation) rather than
|
||||
[libwayland](https://gitlab.freedesktop.org/wayland/wayland/). At least the client side of this is a worthwhile read as well.
|
||||
|
||||
## Rust
|
||||
|
||||
Smithay makes heavy use of Rust's features, which is likely why you selected the framework in the first place.
|
||||
As such we assume you have *some* experience with the language already.
|
||||
|
||||
If not the [awesome-rust resource section](https://github.com/rust-unofficial/awesome-rust?tab=readme-ov-file#resources) has you covered for various learning resources.
|
||||
|
||||
## Smithay
|
||||
|
||||
Unfortunately, the Smithay book doesn't cover the server-side (yet).
|
||||
|
||||
Which leaves you with the following resources to understand how Smithay works:
|
||||
|
||||
- A large portion of Smithay is build around [wayland-rs](https://github.com/smithay/wayland-rs) `Dispatch` machinery. It is thus highly recommended to read and understand
|
||||
the documentation of the [wayland-server](https://smithay.github.io/wayland-rs/wayland_server/) crate.
|
||||
- Smithay itself has [comprehensive documentation](https://smithay.github.io/smithay/) including for its [various](https://smithay.github.io/smithay/smithay/backend/index.html) [modules](https://smithay.github.io/smithay/smithay/wayland/index.html)!
|
||||
- [smallvil](https://github.com/Smithay/smithay/tree/master/smallvil) is the smallest (somewhat) usable compositor using Smithay and intended as a learning resource and potential starting point.
|
||||
- [anvil](https://github.com/Smithay/smithay/tree/master/anvil) is smithay's testing ground and thus a much more complete compositor to study. Its code strives to fill a gap between real-world examples and easy-to-understand/maintain code paths, however it is lacking in overall quality.
|
||||
- Many real world compositors use Smithay, please refer to our [README](https://github.com/Smithay/smithay/blob/master/README.md#other-compositors-that-use-smithay) for a list of them to study.
|
||||
|
||||
### Side-note: Which version of Smithay should I use?
|
||||
|
||||
Smithay itself does not follow a regular release schedule, as such many compositors choose to depend on a given git commit and frequent updates. We strive to do better in this department and try to publish semi-frequently to crates.io.
|
||||
|
||||
However the framework is still evolving a lot, thus we cannot really commit to any sort of ABI stability. Thus you currently need to rely on git-based updates to not only get new features, but also bugfixes. As a result released Smithay versions don't really have a benefit other than coming from crates.io.
|
||||
|
||||
If you are still unsure, check our [Changelog](https://github.com/Smithay/smithay/blob/master/CHANGELOG.md) and see how many unreleased features
|
||||
are currently waiting for a new Smithay release. The longer the list, the more we recommend to start off with the current git commit to make updating
|
||||
to the next released version as easy as possible.
|
||||
|
||||
## Further help
|
||||
|
||||
If you still feel stuck trying to work your way through these resources or have questions not covered here, please feel free to reach out in our chatroom.
|
||||
You can find us on Matrix: [#smithay:matrix.org](https://matrix.to/#/#smithay:matrix.org). If you don't want to use Matrix, this room is also bridged to libera.chat IRC on #smithay.
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Victor Berger and Victoria Brekenfeld
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
Vendored
+89
@@ -0,0 +1,89 @@
|
||||
<p align="center">
|
||||
<img width="240" src="https://github.com/Smithay/smithay/assets/20758186/7a84ab10-e229-4823-bad8-9c647546407b" alt="Smithay"/>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://crates.io/crates/smithay"><img src="https://img.shields.io/crates/v/smithay.svg" alt="Crates.io"></a>
|
||||
<a href="https://docs.rs/smithay"><img src="https://docs.rs/smithay/badge.svg" alt="docs.rs"></a>
|
||||
<a href="https://github.com/Smithay/smithay/actions"><img src="https://github.com/Smithay/smithay/workflows/Continuous%20Integration/badge.svg" alt="CI"></a>
|
||||
<a href="https://matrix.to/#/#smithay:matrix.org"><img src="https://img.shields.io/badge/Matrix-%23smithay%3Amatrix.org-blue.svg" alt="Matrix"></a>
|
||||
<a href="https://libera.chat/"><img src="https://img.shields.io/badge/IRC-%23Smithay-blue.svg" alt="IRC"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<strong>A smithy for rusty Wayland compositors</strong>
|
||||
</p>
|
||||
|
||||
## Goals
|
||||
|
||||
Smithay aims to provide building blocks to create Wayland compositors in Rust. While not
|
||||
being a full-blown compositor, it'll provide objects and interfaces implementing common
|
||||
functionalities that pretty much any compositor will need, in a generic fashion.
|
||||
|
||||
It supports the [core Wayland protocols](https://gitlab.freedesktop.org/wayland/wayland), the official [protocol extensions](https://gitlab.freedesktop.org/wayland/wayland-protocols), and *some* external extensions, such as those made by and for [wlroots](https://gitlab.freedesktop.org/wlroots/wlr-protocols) and [KDE](https://invent.kde.org/libraries/plasma-wayland-protocols).
|
||||
<!-- https://github.com/Smithay/smithay/pull/779#discussion_r993640470 https://github.com/Smithay/smithay/issues/778 -->
|
||||
|
||||
Also:
|
||||
|
||||
- **Documented:** Smithay strives to maintain clear and detailed documentation of its API and
|
||||
functionalities. Compiled documentation is available on [docs.rs](https://docs.rs/smithay) for released
|
||||
versions, and [here](https://smithay.github.io/smithay) for the master branch.
|
||||
- **Safety:** Smithay aims to be memory-safe and reliable, thanks to Rust.
|
||||
- **Modularity:** Smithay is not a framework and does not impose constraints. You are never required
|
||||
to use components you don’t need.
|
||||
- **High-level:** You should not have to worry about gory low-level stuff (but Smithay won't
|
||||
stop you if you really want to dive into it).
|
||||
|
||||
## Getting started
|
||||
|
||||
If you want to learn how to build a compositor with Smithay, the best place to start is the [getting started](https://github.com/Smithay/smithay/blob/master/GETTING_STARTED.md) guide.
|
||||
|
||||
## Anvil
|
||||
|
||||
Smithay as a compositor library has its own sample compositor: `anvil`.
|
||||
|
||||
To get information about it and how you can run it, check out the [`anvil` README](https://github.com/Smithay/smithay/blob/master/anvil/README.md)
|
||||
|
||||
## Other compositors that use Smithay
|
||||
|
||||
- [Cosmic](https://github.com/pop-os/cosmic-epoch): Next-generation Cosmic desktop environment
|
||||
- [Catacomb](https://github.com/catacombing/catacomb): A Wayland Mobile Compositor
|
||||
- [emskin](https://github.com/emskin/emskin): A nested Wayland compositor for embedding any app inside Emacs
|
||||
- [MagmaWM](https://github.com/MagmaWM/MagmaWM): A versatile and customizable Wayland Compositor
|
||||
- [Niri](https://github.com/YaLTeR/niri): A scrollable-tiling Wayland compositor
|
||||
- [Strata](https://github.com/StrataWM/strata): A cutting-edge, robust and sleek Wayland compositor
|
||||
- [Pinnacle](https://github.com/Ottatop/pinnacle): A WIP Wayland compositor, inspired by AwesomeWM
|
||||
- [Sudbury](https://gitlab.freedesktop.org/bwidawsk/sudbury): Compositor designed for ChromeOS
|
||||
- [wprs](https://github.com/wayland-transpositor/wprs): Like [xpra](https://en.wikipedia.org/wiki/Xpra), but for Wayland, and written in
|
||||
Rust.
|
||||
- [Local Desktop](https://github.com/localdesktop/localdesktop): An Android app for running GUI Linux via PRoot and Wayland.
|
||||
- [Otto](https://github.com/nongio/otto): A gesture-driven stacking compositor.
|
||||
- [DriftWM](https://github.com/malbiruk/driftwm): A trackpad-first infinite canvas Wayland compositor.
|
||||
- [Denial](https://github.com/denialwm/denial): A stacking, tiling, and scrolling-tiling Wayland compositor with a Flutter-based shell.
|
||||
|
||||
## System Dependencies
|
||||
|
||||
(This list can depend on the features you enable)
|
||||
|
||||
- `libwayland`
|
||||
- `libxkbcommon`
|
||||
- `libudev`
|
||||
- `libinput`
|
||||
- `libgbm`
|
||||
- [`libseat`](https://git.sr.ht/~kennylevinsen/seatd)
|
||||
- `xwayland`
|
||||
- `libEGL`
|
||||
- `libpixman`
|
||||
- `libdisplay-info`
|
||||
|
||||
## Contact us
|
||||
|
||||
If you have questions or want to discuss the project with us, our main chatroom is on Matrix: [`#smithay:matrix.org`](https://matrix.to/#/#smithay:matrix.org).
|
||||
|
||||
## Contributing
|
||||
|
||||
General notes on contributing to Smithay can be found [here](https://github.com/Smithay/smithay/blob/master/CONTRIBUTING.md).
|
||||
|
||||
Please note that to submit code to Smithay, you must agree to our [Developer Certificate of Origin](https://github.com/Smithay/smithay/blob/master/DCO.md).
|
||||
|
||||
If you are used to using generative AI, please ensure you read our [Policy](https://github.com/Smithay/smithay/blob/master/AI.md) before starting.
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
# Vendored Smithay
|
||||
|
||||
- Repository: https://github.com/Smithay/smithay
|
||||
- Revision: `8a270232f1bfc416c781273c98a3f52a812a0bba`
|
||||
- Source: complete tracked snapshot from `git archive --format=tar 8a270232f1bfc416c781273c98a3f52a812a0bba`. No `.git`, `target`, or untracked Cargo cache markers are included.
|
||||
- Original archive: 4894720 bytes; SHA-256 `81a42cfd4c3626c9bad8051023970151dcea0fe7bedd02fc09105db360a847c2`.
|
||||
- Original snapshot: 345 regular files; 4559959 content bytes. File contents, symlink targets and Git executable bits were independently verified against the archive. Archive ownership and write permissions follow the extracting user's normal umask.
|
||||
- Licenses are preserved in `LICENSE.txt`, `smithay-drm-extras/LICENSE`, and individual source headers.
|
||||
|
||||
## Local patch
|
||||
|
||||
`patches/shm-texture-cache-format.patch` changes the GLES SHM surface-cache compatibility check to match dimensions, GL internal format, and alpha interpretation. Previously an equal-sized ARGB/XRGB change reused stale `has_alpha`, selecting the wrong shader variant. A newly allocated texture also forces a complete upload. Context identity is already part of the cache key. Offset and stride describe each upload's source and are read anew; SHM cache entries always have normal orientation and non-external storage, so those properties do not add compatibility criteria.
|
||||
|
||||
At most 16 debug records per process describe format incompatibilities. Records contain only numeric format/dimension fields, alpha booleans, and a fixed event message. They contain no pixel data, application text, titles, or credentials.
|
||||
|
||||
The independent private-Wayland GPU regression is in Ferese's `compositor/src/state/partial_shm_gpu_tests.rs`. It compares the cached renderer with a new, unshared EGL context and full upload/full repaint on every frame, in both ARGB-first and XRGB-first directions. The ordinary partial-damage regression shares the fixture.
|
||||
|
||||
Vendored
+65
@@ -0,0 +1,65 @@
|
||||
[package]
|
||||
authors = ["Victor Berger <[email protected]>", "Drakulix (Victoria Brekenfeld)"]
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
name = "anvil"
|
||||
publish = false
|
||||
version = "0.0.1"
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.2.1"
|
||||
fps_ticker = {version = "1.0.0", optional = true}
|
||||
image = {version = "0.25.1", default-features = false, optional = true, features = ["png"]}
|
||||
rand = "0.8"
|
||||
tracing = { version = "0.1.37", features = ["max_level_trace", "release_max_level_debug"] }
|
||||
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
||||
thiserror = "1"
|
||||
xcursor = {version = "0.3.3", optional = true}
|
||||
xkbcommon = "0.8.0"
|
||||
renderdoc = {version = "0.11.0", optional = true}
|
||||
smithay-drm-extras = {path = "../smithay-drm-extras", optional = true}
|
||||
puffin_http = { version = "0.13", optional = true }
|
||||
profiling = { version = "1.0" }
|
||||
|
||||
[dependencies.smithay]
|
||||
default-features = false
|
||||
features = ["desktop", "wayland_frontend"]
|
||||
path = ".."
|
||||
|
||||
[dependencies.x11rb]
|
||||
default-features = false
|
||||
features = ["composite"]
|
||||
optional = true
|
||||
version = "0.13.0"
|
||||
|
||||
[build-dependencies]
|
||||
gl_generator = "0.14"
|
||||
|
||||
[features]
|
||||
debug = ["fps_ticker", "image/png", "renderdoc"]
|
||||
default = ["egl", "winit", "x11", "udev", "xwayland", "libei"]
|
||||
egl = ["smithay/use_system_lib", "smithay/backend_egl"]
|
||||
test_all_features = ["default", "debug"]
|
||||
udev = [
|
||||
"smithay-drm-extras",
|
||||
"smithay/backend_libinput",
|
||||
"smithay/backend_udev",
|
||||
"smithay/backend_drm",
|
||||
"smithay/backend_gbm",
|
||||
"smithay/backend_vulkan",
|
||||
"smithay/backend_egl",
|
||||
"smithay/backend_session_libseat",
|
||||
"image",
|
||||
"smithay/renderer_gl",
|
||||
"smithay/renderer_pixman",
|
||||
"smithay/renderer_multi",
|
||||
"xcursor",
|
||||
]
|
||||
winit = ["smithay/backend_winit", "smithay/backend_drm"]
|
||||
x11 = ["smithay/backend_x11", "x11rb", "smithay/renderer_gl", "smithay/backend_vulkan"]
|
||||
xwayland = ["smithay/xwayland", "x11rb", "smithay/x11rb_event_source", "xcursor"]
|
||||
libei = ["smithay/backend_libei", "udev"]
|
||||
profile-with-puffin = ["profiling/profile-with-puffin", "puffin_http"]
|
||||
profile-with-tracy = ["profiling/profile-with-tracy"]
|
||||
profile-with-tracy-mem = ["profile-with-tracy"]
|
||||
renderer_sync = []
|
||||
Vendored
+54
@@ -0,0 +1,54 @@
|
||||
# Anvil
|
||||
|
||||
A compositor used as a testing ground for new smithay features.
|
||||
For a simple example compositor consider reading [smallvil](https://github.com/Smithay/smithay/tree/master/smallvil)
|
||||
|
||||
## Dependencies
|
||||
|
||||
You'll need to install the following dependencies (note, that those package
|
||||
names may vary depending on your OS and linux distribution):
|
||||
|
||||
- `libwayland`
|
||||
- `libxkbcommon`
|
||||
|
||||
#### These are needed for the "Udev/DRM backend"
|
||||
|
||||
- `libudev`
|
||||
- `libinput`
|
||||
- `libgbm`
|
||||
- [`libseat`](https://git.sr.ht/~kennylevinsen/seatd)
|
||||
|
||||
If you want to enable X11 support (to run X11 applications within anvil),
|
||||
then you'll need to install the following packages as well:
|
||||
- `xwayland`
|
||||
|
||||
## Build and run
|
||||
|
||||
You can run it with cargo after having cloned this repository:
|
||||
|
||||
```
|
||||
cd anvil;
|
||||
|
||||
cargo run -- --{backend}
|
||||
```
|
||||
|
||||
The currently available backends are:
|
||||
|
||||
- `--x11`: start anvil as an X11 client. This allows you to run the compositor inside an X11 session or any compositor supporting XWayland. Should be preferred over the winit backend where possible.
|
||||
- `--winit`: start anvil as a [Winit](https://github.com/tomaka/winit) application. This allows you to run it
|
||||
inside of an other X11 or Wayland session.
|
||||
- `--tty-udev`: start anvil in a tty with udev support. This is the "traditional" launch of a Wayland
|
||||
compositor. Note that this requires you to start anvil as root if your system does not have logind
|
||||
available.
|
||||
|
||||
### Supported Environment Variables
|
||||
|
||||
| Variable | Example | Backends |
|
||||
|-------------------------------|-----------------|-----------|
|
||||
| ANVIL_DRM_DEVICE | /dev/dri/card0 | tty-udev |
|
||||
| ANVIL_DISABLE_10BIT | any | tty-udev |
|
||||
| ANVIL_DISABLE_DIRECT_SCANOUT | any | tty-udev |
|
||||
| ANVIL_GLES_DISABLE_INSTANCING | any | tty-udev |
|
||||
| ANVIL_NO_VULKAN | 1,true,yes,y | x11 |
|
||||
| SMITHAY_USE_LEGACY | 1,true,yes,y | tty-udev |
|
||||
| SMITHAY_VK_VERSION | 1.3 | |
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
Vendored
+96
@@ -0,0 +1,96 @@
|
||||
use std::{io::Read, time::Duration};
|
||||
|
||||
use tracing::warn;
|
||||
use xcursor::{
|
||||
CursorTheme,
|
||||
parser::{Image, parse_xcursor},
|
||||
};
|
||||
|
||||
static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../resources/cursor.rgba");
|
||||
|
||||
pub struct Cursor {
|
||||
icons: Vec<Image>,
|
||||
size: u32,
|
||||
}
|
||||
|
||||
impl Cursor {
|
||||
pub fn load() -> Cursor {
|
||||
let name = std::env::var("XCURSOR_THEME")
|
||||
.ok()
|
||||
.unwrap_or_else(|| "default".into());
|
||||
let size = std::env::var("XCURSOR_SIZE")
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or(24);
|
||||
|
||||
let theme = CursorTheme::load(&name);
|
||||
let icons = load_icon(&theme)
|
||||
.map_err(|err| warn!("Unable to load xcursor: {}, using fallback cursor", err))
|
||||
.unwrap_or_else(|_| {
|
||||
vec![Image {
|
||||
size: 32,
|
||||
width: 64,
|
||||
height: 64,
|
||||
xhot: 1,
|
||||
yhot: 1,
|
||||
delay: 1,
|
||||
pixels_rgba: Vec::from(FALLBACK_CURSOR_DATA),
|
||||
pixels_argb: vec![], //unused
|
||||
}]
|
||||
});
|
||||
|
||||
Cursor { icons, size }
|
||||
}
|
||||
|
||||
pub fn get_image(&self, scale: u32, time: Duration) -> Image {
|
||||
let size = self.size * scale;
|
||||
frame(time.as_millis() as u32, size, &self.icons)
|
||||
}
|
||||
}
|
||||
|
||||
fn nearest_images(size: u32, images: &[Image]) -> impl Iterator<Item = &Image> {
|
||||
// Follow the nominal size of the cursor to choose the nearest
|
||||
let nearest_image = images
|
||||
.iter()
|
||||
.min_by_key(|image| (size as i32 - image.size as i32).abs())
|
||||
.unwrap();
|
||||
|
||||
images
|
||||
.iter()
|
||||
.filter(move |image| image.width == nearest_image.width && image.height == nearest_image.height)
|
||||
}
|
||||
|
||||
fn frame(mut millis: u32, size: u32, images: &[Image]) -> Image {
|
||||
let total = nearest_images(size, images).fold(0, |acc, image| acc + image.delay);
|
||||
if total == 0 {
|
||||
return nearest_images(size, images).next().unwrap().clone();
|
||||
}
|
||||
millis %= total;
|
||||
|
||||
for img in nearest_images(size, images) {
|
||||
if millis < img.delay {
|
||||
return img.clone();
|
||||
}
|
||||
millis -= img.delay;
|
||||
}
|
||||
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
enum Error {
|
||||
#[error("Theme has no default cursor")]
|
||||
NoDefaultCursor,
|
||||
#[error("Error opening xcursor file: {0}")]
|
||||
File(#[from] std::io::Error),
|
||||
#[error("Failed to parse XCursor file")]
|
||||
Parse,
|
||||
}
|
||||
|
||||
fn load_icon(theme: &CursorTheme) -> Result<Vec<Image>, Error> {
|
||||
let icon_path = theme.load_icon("default").ok_or(Error::NoDefaultCursor)?;
|
||||
let mut cursor_file = std::fs::File::open(icon_path)?;
|
||||
let mut cursor_data = Vec::new();
|
||||
cursor_file.read_to_end(&mut cursor_data)?;
|
||||
parse_xcursor(&cursor_data).ok_or(Error::Parse)
|
||||
}
|
||||
Vendored
+259
@@ -0,0 +1,259 @@
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use smithay::{
|
||||
backend::renderer::{
|
||||
Color32F, ImportAll, ImportMem, Renderer, Texture,
|
||||
element::{
|
||||
AsRenderElements, Kind,
|
||||
memory::{MemoryRenderBuffer, MemoryRenderBufferRenderElement},
|
||||
surface::WaylandSurfaceRenderElement,
|
||||
},
|
||||
},
|
||||
input::pointer::CursorImageStatus,
|
||||
render_elements,
|
||||
utils::{Physical, Point, Scale},
|
||||
};
|
||||
#[cfg(feature = "debug")]
|
||||
use smithay::{
|
||||
backend::renderer::{
|
||||
Frame,
|
||||
element::{Element, Id, RenderElement},
|
||||
utils::CommitCounter,
|
||||
},
|
||||
utils::{Buffer, Logical, Rectangle, Size, Transform, user_data::UserDataMap},
|
||||
};
|
||||
|
||||
pub static CLEAR_COLOR: Color32F = Color32F::new(0.8, 0.8, 0.9, 1.0);
|
||||
pub static CLEAR_COLOR_FULLSCREEN: Color32F = Color32F::new(0.0, 0.0, 0.0, 0.0);
|
||||
|
||||
pub struct PointerElement {
|
||||
buffer: Option<MemoryRenderBuffer>,
|
||||
status: CursorImageStatus,
|
||||
}
|
||||
|
||||
impl Default for PointerElement {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
buffer: Default::default(),
|
||||
status: CursorImageStatus::default_named(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PointerElement {
|
||||
pub fn set_status(&mut self, status: CursorImageStatus) {
|
||||
self.status = status;
|
||||
}
|
||||
|
||||
pub fn set_buffer(&mut self, buffer: MemoryRenderBuffer) {
|
||||
self.buffer = Some(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
render_elements! {
|
||||
pub PointerRenderElement<R> where R: ImportAll + ImportMem;
|
||||
Surface=WaylandSurfaceRenderElement<R>,
|
||||
Memory=MemoryRenderBufferRenderElement<R>,
|
||||
}
|
||||
|
||||
impl<R: Renderer> std::fmt::Debug for PointerRenderElement<R> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Surface(arg0) => f.debug_tuple("Surface").field(arg0).finish(),
|
||||
Self::Memory(arg0) => f.debug_tuple("Memory").field(arg0).finish(),
|
||||
Self::_GenericCatcher(arg0) => f.debug_tuple("_GenericCatcher").field(arg0).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Texture + Clone + Send + 'static, R> AsRenderElements<R> for PointerElement
|
||||
where
|
||||
R: Renderer<TextureId = T> + ImportAll + ImportMem,
|
||||
{
|
||||
type RenderElement = PointerRenderElement<R>;
|
||||
fn render_elements<E>(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
location: Point<i32, Physical>,
|
||||
scale: Scale<f64>,
|
||||
alpha: f32,
|
||||
) -> Vec<E>
|
||||
where
|
||||
E: From<PointerRenderElement<R>>,
|
||||
{
|
||||
match &self.status {
|
||||
CursorImageStatus::Hidden => vec![],
|
||||
// Always render `Default` for a named shape.
|
||||
CursorImageStatus::Named(_) => {
|
||||
if let Some(buffer) = self.buffer.as_ref() {
|
||||
vec![
|
||||
PointerRenderElement::<R>::from(
|
||||
MemoryRenderBufferRenderElement::from_buffer(
|
||||
renderer,
|
||||
location.to_f64(),
|
||||
buffer,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Kind::Cursor,
|
||||
)
|
||||
.expect("Lost system pointer buffer"),
|
||||
)
|
||||
.into(),
|
||||
]
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
CursorImageStatus::Surface(surface) => {
|
||||
let elements: Vec<PointerRenderElement<R>> =
|
||||
smithay::backend::renderer::element::surface::render_elements_from_surface_tree(
|
||||
renderer,
|
||||
surface,
|
||||
location,
|
||||
scale,
|
||||
alpha,
|
||||
Kind::Cursor,
|
||||
);
|
||||
elements.into_iter().map(E::from).collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
pub static FPS_NUMBERS_PNG: &[u8] = include_bytes!("../resources/numbers.png");
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FpsElement<T: Texture> {
|
||||
id: Id,
|
||||
value: u32,
|
||||
texture: T,
|
||||
commit_counter: CommitCounter,
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
impl<T: Texture> FpsElement<T> {
|
||||
pub fn new(texture: T) -> Self {
|
||||
FpsElement {
|
||||
id: Id::new(),
|
||||
texture,
|
||||
value: 0,
|
||||
commit_counter: CommitCounter::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_fps(&mut self, fps: u32) {
|
||||
if self.value != fps {
|
||||
self.value = fps;
|
||||
self.commit_counter.increment();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
impl<T> Element for FpsElement<T>
|
||||
where
|
||||
T: Texture + 'static,
|
||||
{
|
||||
fn id(&self) -> &Id {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn location(&self, _scale: Scale<f64>) -> Point<i32, Physical> {
|
||||
(0, 0).into()
|
||||
}
|
||||
|
||||
fn src(&self) -> Rectangle<f64, Buffer> {
|
||||
let digits = if self.value < 10 {
|
||||
1
|
||||
} else if self.value < 100 {
|
||||
2
|
||||
} else {
|
||||
3
|
||||
};
|
||||
Rectangle::from_size((24 * digits, 35).into()).to_f64()
|
||||
}
|
||||
|
||||
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
|
||||
let digits = if self.value < 10 {
|
||||
1
|
||||
} else if self.value < 100 {
|
||||
2
|
||||
} else {
|
||||
3
|
||||
};
|
||||
Rectangle::from_size((24 * digits, 35).into()).to_physical_precise_round(scale)
|
||||
}
|
||||
|
||||
fn current_commit(&self) -> CommitCounter {
|
||||
self.commit_counter
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
impl<R> RenderElement<R> for FpsElement<R::TextureId>
|
||||
where
|
||||
R: Renderer + ImportAll,
|
||||
R::TextureId: 'static,
|
||||
{
|
||||
fn draw(
|
||||
&self,
|
||||
frame: &mut R::Frame<'_, '_>,
|
||||
_src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
_opaque_regions: &[Rectangle<i32, Physical>],
|
||||
_cache: Option<&UserDataMap>,
|
||||
) -> Result<(), R::Error> {
|
||||
// FIXME: respect the src for cropping
|
||||
let scale = dst.size.to_f64() / self.src().size;
|
||||
let value_str = std::cmp::min(self.value, 999).to_string();
|
||||
let mut offset: Point<f64, Physical> = Point::from((0.0, 0.0));
|
||||
for digit in value_str.chars().map(|d| d.to_digit(10).unwrap()) {
|
||||
let digit_location = dst.loc.to_f64() + offset;
|
||||
let digit_size = Size::<i32, Logical>::from((22, 35)).to_f64().to_physical(scale);
|
||||
let dst = Rectangle::new(
|
||||
digit_location.to_i32_round(),
|
||||
((digit_size.to_point() + digit_location).to_i32_round() - digit_location.to_i32_round())
|
||||
.to_size(),
|
||||
);
|
||||
let damage = damage
|
||||
.iter()
|
||||
.cloned()
|
||||
.flat_map(|x| x.intersection(dst))
|
||||
.map(|mut x| {
|
||||
x.loc -= dst.loc;
|
||||
x
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let texture_src: Rectangle<i32, Buffer> = match digit {
|
||||
9 => Rectangle::from_size((22, 35).into()),
|
||||
6 => Rectangle::new((22, 0).into(), (22, 35).into()),
|
||||
3 => Rectangle::new((44, 0).into(), (22, 35).into()),
|
||||
1 => Rectangle::new((66, 0).into(), (22, 35).into()),
|
||||
8 => Rectangle::new((0, 35).into(), (22, 35).into()),
|
||||
0 => Rectangle::new((22, 35).into(), (22, 35).into()),
|
||||
2 => Rectangle::new((44, 35).into(), (22, 35).into()),
|
||||
7 => Rectangle::new((0, 70).into(), (22, 35).into()),
|
||||
4 => Rectangle::new((22, 70).into(), (22, 35).into()),
|
||||
5 => Rectangle::new((44, 70).into(), (22, 35).into()),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
frame.render_texture_from_to(
|
||||
&self.texture,
|
||||
texture_src.to_f64(),
|
||||
dst,
|
||||
&damage,
|
||||
&[],
|
||||
Transform::Normal,
|
||||
1.0,
|
||||
)?;
|
||||
offset += Point::from((24.0, 0.0)).to_physical(scale);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Vendored
+712
@@ -0,0 +1,712 @@
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
|
||||
#[cfg(feature = "xwayland")]
|
||||
use smithay::xwayland::X11Surface;
|
||||
#[cfg(feature = "xwayland")]
|
||||
use smithay::xwayland::xwm::XwmOfferData;
|
||||
pub use smithay::{
|
||||
backend::input::{InputTime, KeyState},
|
||||
desktop::{LayerSurface, PopupKind},
|
||||
input::{
|
||||
Seat,
|
||||
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
|
||||
pointer::{AxisFrame, ButtonEvent, MotionEvent, PointerTarget, RelativeMotionEvent},
|
||||
},
|
||||
reexports::wayland_server::{Resource, backend::ObjectId, protocol::wl_surface::WlSurface},
|
||||
utils::{IsAlive, Serial},
|
||||
wayland::seat::WaylandFocus,
|
||||
};
|
||||
use smithay::{
|
||||
desktop::{Window, WindowSurface},
|
||||
input::{
|
||||
dnd::{DndFocus, OfferData, Source},
|
||||
pointer::{
|
||||
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent,
|
||||
GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
||||
},
|
||||
tablet::tool::TabletToolTarget,
|
||||
touch::{FrameMarker, TouchTarget},
|
||||
},
|
||||
reexports::wayland_server::DisplayHandle,
|
||||
utils::{Logical, Point},
|
||||
wayland::selection::data_device::WlOfferData,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
shell::{SSD, WindowElement},
|
||||
state::{AnvilState, Backend},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum KeyboardFocusTarget {
|
||||
Window(Window),
|
||||
LayerSurface(LayerSurface),
|
||||
Popup(PopupKind),
|
||||
}
|
||||
|
||||
impl IsAlive for KeyboardFocusTarget {
|
||||
#[inline]
|
||||
fn alive(&self) -> bool {
|
||||
match self {
|
||||
KeyboardFocusTarget::Window(w) => w.alive(),
|
||||
KeyboardFocusTarget::LayerSurface(l) => l.alive(),
|
||||
KeyboardFocusTarget::Popup(p) => p.alive(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum PointerFocusTarget {
|
||||
WlSurface(WlSurface),
|
||||
#[cfg(feature = "xwayland")]
|
||||
X11Surface(X11Surface),
|
||||
SSD(SSD),
|
||||
}
|
||||
|
||||
impl IsAlive for PointerFocusTarget {
|
||||
#[inline]
|
||||
fn alive(&self) -> bool {
|
||||
match self {
|
||||
PointerFocusTarget::WlSurface(w) => w.alive(),
|
||||
#[cfg(feature = "xwayland")]
|
||||
PointerFocusTarget::X11Surface(w) => w.alive(),
|
||||
PointerFocusTarget::SSD(x) => x.alive(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PointerFocusTarget> for WlSurface {
|
||||
#[inline]
|
||||
fn from(target: PointerFocusTarget) -> Self {
|
||||
target.wl_surface().unwrap().into_owned()
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyboardFocusTarget {
|
||||
fn inner_keyboard_target<BackendData: Backend>(&self) -> &dyn KeyboardTarget<AnvilState<BackendData>> {
|
||||
match self {
|
||||
Self::Window(w) => match w.underlying_surface() {
|
||||
WindowSurface::Wayland(w) => w.wl_surface(),
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(s) => s,
|
||||
},
|
||||
Self::LayerSurface(l) => l.wl_surface(),
|
||||
Self::Popup(p) => p.wl_surface(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PointerFocusTarget {
|
||||
fn inner_pointer_target<BackendData: Backend>(&self) -> &dyn PointerTarget<AnvilState<BackendData>> {
|
||||
match self {
|
||||
Self::WlSurface(w) => w,
|
||||
#[cfg(feature = "xwayland")]
|
||||
Self::X11Surface(w) => w,
|
||||
Self::SSD(w) => w,
|
||||
}
|
||||
}
|
||||
|
||||
fn inner_touch_target<BackendData: Backend>(&self) -> &dyn TouchTarget<AnvilState<BackendData>> {
|
||||
match self {
|
||||
Self::WlSurface(w) => w,
|
||||
#[cfg(feature = "xwayland")]
|
||||
Self::X11Surface(w) => w,
|
||||
Self::SSD(w) => w,
|
||||
}
|
||||
}
|
||||
|
||||
fn inner_tablet_tool_target<BackendData: Backend>(
|
||||
&self,
|
||||
) -> &dyn TabletToolTarget<AnvilState<BackendData>> {
|
||||
match self {
|
||||
Self::WlSurface(w) => w,
|
||||
#[cfg(feature = "xwayland")]
|
||||
Self::X11Surface(w) => w,
|
||||
Self::SSD(w) => w,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> PointerTarget<AnvilState<BackendData>> for PointerFocusTarget {
|
||||
fn enter(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &MotionEvent,
|
||||
) {
|
||||
self.inner_pointer_target().enter(seat, data, event)
|
||||
}
|
||||
fn motion(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &MotionEvent,
|
||||
) {
|
||||
self.inner_pointer_target().motion(seat, data, event)
|
||||
}
|
||||
fn relative_motion(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &RelativeMotionEvent,
|
||||
) {
|
||||
self.inner_pointer_target().relative_motion(seat, data, event)
|
||||
}
|
||||
fn button(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &ButtonEvent,
|
||||
) {
|
||||
self.inner_pointer_target().button(seat, data, event)
|
||||
}
|
||||
fn axis(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
frame: AxisFrame,
|
||||
) {
|
||||
self.inner_pointer_target().axis(seat, data, frame)
|
||||
}
|
||||
fn frame(&self, seat: &Seat<AnvilState<BackendData>>, data: &mut AnvilState<BackendData>) {
|
||||
self.inner_pointer_target().frame(seat, data)
|
||||
}
|
||||
fn leave(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
serial: Serial,
|
||||
time: InputTime,
|
||||
) {
|
||||
self.inner_pointer_target().leave(seat, data, serial, time)
|
||||
}
|
||||
fn gesture_swipe_begin(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &GestureSwipeBeginEvent,
|
||||
) {
|
||||
self.inner_pointer_target().gesture_swipe_begin(seat, data, event)
|
||||
}
|
||||
fn gesture_swipe_update(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &GestureSwipeUpdateEvent,
|
||||
) {
|
||||
self.inner_pointer_target()
|
||||
.gesture_swipe_update(seat, data, event)
|
||||
}
|
||||
fn gesture_swipe_end(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &GestureSwipeEndEvent,
|
||||
) {
|
||||
self.inner_pointer_target().gesture_swipe_end(seat, data, event)
|
||||
}
|
||||
fn gesture_pinch_begin(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &GesturePinchBeginEvent,
|
||||
) {
|
||||
self.inner_pointer_target().gesture_pinch_begin(seat, data, event)
|
||||
}
|
||||
fn gesture_pinch_update(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &GesturePinchUpdateEvent,
|
||||
) {
|
||||
self.inner_pointer_target()
|
||||
.gesture_pinch_update(seat, data, event)
|
||||
}
|
||||
fn gesture_pinch_end(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &GesturePinchEndEvent,
|
||||
) {
|
||||
self.inner_pointer_target().gesture_pinch_end(seat, data, event)
|
||||
}
|
||||
fn gesture_hold_begin(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &GestureHoldBeginEvent,
|
||||
) {
|
||||
self.inner_pointer_target().gesture_hold_begin(seat, data, event)
|
||||
}
|
||||
fn gesture_hold_end(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &GestureHoldEndEvent,
|
||||
) {
|
||||
self.inner_pointer_target().gesture_hold_end(seat, data, event)
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> KeyboardTarget<AnvilState<BackendData>> for KeyboardFocusTarget {
|
||||
fn enter(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
keys: Vec<KeysymHandle<'_>>,
|
||||
serial: Serial,
|
||||
) {
|
||||
self.inner_keyboard_target().enter(seat, data, keys, serial)
|
||||
}
|
||||
fn leave(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
serial: Serial,
|
||||
) {
|
||||
self.inner_keyboard_target().leave(seat, data, serial)
|
||||
}
|
||||
fn key(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
key: KeysymHandle<'_>,
|
||||
state: KeyState,
|
||||
serial: Serial,
|
||||
time: InputTime,
|
||||
) {
|
||||
self.inner_keyboard_target()
|
||||
.key(seat, data, key, state, serial, time)
|
||||
}
|
||||
fn modifiers(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
modifiers: ModifiersState,
|
||||
serial: Serial,
|
||||
) {
|
||||
self.inner_keyboard_target()
|
||||
.modifiers(seat, data, modifiers, serial)
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> TouchTarget<AnvilState<BackendData>> for PointerFocusTarget {
|
||||
fn down(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &smithay::input::touch::DownEvent,
|
||||
) {
|
||||
self.inner_touch_target().down(seat, data, event)
|
||||
}
|
||||
|
||||
fn up(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &smithay::input::touch::UpEvent,
|
||||
) {
|
||||
self.inner_touch_target().up(seat, data, event)
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &smithay::input::touch::MotionEvent,
|
||||
) {
|
||||
self.inner_touch_target().motion(seat, data, event)
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
marker: FrameMarker,
|
||||
) {
|
||||
self.inner_touch_target().frame(seat, data, marker)
|
||||
}
|
||||
|
||||
fn cancel(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
marker: FrameMarker,
|
||||
) {
|
||||
self.inner_touch_target().cancel(seat, data, marker)
|
||||
}
|
||||
|
||||
fn shape(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &smithay::input::touch::ShapeEvent,
|
||||
) {
|
||||
self.inner_touch_target().shape(seat, data, event)
|
||||
}
|
||||
|
||||
fn orientation(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &smithay::input::touch::OrientationEvent,
|
||||
) {
|
||||
self.inner_touch_target().orientation(seat, data, event)
|
||||
}
|
||||
|
||||
fn last_frame(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
) -> Option<FrameMarker> {
|
||||
self.inner_touch_target().last_frame(seat, data)
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> TabletToolTarget<AnvilState<BackendData>> for PointerFocusTarget {
|
||||
fn proximity_in(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
tablet: &smithay::input::tablet::Tablet,
|
||||
serial: Serial,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.proximity_in(seat, data, tool_descriptor, tablet, serial);
|
||||
}
|
||||
|
||||
fn proximity_out(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.proximity_out(seat, data, tool_descriptor);
|
||||
}
|
||||
|
||||
fn down(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
event: &smithay::input::tablet::tool::DownEvent,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.down(seat, data, tool_descriptor, event);
|
||||
}
|
||||
|
||||
fn up(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
event: &smithay::input::tablet::tool::UpEvent,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.up(seat, data, tool_descriptor, event);
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
event: &smithay::input::tablet::tool::MotionEvent,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.motion(seat, data, tool_descriptor, event);
|
||||
}
|
||||
|
||||
fn button(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
event: &smithay::input::tablet::tool::ButtonEvent,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.button(seat, data, tool_descriptor, event);
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
frame: smithay::input::tablet::tool::AxisFrame,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.axis(seat, data, tool_descriptor, frame);
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
time: InputTime,
|
||||
) {
|
||||
self.inner_tablet_tool_target()
|
||||
.frame(seat, data, tool_descriptor, time);
|
||||
}
|
||||
}
|
||||
|
||||
impl WaylandFocus for PointerFocusTarget {
|
||||
#[inline]
|
||||
fn wl_surface(&self) -> Option<Cow<'_, WlSurface>> {
|
||||
match self {
|
||||
PointerFocusTarget::WlSurface(w) => w.wl_surface(),
|
||||
#[cfg(feature = "xwayland")]
|
||||
PointerFocusTarget::X11Surface(w) => w.wl_surface().map(Cow::Owned),
|
||||
PointerFocusTarget::SSD(_) => None,
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
fn same_client_as(&self, object_id: &ObjectId) -> bool {
|
||||
match self {
|
||||
PointerFocusTarget::WlSurface(w) => w.same_client_as(object_id),
|
||||
#[cfg(feature = "xwayland")]
|
||||
PointerFocusTarget::X11Surface(w) => w.same_client_as(object_id),
|
||||
PointerFocusTarget::SSD(w) => w
|
||||
.wl_surface()
|
||||
.map(|surface| surface.same_client_as(object_id))
|
||||
.unwrap_or(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WaylandFocus for KeyboardFocusTarget {
|
||||
#[inline]
|
||||
fn wl_surface(&self) -> Option<Cow<'_, WlSurface>> {
|
||||
match self {
|
||||
KeyboardFocusTarget::Window(w) => w.wl_surface(),
|
||||
KeyboardFocusTarget::LayerSurface(l) => Some(Cow::Borrowed(l.wl_surface())),
|
||||
KeyboardFocusTarget::Popup(p) => Some(Cow::Borrowed(p.wl_surface())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum AnvilOfferData<S: Source> {
|
||||
Wayland(WlOfferData<S>),
|
||||
#[cfg(feature = "xwayland")]
|
||||
X11(XwmOfferData<S>),
|
||||
}
|
||||
|
||||
impl<S: Source> OfferData for AnvilOfferData<S> {
|
||||
fn disable(&self) {
|
||||
match self {
|
||||
AnvilOfferData::Wayland(data) => data.disable(),
|
||||
#[cfg(feature = "xwayland")]
|
||||
AnvilOfferData::X11(data) => data.disable(),
|
||||
}
|
||||
}
|
||||
|
||||
fn drop(&self) {
|
||||
match self {
|
||||
AnvilOfferData::Wayland(data) => data.drop(),
|
||||
#[cfg(feature = "xwayland")]
|
||||
AnvilOfferData::X11(data) => data.drop(),
|
||||
}
|
||||
}
|
||||
|
||||
fn validated(&self) -> bool {
|
||||
match self {
|
||||
AnvilOfferData::Wayland(data) => data.validated(),
|
||||
#[cfg(feature = "xwayland")]
|
||||
AnvilOfferData::X11(data) => data.validated(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unreachable_patterns)]
|
||||
impl<BackendData: Backend> DndFocus<AnvilState<BackendData>> for PointerFocusTarget {
|
||||
type OfferData<S>
|
||||
= AnvilOfferData<S>
|
||||
where
|
||||
S: Source;
|
||||
|
||||
fn enter<S: Source>(
|
||||
&self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
dh: &DisplayHandle,
|
||||
source: Arc<S>,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
location: Point<f64, Logical>,
|
||||
serial: &Serial,
|
||||
) -> Option<AnvilOfferData<S>> {
|
||||
match self {
|
||||
PointerFocusTarget::WlSurface(surface) => {
|
||||
DndFocus::enter(surface, data, dh, source, seat, location, serial)
|
||||
.map(AnvilOfferData::Wayland)
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
PointerFocusTarget::X11Surface(surface) => {
|
||||
DndFocus::enter(surface, data, dh, source, seat, location, serial).map(AnvilOfferData::X11)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn motion<S: Source>(
|
||||
&self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
offer: Option<&mut AnvilOfferData<S>>,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
location: Point<f64, Logical>,
|
||||
time: InputTime,
|
||||
) {
|
||||
match self {
|
||||
PointerFocusTarget::WlSurface(surface) => {
|
||||
let offer = match offer {
|
||||
Some(AnvilOfferData::Wayland(offer)) => Some(offer),
|
||||
None => None,
|
||||
_ => return,
|
||||
};
|
||||
DndFocus::motion(surface, data, offer, seat, location, time)
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
PointerFocusTarget::X11Surface(surface) => {
|
||||
let offer = match offer {
|
||||
Some(AnvilOfferData::X11(offer)) => Some(offer),
|
||||
None => None,
|
||||
_ => return,
|
||||
};
|
||||
DndFocus::motion(surface, data, offer, seat, location, time)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn leave<S: Source>(
|
||||
&self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
offer: Option<&mut AnvilOfferData<S>>,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
) {
|
||||
match self {
|
||||
PointerFocusTarget::WlSurface(surface) => {
|
||||
let offer = match offer {
|
||||
Some(AnvilOfferData::Wayland(offer)) => Some(offer),
|
||||
None => None,
|
||||
_ => return,
|
||||
};
|
||||
DndFocus::leave(surface, data, offer, seat)
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
PointerFocusTarget::X11Surface(surface) => {
|
||||
let offer = match offer {
|
||||
Some(AnvilOfferData::X11(offer)) => Some(offer),
|
||||
None => None,
|
||||
_ => return,
|
||||
};
|
||||
DndFocus::leave(surface, data, offer, seat)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn drop<S: Source>(
|
||||
&self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
offer: Option<&mut AnvilOfferData<S>>,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
) {
|
||||
match self {
|
||||
PointerFocusTarget::WlSurface(surface) => {
|
||||
let offer = match offer {
|
||||
Some(AnvilOfferData::Wayland(offer)) => Some(offer),
|
||||
None => None,
|
||||
_ => return,
|
||||
};
|
||||
DndFocus::drop(surface, data, offer, seat)
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
PointerFocusTarget::X11Surface(surface) => {
|
||||
let offer = match offer {
|
||||
Some(AnvilOfferData::X11(offer)) => Some(offer),
|
||||
None => None,
|
||||
_ => return,
|
||||
};
|
||||
DndFocus::drop(surface, data, offer, seat)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WlSurface> for PointerFocusTarget {
|
||||
#[inline]
|
||||
fn from(value: WlSurface) -> Self {
|
||||
PointerFocusTarget::WlSurface(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&WlSurface> for PointerFocusTarget {
|
||||
#[inline]
|
||||
fn from(value: &WlSurface) -> Self {
|
||||
PointerFocusTarget::from(value.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PopupKind> for PointerFocusTarget {
|
||||
#[inline]
|
||||
fn from(value: PopupKind) -> Self {
|
||||
PointerFocusTarget::from(value.wl_surface())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "xwayland")]
|
||||
impl From<X11Surface> for PointerFocusTarget {
|
||||
#[inline]
|
||||
fn from(value: X11Surface) -> Self {
|
||||
PointerFocusTarget::X11Surface(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "xwayland")]
|
||||
impl From<&X11Surface> for PointerFocusTarget {
|
||||
#[inline]
|
||||
fn from(value: &X11Surface) -> Self {
|
||||
PointerFocusTarget::from(value.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WindowElement> for KeyboardFocusTarget {
|
||||
#[inline]
|
||||
fn from(w: WindowElement) -> Self {
|
||||
KeyboardFocusTarget::Window(w.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LayerSurface> for KeyboardFocusTarget {
|
||||
#[inline]
|
||||
fn from(l: LayerSurface) -> Self {
|
||||
KeyboardFocusTarget::LayerSurface(l)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PopupKind> for KeyboardFocusTarget {
|
||||
#[inline]
|
||||
fn from(p: PopupKind) -> Self {
|
||||
KeyboardFocusTarget::Popup(p)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeyboardFocusTarget> for PointerFocusTarget {
|
||||
#[inline]
|
||||
fn from(value: KeyboardFocusTarget) -> Self {
|
||||
match value {
|
||||
KeyboardFocusTarget::Window(w) => match w.underlying_surface() {
|
||||
WindowSurface::Wayland(w) => PointerFocusTarget::from(w.wl_surface()),
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(s) => PointerFocusTarget::from(s),
|
||||
},
|
||||
KeyboardFocusTarget::LayerSurface(surface) => PointerFocusTarget::from(surface.wl_surface()),
|
||||
KeyboardFocusTarget::Popup(popup) => PointerFocusTarget::from(popup.wl_surface()),
|
||||
}
|
||||
}
|
||||
}
|
||||
+1457
File diff suppressed because it is too large.
Load diff
Vendored
+27
@@ -0,0 +1,27 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
#![allow(clippy::collapsible_match)]
|
||||
// If no backend is enabled, a large portion of the codebase is unused.
|
||||
// So silence this useless warning for the CI.
|
||||
#![cfg_attr(
|
||||
not(any(feature = "winit", feature = "x11", feature = "udev")),
|
||||
allow(dead_code, unused_imports)
|
||||
)]
|
||||
|
||||
#[cfg(any(feature = "udev", feature = "xwayland"))]
|
||||
pub mod cursor;
|
||||
pub mod drawing;
|
||||
pub mod focus;
|
||||
pub mod input_handler;
|
||||
#[cfg(feature = "libei")]
|
||||
pub mod libei;
|
||||
pub mod render;
|
||||
pub mod shell;
|
||||
pub mod state;
|
||||
#[cfg(feature = "udev")]
|
||||
pub mod udev;
|
||||
#[cfg(feature = "winit")]
|
||||
pub mod winit;
|
||||
#[cfg(feature = "x11")]
|
||||
pub mod x11;
|
||||
|
||||
pub use state::{AnvilState, ClientState};
|
||||
Vendored
+50
@@ -0,0 +1,50 @@
|
||||
use smithay::{
|
||||
backend::libei::{EiInput, EiInputEvent},
|
||||
input::keyboard::XkbConfig,
|
||||
reexports::{
|
||||
calloop,
|
||||
reis::{calloop::EisListenerSource, eis},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{state::AnvilState, udev::UdevData};
|
||||
|
||||
pub fn listen_eis(handle: &calloop::LoopHandle<'static, AnvilState<UdevData>>) {
|
||||
let listener = match eis::Listener::bind_auto() {
|
||||
Ok(listener) => listener,
|
||||
Err(err) => {
|
||||
tracing::error!("Failed to bind EI listener socket: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
unsafe { std::env::set_var("LIBEI_SOCKET", listener.path()) };
|
||||
|
||||
let listener_source = EisListenerSource::new(listener);
|
||||
let handle_clone = handle.clone();
|
||||
handle
|
||||
.insert_source(listener_source, move |context, _, _| {
|
||||
let source = EiInput::new(context);
|
||||
handle_clone
|
||||
.insert_source(source, |event, connection, data| match event {
|
||||
EiInputEvent::Connected => {
|
||||
let seat = connection.add_seat("default");
|
||||
let _ = seat.add_keyboard("virtual keyboard", XkbConfig::default());
|
||||
seat.add_pointer("virtual pointer");
|
||||
seat.add_pointer_absolute("virtual absolute pointer", &[]);
|
||||
seat.add_touch("virtual touch", &[]);
|
||||
}
|
||||
EiInputEvent::Disconnected => {}
|
||||
EiInputEvent::Event(event) => {
|
||||
let dh = data.display_handle.clone();
|
||||
data.process_input_event(&dh, event);
|
||||
}
|
||||
EiInputEvent::TextKeysym { .. } | EiInputEvent::TextUtf8 { .. } => {
|
||||
// Anvil doesn't add a text device to the libei seat
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
Ok(calloop::PostAction::Continue)
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
Vendored
+69
@@ -0,0 +1,69 @@
|
||||
static POSSIBLE_BACKENDS: &[&str] = &[
|
||||
#[cfg(feature = "winit")]
|
||||
"--winit : Run anvil as a X11 or Wayland client using winit.",
|
||||
#[cfg(feature = "udev")]
|
||||
"--tty-udev : Run anvil as a tty udev client (requires root if without logind).",
|
||||
#[cfg(feature = "x11")]
|
||||
"--x11 : Run anvil as an X11 client.",
|
||||
];
|
||||
|
||||
#[cfg(feature = "profile-with-tracy-mem")]
|
||||
#[global_allocator]
|
||||
static GLOBAL: profiling::tracy_client::ProfiledAllocator<std::alloc::System> =
|
||||
profiling::tracy_client::ProfiledAllocator::new(std::alloc::System, 10);
|
||||
|
||||
// Allow in this function because of existing usage
|
||||
#[allow(clippy::uninlined_format_args)]
|
||||
fn main() {
|
||||
if let Ok(env_filter) = tracing_subscriber::EnvFilter::try_from_default_env() {
|
||||
tracing_subscriber::fmt()
|
||||
.compact()
|
||||
.with_env_filter(env_filter)
|
||||
.init();
|
||||
} else {
|
||||
tracing_subscriber::fmt().compact().init();
|
||||
}
|
||||
|
||||
#[cfg(feature = "profile-with-tracy")]
|
||||
profiling::tracy_client::Client::start();
|
||||
|
||||
profiling::register_thread!("Main Thread");
|
||||
|
||||
#[cfg(feature = "profile-with-puffin")]
|
||||
let _server = puffin_http::Server::new(&format!("0.0.0.0:{}", puffin_http::DEFAULT_PORT)).unwrap();
|
||||
#[cfg(feature = "profile-with-puffin")]
|
||||
profiling::puffin::set_scopes_on(true);
|
||||
|
||||
let arg = ::std::env::args().nth(1);
|
||||
match arg.as_ref().map(|s| &s[..]) {
|
||||
#[cfg(feature = "winit")]
|
||||
Some("--winit") => {
|
||||
tracing::info!("Starting anvil with winit backend");
|
||||
anvil::winit::run_winit();
|
||||
}
|
||||
#[cfg(feature = "udev")]
|
||||
Some("--tty-udev") => {
|
||||
tracing::info!("Starting anvil on a tty using udev");
|
||||
anvil::udev::run_udev();
|
||||
}
|
||||
#[cfg(feature = "x11")]
|
||||
Some("--x11") => {
|
||||
tracing::info!("Starting anvil with x11 backend");
|
||||
anvil::x11::run_x11();
|
||||
}
|
||||
Some(other) => {
|
||||
tracing::error!("Unknown backend: {}", other);
|
||||
}
|
||||
None => {
|
||||
#[allow(clippy::disallowed_macros)]
|
||||
{
|
||||
println!("USAGE: anvil --backend");
|
||||
println!();
|
||||
println!("Possible backends are:");
|
||||
for b in POSSIBLE_BACKENDS {
|
||||
println!("\t{b}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+210
@@ -0,0 +1,210 @@
|
||||
use smithay::{
|
||||
backend::renderer::{
|
||||
Color32F, ImportAll, ImportMem, Renderer,
|
||||
damage::{Error as OutputDamageTrackerError, OutputDamageTracker, RenderOutputResult},
|
||||
element::{
|
||||
AsRenderElements, RenderElement, Wrap,
|
||||
surface::WaylandSurfaceRenderElement,
|
||||
utils::{
|
||||
ConstrainAlign, ConstrainScaleBehavior, CropRenderElement, RelocateRenderElement,
|
||||
RescaleRenderElement,
|
||||
},
|
||||
},
|
||||
},
|
||||
desktop::space::{
|
||||
ConstrainBehavior, ConstrainReference, Space, SpaceRenderElements, constrain_space_element,
|
||||
},
|
||||
output::Output,
|
||||
utils::{Point, Rectangle, Size},
|
||||
};
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
use crate::drawing::FpsElement;
|
||||
use crate::{
|
||||
drawing::{CLEAR_COLOR, CLEAR_COLOR_FULLSCREEN, PointerRenderElement},
|
||||
shell::{FullscreenSurface, WindowElement, WindowRenderElement},
|
||||
};
|
||||
|
||||
smithay::backend::renderer::element::render_elements! {
|
||||
pub CustomRenderElements<R> where
|
||||
R: ImportAll + ImportMem;
|
||||
Pointer=PointerRenderElement<R>,
|
||||
Surface=WaylandSurfaceRenderElement<R>,
|
||||
#[cfg(feature = "debug")]
|
||||
// Note: We would like to borrow this element instead, but that would introduce
|
||||
// a feature-dependent lifetime, which introduces a lot more feature bounds
|
||||
// as the whole type changes and we can't have an unused lifetime (for when "debug" is disabled)
|
||||
// in the declaration.
|
||||
Fps=FpsElement<R::TextureId>,
|
||||
}
|
||||
|
||||
impl<R: Renderer> std::fmt::Debug for CustomRenderElements<R> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Pointer(arg0) => f.debug_tuple("Pointer").field(arg0).finish(),
|
||||
Self::Surface(arg0) => f.debug_tuple("Surface").field(arg0).finish(),
|
||||
#[cfg(feature = "debug")]
|
||||
Self::Fps(arg0) => f.debug_tuple("Fps").field(arg0).finish(),
|
||||
Self::_GenericCatcher(arg0) => f.debug_tuple("_GenericCatcher").field(arg0).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
smithay::backend::renderer::element::render_elements! {
|
||||
pub OutputRenderElements<R, E> where R: ImportAll + ImportMem;
|
||||
Space=SpaceRenderElements<R, E>,
|
||||
Window=Wrap<E>,
|
||||
Custom=CustomRenderElements<R>,
|
||||
Preview=CropRenderElement<RelocateRenderElement<RescaleRenderElement<WindowRenderElement<R>>>>,
|
||||
}
|
||||
|
||||
impl<R: Renderer + ImportAll + ImportMem, E: RenderElement<R> + std::fmt::Debug> std::fmt::Debug
|
||||
for OutputRenderElements<R, E>
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Space(arg0) => f.debug_tuple("Space").field(arg0).finish(),
|
||||
Self::Window(arg0) => f.debug_tuple("Window").field(arg0).finish(),
|
||||
Self::Custom(arg0) => f.debug_tuple("Custom").field(arg0).finish(),
|
||||
Self::Preview(arg0) => f.debug_tuple("Preview").field(arg0).finish(),
|
||||
Self::_GenericCatcher(arg0) => f.debug_tuple("_GenericCatcher").field(arg0).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn space_preview_elements<'a, R, C>(
|
||||
renderer: &'a mut R,
|
||||
space: &'a Space<WindowElement>,
|
||||
output: &'a Output,
|
||||
) -> impl Iterator<Item = C> + 'a
|
||||
where
|
||||
R: Renderer + ImportAll + ImportMem,
|
||||
R::TextureId: Clone + 'static,
|
||||
C: From<CropRenderElement<RelocateRenderElement<RescaleRenderElement<WindowRenderElement<R>>>>> + 'a,
|
||||
{
|
||||
let constrain_behavior = ConstrainBehavior {
|
||||
reference: ConstrainReference::BoundingBox,
|
||||
behavior: ConstrainScaleBehavior::Fit,
|
||||
align: ConstrainAlign::CENTER,
|
||||
};
|
||||
|
||||
let preview_padding = 10;
|
||||
|
||||
let elements_on_space = space.elements_for_output(output).count();
|
||||
let output_scale = output.current_scale().fractional_scale();
|
||||
let output_transform = output.current_transform();
|
||||
let output_size = output
|
||||
.current_mode()
|
||||
.map(|mode| {
|
||||
output_transform
|
||||
.transform_size(mode.size)
|
||||
.to_f64()
|
||||
.to_logical(output_scale)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
let max_elements_per_row = 4;
|
||||
let elements_per_row = usize::min(elements_on_space, max_elements_per_row);
|
||||
let rows = f64::ceil(elements_on_space as f64 / elements_per_row as f64);
|
||||
|
||||
let preview_size = Size::from((
|
||||
f64::round(output_size.w / elements_per_row as f64) as i32 - preview_padding * 2,
|
||||
f64::round(output_size.h / rows) as i32 - preview_padding * 2,
|
||||
));
|
||||
|
||||
space
|
||||
.elements_for_output(output)
|
||||
.enumerate()
|
||||
.flat_map(move |(element_index, window)| {
|
||||
let column = element_index % elements_per_row;
|
||||
let row = element_index / elements_per_row;
|
||||
let preview_location = Point::from((
|
||||
preview_padding + (preview_padding + preview_size.w) * column as i32,
|
||||
preview_padding + (preview_padding + preview_size.h) * row as i32,
|
||||
));
|
||||
let constrain = Rectangle::new(preview_location, preview_size);
|
||||
constrain_space_element(
|
||||
renderer,
|
||||
window,
|
||||
preview_location,
|
||||
1.0,
|
||||
output_scale,
|
||||
constrain,
|
||||
constrain_behavior,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[profiling::function]
|
||||
pub fn output_elements<R>(
|
||||
output: &Output,
|
||||
space: &Space<WindowElement>,
|
||||
custom_elements: impl IntoIterator<Item = CustomRenderElements<R>>,
|
||||
renderer: &mut R,
|
||||
show_window_preview: bool,
|
||||
) -> (Vec<OutputRenderElements<R, WindowRenderElement<R>>>, Color32F)
|
||||
where
|
||||
R: Renderer + ImportAll + ImportMem,
|
||||
R::TextureId: Clone + 'static,
|
||||
{
|
||||
if let Some(window) = output
|
||||
.user_data()
|
||||
.get::<FullscreenSurface>()
|
||||
.and_then(|f| f.get())
|
||||
{
|
||||
let scale = output.current_scale().fractional_scale().into();
|
||||
let window_render_elements: Vec<WindowRenderElement<R>> =
|
||||
AsRenderElements::<R>::render_elements(&window, renderer, (0, 0).into(), scale, 1.0);
|
||||
|
||||
let elements = custom_elements
|
||||
.into_iter()
|
||||
.map(OutputRenderElements::from)
|
||||
.chain(
|
||||
window_render_elements
|
||||
.into_iter()
|
||||
.map(|e| OutputRenderElements::Window(Wrap::from(e))),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
(elements, CLEAR_COLOR_FULLSCREEN)
|
||||
} else {
|
||||
let mut output_render_elements = custom_elements
|
||||
.into_iter()
|
||||
.map(OutputRenderElements::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if show_window_preview && space.elements_for_output(output).count() > 0 {
|
||||
output_render_elements.extend(space_preview_elements(renderer, space, output));
|
||||
}
|
||||
|
||||
let space_elements = smithay::desktop::space::space_render_elements::<_, WindowElement, _>(
|
||||
renderer,
|
||||
[space],
|
||||
output,
|
||||
1.0,
|
||||
)
|
||||
.expect("output without mode?");
|
||||
output_render_elements.extend(space_elements.into_iter().map(OutputRenderElements::Space));
|
||||
|
||||
(output_render_elements, CLEAR_COLOR)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn render_output<'a, 'd, R>(
|
||||
output: &'a Output,
|
||||
space: &'a Space<WindowElement>,
|
||||
custom_elements: impl IntoIterator<Item = CustomRenderElements<R>>,
|
||||
renderer: &'a mut R,
|
||||
framebuffer: &'a mut R::Framebuffer<'_>,
|
||||
damage_tracker: &'d mut OutputDamageTracker,
|
||||
age: usize,
|
||||
show_window_preview: bool,
|
||||
) -> Result<RenderOutputResult<'d>, OutputDamageTrackerError<R::Error>>
|
||||
where
|
||||
R: Renderer + ImportAll + ImportMem,
|
||||
R::TextureId: Clone + 'static,
|
||||
{
|
||||
let (elements, clear_color) =
|
||||
output_elements(output, space, custom_elements, renderer, show_window_preview);
|
||||
damage_tracker.render_output(renderer, framebuffer, age, &elements, clear_color)
|
||||
}
|
||||
+564
@@ -0,0 +1,564 @@
|
||||
use std::{borrow::Cow, time::Duration};
|
||||
|
||||
use smithay::{
|
||||
backend::{
|
||||
input::InputTime,
|
||||
renderer::{
|
||||
ImportAll, ImportMem, Renderer, Texture,
|
||||
element::{
|
||||
AsRenderElements, solid::SolidColorRenderElement, surface::WaylandSurfaceRenderElement,
|
||||
},
|
||||
},
|
||||
},
|
||||
desktop::{
|
||||
Window, WindowSurface, WindowSurfaceType, space::SpaceElement, utils::OutputPresentationFeedback,
|
||||
},
|
||||
input::{
|
||||
Seat,
|
||||
pointer::{
|
||||
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent,
|
||||
GestureSwipeUpdateEvent, MotionEvent, PointerTarget, RelativeMotionEvent,
|
||||
},
|
||||
tablet::tool::TabletToolTarget,
|
||||
touch::{FrameMarker, TouchTarget},
|
||||
},
|
||||
output::Output,
|
||||
reexports::{
|
||||
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback,
|
||||
wayland_server::protocol::wl_surface::WlSurface,
|
||||
},
|
||||
render_elements,
|
||||
utils::{IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial, user_data::UserDataMap},
|
||||
wayland::{compositor::SurfaceData as WlSurfaceData, dmabuf::DmabufFeedback, seat::WaylandFocus},
|
||||
};
|
||||
|
||||
use super::ssd::HEADER_BAR_HEIGHT;
|
||||
use crate::{AnvilState, focus::PointerFocusTarget, state::Backend};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct WindowElement(pub Window);
|
||||
|
||||
impl WindowElement {
|
||||
pub fn surface_under(
|
||||
&self,
|
||||
location: Point<f64, Logical>,
|
||||
window_type: WindowSurfaceType,
|
||||
) -> Option<(PointerFocusTarget, Point<i32, Logical>)> {
|
||||
let state = self.decoration_state();
|
||||
if state.is_ssd && location.y < HEADER_BAR_HEIGHT as f64 {
|
||||
return Some((PointerFocusTarget::SSD(SSD(self.clone())), Point::default()));
|
||||
}
|
||||
let offset = if state.is_ssd {
|
||||
Point::from((0, HEADER_BAR_HEIGHT))
|
||||
} else {
|
||||
Point::default()
|
||||
};
|
||||
|
||||
let surface_under = self.0.surface_under(location - offset.to_f64(), window_type);
|
||||
let (under, loc) = match self.0.underlying_surface() {
|
||||
WindowSurface::Wayland(_) => {
|
||||
surface_under.map(|(surface, loc)| (PointerFocusTarget::WlSurface(surface), loc))
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(s) => {
|
||||
surface_under.map(|(_, loc)| (PointerFocusTarget::X11Surface(s.clone()), loc))
|
||||
}
|
||||
}?;
|
||||
Some((under, loc + offset))
|
||||
}
|
||||
|
||||
pub fn with_surfaces<F>(&self, processor: F)
|
||||
where
|
||||
F: FnMut(&WlSurface, &WlSurfaceData),
|
||||
{
|
||||
self.0.with_surfaces(processor);
|
||||
}
|
||||
|
||||
pub fn send_frame<T, F>(
|
||||
&self,
|
||||
output: &Output,
|
||||
time: T,
|
||||
throttle: Option<Duration>,
|
||||
primary_scan_out_output: F,
|
||||
) where
|
||||
T: Into<Duration>,
|
||||
F: FnMut(&WlSurface, &WlSurfaceData) -> Option<Output> + Copy,
|
||||
{
|
||||
self.0.send_frame(output, time, throttle, primary_scan_out_output)
|
||||
}
|
||||
|
||||
pub fn send_dmabuf_feedback<'a, P, F>(
|
||||
&self,
|
||||
output: &Output,
|
||||
primary_scan_out_output: P,
|
||||
select_dmabuf_feedback: F,
|
||||
) where
|
||||
P: FnMut(&WlSurface, &WlSurfaceData) -> Option<Output> + Copy,
|
||||
F: Fn(&WlSurface, &WlSurfaceData) -> &'a DmabufFeedback + Copy,
|
||||
{
|
||||
self.0
|
||||
.send_dmabuf_feedback(output, primary_scan_out_output, select_dmabuf_feedback)
|
||||
}
|
||||
|
||||
pub fn take_presentation_feedback<F1, F2>(
|
||||
&self,
|
||||
output_feedback: &mut OutputPresentationFeedback,
|
||||
primary_scan_out_output: F1,
|
||||
presentation_feedback_flags: F2,
|
||||
) where
|
||||
F1: FnMut(&WlSurface, &WlSurfaceData) -> Option<Output> + Copy,
|
||||
F2: FnMut(&WlSurface, &WlSurfaceData) -> wp_presentation_feedback::Kind + Copy,
|
||||
{
|
||||
self.0.take_presentation_feedback(
|
||||
output_feedback,
|
||||
primary_scan_out_output,
|
||||
presentation_feedback_flags,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "xwayland")]
|
||||
#[inline]
|
||||
pub fn is_x11(&self) -> bool {
|
||||
self.0.is_x11()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_wayland(&self) -> bool {
|
||||
self.0.is_wayland()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn wl_surface(&self) -> Option<Cow<'_, WlSurface>> {
|
||||
self.0.wl_surface()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn user_data(&self) -> &UserDataMap {
|
||||
self.0.user_data()
|
||||
}
|
||||
}
|
||||
|
||||
impl IsAlive for WindowElement {
|
||||
#[inline]
|
||||
fn alive(&self) -> bool {
|
||||
self.0.alive()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct SSD(WindowElement);
|
||||
|
||||
impl IsAlive for SSD {
|
||||
#[inline]
|
||||
fn alive(&self) -> bool {
|
||||
self.0.alive()
|
||||
}
|
||||
}
|
||||
|
||||
impl WaylandFocus for SSD {
|
||||
#[inline]
|
||||
fn wl_surface(&self) -> Option<Cow<'_, WlSurface>> {
|
||||
self.0.wl_surface()
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> PointerTarget<AnvilState<BackendData>> for SSD {
|
||||
fn enter(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
event: &MotionEvent,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.pointer_enter(event.location);
|
||||
}
|
||||
}
|
||||
fn motion(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
event: &MotionEvent,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.pointer_enter(event.location);
|
||||
}
|
||||
}
|
||||
fn relative_motion(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &RelativeMotionEvent,
|
||||
) {
|
||||
}
|
||||
fn button(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &ButtonEvent,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.clicked(seat, data, &self.0, event.serial);
|
||||
}
|
||||
}
|
||||
fn axis(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_frame: AxisFrame,
|
||||
) {
|
||||
}
|
||||
fn frame(&self, _seat: &Seat<AnvilState<BackendData>>, _data: &mut AnvilState<BackendData>) {}
|
||||
fn leave(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_serial: Serial,
|
||||
_time: InputTime,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.pointer_leave();
|
||||
}
|
||||
}
|
||||
fn gesture_swipe_begin(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &GestureSwipeBeginEvent,
|
||||
) {
|
||||
}
|
||||
fn gesture_swipe_update(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &GestureSwipeUpdateEvent,
|
||||
) {
|
||||
}
|
||||
fn gesture_swipe_end(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &GestureSwipeEndEvent,
|
||||
) {
|
||||
}
|
||||
fn gesture_pinch_begin(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &GesturePinchBeginEvent,
|
||||
) {
|
||||
}
|
||||
fn gesture_pinch_update(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &GesturePinchUpdateEvent,
|
||||
) {
|
||||
}
|
||||
fn gesture_pinch_end(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &GesturePinchEndEvent,
|
||||
) {
|
||||
}
|
||||
fn gesture_hold_begin(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &GestureHoldBeginEvent,
|
||||
) {
|
||||
}
|
||||
fn gesture_hold_end(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &GestureHoldEndEvent,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> TouchTarget<AnvilState<BackendData>> for SSD {
|
||||
fn down(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
event: &smithay::input::touch::DownEvent,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.pointer_enter(event.location);
|
||||
state.header_bar.touch_down(seat, data, &self.0, event.serial);
|
||||
}
|
||||
}
|
||||
|
||||
fn up(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
_event: &smithay::input::touch::UpEvent,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.touch_up(seat, data, &self.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
event: &smithay::input::touch::MotionEvent,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.pointer_enter(event.location);
|
||||
}
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_marker: FrameMarker,
|
||||
) {
|
||||
}
|
||||
|
||||
fn cancel(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_marker: FrameMarker,
|
||||
) {
|
||||
}
|
||||
|
||||
fn shape(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &smithay::input::touch::ShapeEvent,
|
||||
) {
|
||||
}
|
||||
|
||||
fn orientation(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_event: &smithay::input::touch::OrientationEvent,
|
||||
) {
|
||||
}
|
||||
|
||||
fn last_frame(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
) -> Option<FrameMarker> {
|
||||
// It would be more correct to store the marker on frame and cancel,
|
||||
// but since we're ignoring those anyway, no need for the added complexity.
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> TabletToolTarget<AnvilState<BackendData>> for SSD {
|
||||
fn proximity_in(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
_tablet: &smithay::input::tablet::Tablet,
|
||||
_serial: Serial,
|
||||
) {
|
||||
}
|
||||
|
||||
fn proximity_out(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.pointer_leave();
|
||||
}
|
||||
}
|
||||
|
||||
fn down(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
_tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
event: &smithay::input::tablet::tool::DownEvent,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.touch_down(seat, data, &self.0, event.serial);
|
||||
}
|
||||
}
|
||||
|
||||
fn up(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
_tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
_event: &smithay::input::tablet::tool::UpEvent,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.touch_up(seat, data, &self.0);
|
||||
}
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
event: &smithay::input::tablet::tool::MotionEvent,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.pointer_enter(event.location);
|
||||
}
|
||||
}
|
||||
|
||||
fn button(
|
||||
&self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
_tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
event: &smithay::input::tablet::tool::ButtonEvent,
|
||||
) {
|
||||
let mut state = self.0.decoration_state();
|
||||
if state.is_ssd {
|
||||
state.header_bar.clicked(seat, data, &self.0, event.serial);
|
||||
}
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
_frame: smithay::input::tablet::tool::AxisFrame,
|
||||
) {
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_tool_descriptor: &smithay::backend::input::TabletToolDescriptor,
|
||||
_time: InputTime,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
impl SpaceElement for WindowElement {
|
||||
fn geometry(&self) -> Rectangle<i32, Logical> {
|
||||
let mut geo = SpaceElement::geometry(&self.0);
|
||||
if self.decoration_state().is_ssd {
|
||||
geo.size.h += HEADER_BAR_HEIGHT;
|
||||
}
|
||||
geo
|
||||
}
|
||||
fn bbox(&self) -> Rectangle<i32, Logical> {
|
||||
let mut bbox = SpaceElement::bbox(&self.0);
|
||||
if self.decoration_state().is_ssd {
|
||||
bbox.size.h += HEADER_BAR_HEIGHT;
|
||||
}
|
||||
bbox
|
||||
}
|
||||
fn is_in_input_region(&self, point: &Point<f64, Logical>) -> bool {
|
||||
if self.decoration_state().is_ssd {
|
||||
point.y < HEADER_BAR_HEIGHT as f64
|
||||
|| SpaceElement::is_in_input_region(
|
||||
&self.0,
|
||||
&(*point - Point::from((0.0, HEADER_BAR_HEIGHT as f64))),
|
||||
)
|
||||
} else {
|
||||
SpaceElement::is_in_input_region(&self.0, point)
|
||||
}
|
||||
}
|
||||
fn z_index(&self) -> u8 {
|
||||
SpaceElement::z_index(&self.0)
|
||||
}
|
||||
|
||||
fn set_activate(&self, activated: bool) {
|
||||
SpaceElement::set_activate(&self.0, activated);
|
||||
}
|
||||
fn output_enter(&self, output: &Output, overlap: Rectangle<i32, Logical>) {
|
||||
SpaceElement::output_enter(&self.0, output, overlap);
|
||||
}
|
||||
fn output_leave(&self, output: &Output) {
|
||||
SpaceElement::output_leave(&self.0, output);
|
||||
}
|
||||
#[profiling::function]
|
||||
fn refresh(&self) {
|
||||
SpaceElement::refresh(&self.0);
|
||||
}
|
||||
}
|
||||
|
||||
render_elements!(
|
||||
pub WindowRenderElement<R> where R: ImportAll + ImportMem;
|
||||
Window=WaylandSurfaceRenderElement<R>,
|
||||
Decoration=SolidColorRenderElement,
|
||||
);
|
||||
|
||||
impl<R: Renderer> std::fmt::Debug for WindowRenderElement<R> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Window(arg0) => f.debug_tuple("Window").field(arg0).finish(),
|
||||
Self::Decoration(arg0) => f.debug_tuple("Decoration").field(arg0).finish(),
|
||||
Self::_GenericCatcher(arg0) => f.debug_tuple("_GenericCatcher").field(arg0).finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> AsRenderElements<R> for WindowElement
|
||||
where
|
||||
R: Renderer + ImportAll + ImportMem,
|
||||
R::TextureId: Clone + Texture + 'static,
|
||||
{
|
||||
type RenderElement = WindowRenderElement<R>;
|
||||
|
||||
fn render_elements<C: From<Self::RenderElement>>(
|
||||
&self,
|
||||
renderer: &mut R,
|
||||
mut location: Point<i32, Physical>,
|
||||
scale: Scale<f64>,
|
||||
alpha: f32,
|
||||
) -> Vec<C> {
|
||||
let window_bbox = SpaceElement::bbox(&self.0);
|
||||
|
||||
if self.decoration_state().is_ssd && !window_bbox.is_empty() {
|
||||
let window_geo = SpaceElement::geometry(&self.0);
|
||||
|
||||
let mut state = self.decoration_state();
|
||||
let width = window_geo.size.w;
|
||||
state.header_bar.redraw(width as u32);
|
||||
let mut vec = AsRenderElements::<R>::render_elements::<WindowRenderElement<R>>(
|
||||
&state.header_bar,
|
||||
renderer,
|
||||
location,
|
||||
scale,
|
||||
alpha,
|
||||
);
|
||||
|
||||
location.y += (scale.y * HEADER_BAR_HEIGHT as f64) as i32;
|
||||
|
||||
let window_elements =
|
||||
AsRenderElements::render_elements(&self.0, renderer, location, scale, alpha);
|
||||
vec.extend(window_elements);
|
||||
vec.into_iter().map(C::from).collect()
|
||||
} else {
|
||||
AsRenderElements::render_elements(&self.0, renderer, location, scale, alpha)
|
||||
.into_iter()
|
||||
.map(C::from)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
+855
@@ -0,0 +1,855 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use smithay::{
|
||||
desktop::{WindowSurface, space::SpaceElement},
|
||||
input::{
|
||||
pointer::{
|
||||
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent,
|
||||
GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab,
|
||||
PointerInnerHandle, RelativeMotionEvent,
|
||||
},
|
||||
touch::{GrabStartData as TouchGrabStartData, TouchGrab},
|
||||
},
|
||||
reexports::wayland_protocols::xdg::shell::server::xdg_toplevel,
|
||||
utils::{IsAlive, Logical, Point, Serial, Size},
|
||||
wayland::{compositor::with_states, shell::xdg::SurfaceCachedState},
|
||||
};
|
||||
#[cfg(feature = "xwayland")]
|
||||
use smithay::{utils::Rectangle, xwayland::xwm::ResizeEdge as X11ResizeEdge};
|
||||
|
||||
use super::{SurfaceData, WindowElement};
|
||||
use crate::{
|
||||
focus::PointerFocusTarget,
|
||||
state::{AnvilState, Backend},
|
||||
};
|
||||
|
||||
pub struct PointerMoveSurfaceGrab<BackendData: Backend + 'static> {
|
||||
pub start_data: PointerGrabStartData<AnvilState<BackendData>>,
|
||||
pub window: WindowElement,
|
||||
pub initial_window_location: Point<i32, Logical>,
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> PointerGrab<AnvilState<BackendData>> for PointerMoveSurfaceGrab<BackendData> {
|
||||
fn motion(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &MotionEvent,
|
||||
) {
|
||||
// While the grab is active, no client has pointer focus
|
||||
handle.motion(data, None, event);
|
||||
|
||||
let delta = event.location - self.start_data.location;
|
||||
let new_location = self.initial_window_location.to_f64() + delta;
|
||||
|
||||
data.space
|
||||
.map_element(self.window.clone(), new_location.to_i32_round(), true);
|
||||
}
|
||||
|
||||
fn relative_motion(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &RelativeMotionEvent,
|
||||
) {
|
||||
handle.relative_motion(data, focus, event);
|
||||
}
|
||||
|
||||
fn button(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &ButtonEvent,
|
||||
) {
|
||||
handle.button(data, event);
|
||||
if handle.current_pressed().is_empty() {
|
||||
// No more buttons are pressed, release the grab.
|
||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||
}
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
details: AxisFrame,
|
||||
) {
|
||||
handle.axis(data, details)
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
) {
|
||||
handle.frame(data);
|
||||
}
|
||||
|
||||
fn gesture_swipe_begin(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GestureSwipeBeginEvent,
|
||||
) {
|
||||
handle.gesture_swipe_begin(data, event);
|
||||
}
|
||||
|
||||
fn gesture_swipe_update(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GestureSwipeUpdateEvent,
|
||||
) {
|
||||
handle.gesture_swipe_update(data, event);
|
||||
}
|
||||
|
||||
fn gesture_swipe_end(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GestureSwipeEndEvent,
|
||||
) {
|
||||
handle.gesture_swipe_end(data, event);
|
||||
}
|
||||
|
||||
fn gesture_pinch_begin(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GesturePinchBeginEvent,
|
||||
) {
|
||||
handle.gesture_pinch_begin(data, event);
|
||||
}
|
||||
|
||||
fn gesture_pinch_update(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GesturePinchUpdateEvent,
|
||||
) {
|
||||
handle.gesture_pinch_update(data, event);
|
||||
}
|
||||
|
||||
fn gesture_pinch_end(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GesturePinchEndEvent,
|
||||
) {
|
||||
handle.gesture_pinch_end(data, event);
|
||||
}
|
||||
|
||||
fn gesture_hold_begin(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GestureHoldBeginEvent,
|
||||
) {
|
||||
handle.gesture_hold_begin(data, event);
|
||||
}
|
||||
|
||||
fn gesture_hold_end(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GestureHoldEndEvent,
|
||||
) {
|
||||
handle.gesture_hold_end(data, event);
|
||||
}
|
||||
|
||||
fn start_data(&self) -> &PointerGrabStartData<AnvilState<BackendData>> {
|
||||
&self.start_data
|
||||
}
|
||||
|
||||
fn unset(&mut self, _data: &mut AnvilState<BackendData>) {}
|
||||
}
|
||||
|
||||
pub struct TouchMoveSurfaceGrab<BackendData: Backend + 'static> {
|
||||
pub start_data: TouchGrabStartData<AnvilState<BackendData>>,
|
||||
pub window: WindowElement,
|
||||
pub initial_window_location: Point<i32, Logical>,
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> TouchGrab<AnvilState<BackendData>> for TouchMoveSurfaceGrab<BackendData> {
|
||||
fn down(
|
||||
&mut self,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
_focus: Option<(
|
||||
<AnvilState<BackendData> as smithay::input::SeatHandler>::TouchFocus,
|
||||
Point<f64, Logical>,
|
||||
)>,
|
||||
_event: &smithay::input::touch::DownEvent,
|
||||
) {
|
||||
}
|
||||
|
||||
fn up(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &smithay::input::touch::UpEvent,
|
||||
) {
|
||||
if event.slot != self.start_data.slot {
|
||||
return;
|
||||
}
|
||||
|
||||
handle.up(data, event);
|
||||
handle.unset_grab(self, data);
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
_handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
_focus: Option<(
|
||||
<AnvilState<BackendData> as smithay::input::SeatHandler>::TouchFocus,
|
||||
Point<f64, Logical>,
|
||||
)>,
|
||||
event: &smithay::input::touch::MotionEvent,
|
||||
) {
|
||||
if event.slot != self.start_data.slot {
|
||||
return;
|
||||
}
|
||||
|
||||
let delta = event.location - self.start_data.location;
|
||||
let new_location = self.initial_window_location.to_f64() + delta;
|
||||
data.space
|
||||
.map_element(self.window.clone(), new_location.to_i32_round(), true);
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&mut self,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn cancel(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
) {
|
||||
handle.cancel(data);
|
||||
handle.unset_grab(self, data);
|
||||
}
|
||||
|
||||
fn shape(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &smithay::input::touch::ShapeEvent,
|
||||
) {
|
||||
handle.shape(data, event);
|
||||
}
|
||||
|
||||
fn orientation(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &smithay::input::touch::OrientationEvent,
|
||||
) {
|
||||
handle.orientation(data, event);
|
||||
}
|
||||
|
||||
fn start_data(&self) -> &smithay::input::touch::GrabStartData<AnvilState<BackendData>> {
|
||||
&self.start_data
|
||||
}
|
||||
|
||||
fn unset(&mut self, _data: &mut AnvilState<BackendData>) {}
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ResizeEdge: u32 {
|
||||
const NONE = 0;
|
||||
const TOP = 1;
|
||||
const BOTTOM = 2;
|
||||
const LEFT = 4;
|
||||
const TOP_LEFT = 5;
|
||||
const BOTTOM_LEFT = 6;
|
||||
const RIGHT = 8;
|
||||
const TOP_RIGHT = 9;
|
||||
const BOTTOM_RIGHT = 10;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<xdg_toplevel::ResizeEdge> for ResizeEdge {
|
||||
#[inline]
|
||||
fn from(x: xdg_toplevel::ResizeEdge) -> Self {
|
||||
Self::from_bits(x as u32).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ResizeEdge> for xdg_toplevel::ResizeEdge {
|
||||
#[inline]
|
||||
fn from(x: ResizeEdge) -> Self {
|
||||
Self::try_from(x.bits()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "xwayland")]
|
||||
impl From<X11ResizeEdge> for ResizeEdge {
|
||||
#[inline]
|
||||
fn from(edge: X11ResizeEdge) -> Self {
|
||||
match edge {
|
||||
X11ResizeEdge::Bottom => ResizeEdge::BOTTOM,
|
||||
X11ResizeEdge::BottomLeft => ResizeEdge::BOTTOM_LEFT,
|
||||
X11ResizeEdge::BottomRight => ResizeEdge::BOTTOM_RIGHT,
|
||||
X11ResizeEdge::Left => ResizeEdge::LEFT,
|
||||
X11ResizeEdge::Right => ResizeEdge::RIGHT,
|
||||
X11ResizeEdge::Top => ResizeEdge::TOP,
|
||||
X11ResizeEdge::TopLeft => ResizeEdge::TOP_LEFT,
|
||||
X11ResizeEdge::TopRight => ResizeEdge::TOP_RIGHT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about the resize operation.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct ResizeData {
|
||||
/// The edges the surface is being resized with.
|
||||
pub edges: ResizeEdge,
|
||||
/// The initial window location.
|
||||
pub initial_window_location: Point<i32, Logical>,
|
||||
/// The initial window size (geometry width and height).
|
||||
pub initial_window_size: Size<i32, Logical>,
|
||||
}
|
||||
|
||||
/// State of the resize operation.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
|
||||
pub enum ResizeState {
|
||||
/// The surface is not being resized.
|
||||
#[default]
|
||||
NotResizing,
|
||||
/// The surface is currently being resized.
|
||||
Resizing(ResizeData),
|
||||
/// The resize has finished, and the surface needs to ack the final configure.
|
||||
WaitingForFinalAck(ResizeData, Serial),
|
||||
/// The resize has finished, and the surface needs to commit its final state.
|
||||
WaitingForCommit(ResizeData),
|
||||
}
|
||||
|
||||
pub struct PointerResizeSurfaceGrab<BackendData: Backend + 'static> {
|
||||
pub start_data: PointerGrabStartData<AnvilState<BackendData>>,
|
||||
pub window: WindowElement,
|
||||
pub edges: ResizeEdge,
|
||||
pub initial_window_location: Point<i32, Logical>,
|
||||
pub initial_window_size: Size<i32, Logical>,
|
||||
pub last_window_size: Size<i32, Logical>,
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> PointerGrab<AnvilState<BackendData>> for PointerResizeSurfaceGrab<BackendData> {
|
||||
fn motion(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &MotionEvent,
|
||||
) {
|
||||
// While the grab is active, no client has pointer focus
|
||||
handle.motion(data, None, event);
|
||||
|
||||
// It is impossible to get `min_size` and `max_size` of dead toplevel, so we return early.
|
||||
if !self.window.alive() {
|
||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||
return;
|
||||
}
|
||||
|
||||
let (mut dx, mut dy) = (event.location - self.start_data.location).into();
|
||||
|
||||
let mut new_window_width = self.initial_window_size.w;
|
||||
let mut new_window_height = self.initial_window_size.h;
|
||||
|
||||
let left_right = ResizeEdge::LEFT | ResizeEdge::RIGHT;
|
||||
let top_bottom = ResizeEdge::TOP | ResizeEdge::BOTTOM;
|
||||
|
||||
if self.edges.intersects(left_right) {
|
||||
if self.edges.intersects(ResizeEdge::LEFT) {
|
||||
dx = -dx;
|
||||
}
|
||||
|
||||
new_window_width = (self.initial_window_size.w as f64 + dx) as i32;
|
||||
}
|
||||
|
||||
if self.edges.intersects(top_bottom) {
|
||||
if self.edges.intersects(ResizeEdge::TOP) {
|
||||
dy = -dy;
|
||||
}
|
||||
|
||||
new_window_height = (self.initial_window_size.h as f64 + dy) as i32;
|
||||
}
|
||||
|
||||
let (min_size, max_size) = if let Some(surface) = self.window.wl_surface() {
|
||||
with_states(&surface, |states| {
|
||||
let mut guard = states.cached_state.get::<SurfaceCachedState>();
|
||||
let data = guard.current();
|
||||
(data.min_size, data.max_size)
|
||||
})
|
||||
} else {
|
||||
((0, 0).into(), (0, 0).into())
|
||||
};
|
||||
|
||||
let min_width = min_size.w.max(1);
|
||||
let min_height = min_size.h.max(1);
|
||||
let max_width = if max_size.w == 0 { i32::MAX } else { max_size.w };
|
||||
let max_height = if max_size.h == 0 { i32::MAX } else { max_size.h };
|
||||
|
||||
new_window_width = new_window_width.max(min_width).min(max_width);
|
||||
new_window_height = new_window_height.max(min_height).min(max_height);
|
||||
|
||||
self.last_window_size = (new_window_width, new_window_height).into();
|
||||
|
||||
match &self.window.0.underlying_surface() {
|
||||
WindowSurface::Wayland(xdg) => {
|
||||
xdg.with_pending_state(|state| {
|
||||
state.states.set(xdg_toplevel::State::Resizing);
|
||||
state.size = Some(self.last_window_size);
|
||||
});
|
||||
xdg.send_pending_configure();
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(x11) => {
|
||||
let location = data.space.element_location(&self.window).unwrap();
|
||||
x11.configure_with_sync(Rectangle::new(location, self.last_window_size), None)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn relative_motion(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||
event: &RelativeMotionEvent,
|
||||
) {
|
||||
handle.relative_motion(data, focus, event);
|
||||
}
|
||||
|
||||
fn button(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &ButtonEvent,
|
||||
) {
|
||||
handle.button(data, event);
|
||||
if handle.current_pressed().is_empty() {
|
||||
// No more buttons are pressed, release the grab.
|
||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||
|
||||
// If toplevel is dead, we can't resize it, so we return early.
|
||||
if !self.window.alive() {
|
||||
return;
|
||||
}
|
||||
|
||||
match &self.window.0.underlying_surface() {
|
||||
WindowSurface::Wayland(xdg) => {
|
||||
xdg.with_pending_state(|state| {
|
||||
state.states.unset(xdg_toplevel::State::Resizing);
|
||||
state.size = Some(self.last_window_size);
|
||||
});
|
||||
xdg.send_pending_configure();
|
||||
if self.edges.intersects(ResizeEdge::TOP_LEFT) {
|
||||
let geometry = self.window.geometry();
|
||||
let mut location = data.space.element_location(&self.window).unwrap();
|
||||
|
||||
if self.edges.intersects(ResizeEdge::LEFT) {
|
||||
location.x = self.initial_window_location.x
|
||||
+ (self.initial_window_size.w - geometry.size.w);
|
||||
}
|
||||
if self.edges.intersects(ResizeEdge::TOP) {
|
||||
location.y = self.initial_window_location.y
|
||||
+ (self.initial_window_size.h - geometry.size.h);
|
||||
}
|
||||
|
||||
data.space.map_element(self.window.clone(), location, true);
|
||||
}
|
||||
|
||||
with_states(&self.window.wl_surface().unwrap(), |states| {
|
||||
let mut data = states
|
||||
.data_map
|
||||
.get::<RefCell<SurfaceData>>()
|
||||
.unwrap()
|
||||
.borrow_mut();
|
||||
if let ResizeState::Resizing(resize_data) = data.resize_state {
|
||||
data.resize_state = ResizeState::WaitingForFinalAck(resize_data, event.serial);
|
||||
} else {
|
||||
panic!("invalid resize state: {:?}", data.resize_state);
|
||||
}
|
||||
});
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(x11) => {
|
||||
let mut location = data.space.element_location(&self.window).unwrap();
|
||||
if self.edges.intersects(ResizeEdge::TOP_LEFT) {
|
||||
let geometry = self.window.geometry();
|
||||
|
||||
if self.edges.intersects(ResizeEdge::LEFT) {
|
||||
location.x = self.initial_window_location.x
|
||||
+ (self.initial_window_size.w - geometry.size.w);
|
||||
}
|
||||
if self.edges.intersects(ResizeEdge::TOP) {
|
||||
location.y = self.initial_window_location.y
|
||||
+ (self.initial_window_size.h - geometry.size.h);
|
||||
}
|
||||
|
||||
data.space.map_element(self.window.clone(), location, true);
|
||||
}
|
||||
x11.configure_with_sync(Rectangle::new(location, self.last_window_size), None)
|
||||
.unwrap();
|
||||
|
||||
let Some(surface) = self.window.wl_surface() else {
|
||||
// X11 Window got unmapped, abort
|
||||
return;
|
||||
};
|
||||
with_states(&surface, |states| {
|
||||
let mut data = states
|
||||
.data_map
|
||||
.get::<RefCell<SurfaceData>>()
|
||||
.unwrap()
|
||||
.borrow_mut();
|
||||
if let ResizeState::Resizing(resize_data) = data.resize_state {
|
||||
data.resize_state = ResizeState::WaitingForCommit(resize_data);
|
||||
} else {
|
||||
panic!("invalid resize state: {:?}", data.resize_state);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
details: AxisFrame,
|
||||
) {
|
||||
handle.axis(data, details)
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
) {
|
||||
handle.frame(data);
|
||||
}
|
||||
|
||||
fn gesture_swipe_begin(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GestureSwipeBeginEvent,
|
||||
) {
|
||||
handle.gesture_swipe_begin(data, event);
|
||||
}
|
||||
|
||||
fn gesture_swipe_update(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GestureSwipeUpdateEvent,
|
||||
) {
|
||||
handle.gesture_swipe_update(data, event);
|
||||
}
|
||||
|
||||
fn gesture_swipe_end(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GestureSwipeEndEvent,
|
||||
) {
|
||||
handle.gesture_swipe_end(data, event);
|
||||
}
|
||||
|
||||
fn gesture_pinch_begin(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GesturePinchBeginEvent,
|
||||
) {
|
||||
handle.gesture_pinch_begin(data, event);
|
||||
}
|
||||
|
||||
fn gesture_pinch_update(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GesturePinchUpdateEvent,
|
||||
) {
|
||||
handle.gesture_pinch_update(data, event);
|
||||
}
|
||||
|
||||
fn gesture_pinch_end(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GesturePinchEndEvent,
|
||||
) {
|
||||
handle.gesture_pinch_end(data, event);
|
||||
}
|
||||
|
||||
fn gesture_hold_begin(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GestureHoldBeginEvent,
|
||||
) {
|
||||
handle.gesture_hold_begin(data, event);
|
||||
}
|
||||
|
||||
fn gesture_hold_end(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut PointerInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &GestureHoldEndEvent,
|
||||
) {
|
||||
handle.gesture_hold_end(data, event);
|
||||
}
|
||||
|
||||
fn start_data(&self) -> &PointerGrabStartData<AnvilState<BackendData>> {
|
||||
&self.start_data
|
||||
}
|
||||
|
||||
fn unset(&mut self, _data: &mut AnvilState<BackendData>) {}
|
||||
}
|
||||
|
||||
pub struct TouchResizeSurfaceGrab<BackendData: Backend + 'static> {
|
||||
pub start_data: TouchGrabStartData<AnvilState<BackendData>>,
|
||||
pub window: WindowElement,
|
||||
pub edges: ResizeEdge,
|
||||
pub initial_window_location: Point<i32, Logical>,
|
||||
pub initial_window_size: Size<i32, Logical>,
|
||||
pub last_window_size: Size<i32, Logical>,
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> TouchGrab<AnvilState<BackendData>> for TouchResizeSurfaceGrab<BackendData> {
|
||||
fn down(
|
||||
&mut self,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
_focus: Option<(
|
||||
<AnvilState<BackendData> as smithay::input::SeatHandler>::TouchFocus,
|
||||
Point<f64, Logical>,
|
||||
)>,
|
||||
_event: &smithay::input::touch::DownEvent,
|
||||
) {
|
||||
}
|
||||
|
||||
fn up(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &smithay::input::touch::UpEvent,
|
||||
) {
|
||||
if event.slot != self.start_data.slot {
|
||||
return;
|
||||
}
|
||||
handle.unset_grab(self, data);
|
||||
|
||||
// If toplevel is dead, we can't resize it, so we return early.
|
||||
if !self.window.alive() {
|
||||
return;
|
||||
}
|
||||
|
||||
match self.window.0.underlying_surface() {
|
||||
WindowSurface::Wayland(xdg) => {
|
||||
xdg.with_pending_state(|state| {
|
||||
state.states.unset(xdg_toplevel::State::Resizing);
|
||||
state.size = Some(self.last_window_size);
|
||||
});
|
||||
xdg.send_pending_configure();
|
||||
if self.edges.intersects(ResizeEdge::TOP_LEFT) {
|
||||
let geometry = self.window.geometry();
|
||||
let mut location = data.space.element_location(&self.window).unwrap();
|
||||
|
||||
if self.edges.intersects(ResizeEdge::LEFT) {
|
||||
location.x =
|
||||
self.initial_window_location.x + (self.initial_window_size.w - geometry.size.w);
|
||||
}
|
||||
if self.edges.intersects(ResizeEdge::TOP) {
|
||||
location.y =
|
||||
self.initial_window_location.y + (self.initial_window_size.h - geometry.size.h);
|
||||
}
|
||||
|
||||
data.space.map_element(self.window.clone(), location, true);
|
||||
}
|
||||
|
||||
with_states(&self.window.wl_surface().unwrap(), |states| {
|
||||
let mut data = states
|
||||
.data_map
|
||||
.get::<RefCell<SurfaceData>>()
|
||||
.unwrap()
|
||||
.borrow_mut();
|
||||
if let ResizeState::Resizing(resize_data) = data.resize_state {
|
||||
data.resize_state = ResizeState::WaitingForFinalAck(resize_data, event.serial);
|
||||
} else {
|
||||
panic!("invalid resize state: {:?}", data.resize_state);
|
||||
}
|
||||
});
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(x11) => {
|
||||
let mut location = data.space.element_location(&self.window).unwrap();
|
||||
if self.edges.intersects(ResizeEdge::TOP_LEFT) {
|
||||
let geometry = self.window.geometry();
|
||||
|
||||
if self.edges.intersects(ResizeEdge::LEFT) {
|
||||
location.x =
|
||||
self.initial_window_location.x + (self.initial_window_size.w - geometry.size.w);
|
||||
}
|
||||
if self.edges.intersects(ResizeEdge::TOP) {
|
||||
location.y =
|
||||
self.initial_window_location.y + (self.initial_window_size.h - geometry.size.h);
|
||||
}
|
||||
|
||||
data.space.map_element(self.window.clone(), location, true);
|
||||
}
|
||||
x11.configure_with_sync(Rectangle::new(location, self.last_window_size), None)
|
||||
.unwrap();
|
||||
|
||||
let Some(surface) = self.window.wl_surface() else {
|
||||
// X11 Window got unmapped, abort
|
||||
return;
|
||||
};
|
||||
with_states(&surface, |states| {
|
||||
let mut data = states
|
||||
.data_map
|
||||
.get::<RefCell<SurfaceData>>()
|
||||
.unwrap()
|
||||
.borrow_mut();
|
||||
if let ResizeState::Resizing(resize_data) = data.resize_state {
|
||||
data.resize_state = ResizeState::WaitingForCommit(resize_data);
|
||||
} else {
|
||||
panic!("invalid resize state: {:?}", data.resize_state);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn motion(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
_focus: Option<(
|
||||
<AnvilState<BackendData> as smithay::input::SeatHandler>::TouchFocus,
|
||||
Point<f64, Logical>,
|
||||
)>,
|
||||
event: &smithay::input::touch::MotionEvent,
|
||||
) {
|
||||
if event.slot != self.start_data.slot {
|
||||
return;
|
||||
}
|
||||
|
||||
// It is impossible to get `min_size` and `max_size` of dead toplevel, so we return early.
|
||||
if !self.window.alive() {
|
||||
handle.unset_grab(self, data);
|
||||
return;
|
||||
}
|
||||
|
||||
let (mut dx, mut dy) = (event.location - self.start_data.location).into();
|
||||
|
||||
let mut new_window_width = self.initial_window_size.w;
|
||||
let mut new_window_height = self.initial_window_size.h;
|
||||
|
||||
let left_right = ResizeEdge::LEFT | ResizeEdge::RIGHT;
|
||||
let top_bottom = ResizeEdge::TOP | ResizeEdge::BOTTOM;
|
||||
|
||||
if self.edges.intersects(left_right) {
|
||||
if self.edges.intersects(ResizeEdge::LEFT) {
|
||||
dx = -dx;
|
||||
}
|
||||
|
||||
new_window_width = (self.initial_window_size.w as f64 + dx) as i32;
|
||||
}
|
||||
|
||||
if self.edges.intersects(top_bottom) {
|
||||
if self.edges.intersects(ResizeEdge::TOP) {
|
||||
dy = -dy;
|
||||
}
|
||||
|
||||
new_window_height = (self.initial_window_size.h as f64 + dy) as i32;
|
||||
}
|
||||
|
||||
let (min_size, max_size) = if let Some(surface) = self.window.wl_surface() {
|
||||
with_states(&surface, |states| {
|
||||
let mut guard = states.cached_state.get::<SurfaceCachedState>();
|
||||
let data = guard.current();
|
||||
(data.min_size, data.max_size)
|
||||
})
|
||||
} else {
|
||||
((0, 0).into(), (0, 0).into())
|
||||
};
|
||||
|
||||
let min_width = min_size.w.max(1);
|
||||
let min_height = min_size.h.max(1);
|
||||
let max_width = if max_size.w == 0 { i32::MAX } else { max_size.w };
|
||||
let max_height = if max_size.h == 0 { i32::MAX } else { max_size.h };
|
||||
|
||||
new_window_width = new_window_width.max(min_width).min(max_width);
|
||||
new_window_height = new_window_height.max(min_height).min(max_height);
|
||||
|
||||
self.last_window_size = (new_window_width, new_window_height).into();
|
||||
|
||||
match self.window.0.underlying_surface() {
|
||||
WindowSurface::Wayland(xdg) => {
|
||||
xdg.with_pending_state(|state| {
|
||||
state.states.set(xdg_toplevel::State::Resizing);
|
||||
state.size = Some(self.last_window_size);
|
||||
});
|
||||
xdg.send_pending_configure();
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(x11) => {
|
||||
let location = data.space.element_location(&self.window).unwrap();
|
||||
x11.configure_with_sync(Rectangle::new(location, self.last_window_size), None)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn frame(
|
||||
&mut self,
|
||||
_data: &mut AnvilState<BackendData>,
|
||||
_handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn cancel(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
) {
|
||||
handle.cancel(data);
|
||||
handle.unset_grab(self, data);
|
||||
}
|
||||
|
||||
fn shape(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &smithay::input::touch::ShapeEvent,
|
||||
) {
|
||||
handle.shape(data, event);
|
||||
}
|
||||
|
||||
fn orientation(
|
||||
&mut self,
|
||||
data: &mut AnvilState<BackendData>,
|
||||
handle: &mut smithay::input::touch::TouchInnerHandle<'_, AnvilState<BackendData>>,
|
||||
event: &smithay::input::touch::OrientationEvent,
|
||||
) {
|
||||
handle.orientation(data, event);
|
||||
}
|
||||
|
||||
fn start_data(&self) -> &smithay::input::touch::GrabStartData<AnvilState<BackendData>> {
|
||||
&self.start_data
|
||||
}
|
||||
|
||||
fn unset(&mut self, _data: &mut AnvilState<BackendData>) {}
|
||||
}
|
||||
+475
@@ -0,0 +1,475 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
#[cfg(feature = "xwayland")]
|
||||
use smithay::xwayland::XWaylandClientData;
|
||||
|
||||
#[cfg(feature = "udev")]
|
||||
use smithay::wayland::drm_syncobj::DrmSyncobjCachedState;
|
||||
|
||||
use smithay::{
|
||||
backend::renderer::utils::on_commit_buffer_handler,
|
||||
desktop::{
|
||||
LayerSurface, PopupKind, PopupManager, Space, WindowSurfaceType, layer_map_for_output,
|
||||
space::SpaceElement,
|
||||
},
|
||||
input::pointer::{CursorImageStatus, CursorImageSurfaceData},
|
||||
output::Output,
|
||||
reexports::{
|
||||
calloop::Interest,
|
||||
wayland_server::{
|
||||
Client, Resource,
|
||||
protocol::{wl_buffer::WlBuffer, wl_output, wl_surface::WlSurface},
|
||||
},
|
||||
},
|
||||
utils::{IsAlive, Logical, Point, Rectangle, Size},
|
||||
wayland::{
|
||||
buffer::BufferHandler,
|
||||
compositor::{
|
||||
BufferAssignment, CompositorClientState, CompositorHandler, CompositorState, SurfaceAttributes,
|
||||
TraversalAction, add_blocker, add_pre_commit_hook, get_parent, is_sync_subsurface, with_states,
|
||||
with_surface_tree_upward,
|
||||
},
|
||||
dmabuf::get_dmabuf,
|
||||
shell::{
|
||||
wlr_layer::{
|
||||
Layer, LayerSurface as WlrLayerSurface, LayerSurfaceData, WlrLayerShellHandler,
|
||||
WlrLayerShellState,
|
||||
},
|
||||
xdg::XdgToplevelSurfaceData,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ClientState,
|
||||
state::{AnvilState, Backend},
|
||||
};
|
||||
|
||||
mod element;
|
||||
mod grabs;
|
||||
pub(crate) mod ssd;
|
||||
#[cfg(feature = "xwayland")]
|
||||
mod x11;
|
||||
mod xdg;
|
||||
|
||||
pub use self::element::*;
|
||||
pub use self::grabs::*;
|
||||
|
||||
fn fullscreen_output_geometry(
|
||||
wl_surface: &WlSurface,
|
||||
wl_output: Option<&wl_output::WlOutput>,
|
||||
space: &mut Space<WindowElement>,
|
||||
) -> Option<Rectangle<i32, Logical>> {
|
||||
// First test if a specific output has been requested
|
||||
// if the requested output is not found ignore the request
|
||||
wl_output
|
||||
.and_then(Output::from_resource)
|
||||
.or_else(|| {
|
||||
let w = space
|
||||
.elements()
|
||||
.find(|window| window.wl_surface().map(|s| &*s == wl_surface).unwrap_or(false));
|
||||
w.and_then(|w| space.outputs_for_element(w).first().cloned())
|
||||
})
|
||||
.as_ref()
|
||||
.and_then(|o| space.output_geometry(o))
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct FullscreenSurface(RefCell<Option<WindowElement>>);
|
||||
|
||||
impl FullscreenSurface {
|
||||
pub fn set(&self, window: WindowElement) {
|
||||
*self.0.borrow_mut() = Some(window);
|
||||
}
|
||||
|
||||
pub fn get(&self) -> Option<WindowElement> {
|
||||
let mut window = self.0.borrow_mut();
|
||||
if window.as_ref().map(|w| !w.alive()).unwrap_or(false) {
|
||||
*window = None;
|
||||
}
|
||||
window.clone()
|
||||
}
|
||||
|
||||
pub fn clear(&self) -> Option<WindowElement> {
|
||||
self.0.borrow_mut().take()
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> BufferHandler for AnvilState<BackendData> {
|
||||
fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> CompositorHandler for AnvilState<BackendData> {
|
||||
fn compositor_state(&mut self) -> &mut CompositorState {
|
||||
&mut self.compositor_state
|
||||
}
|
||||
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
|
||||
#[cfg(feature = "xwayland")]
|
||||
if let Some(state) = client.get_data::<XWaylandClientData>() {
|
||||
return &state.compositor_state;
|
||||
}
|
||||
if let Some(state) = client.get_data::<ClientState>() {
|
||||
return &state.compositor_state;
|
||||
}
|
||||
panic!("Unknown client data type")
|
||||
}
|
||||
|
||||
fn new_surface(&mut self, surface: &WlSurface) {
|
||||
add_pre_commit_hook::<Self, _>(surface, move |state, _dh, surface| {
|
||||
#[cfg(feature = "udev")]
|
||||
let mut acquire_point = None;
|
||||
let maybe_dmabuf = with_states(surface, |surface_data| {
|
||||
#[cfg(feature = "udev")]
|
||||
acquire_point.clone_from(
|
||||
&surface_data
|
||||
.cached_state
|
||||
.get::<DrmSyncobjCachedState>()
|
||||
.pending()
|
||||
.acquire_point,
|
||||
);
|
||||
surface_data
|
||||
.cached_state
|
||||
.get::<SurfaceAttributes>()
|
||||
.pending()
|
||||
.buffer
|
||||
.as_ref()
|
||||
.and_then(|assignment| match assignment {
|
||||
BufferAssignment::NewBuffer(buffer) => get_dmabuf(buffer).cloned().ok(),
|
||||
_ => None,
|
||||
})
|
||||
});
|
||||
if let Some(dmabuf) = maybe_dmabuf {
|
||||
#[cfg(feature = "udev")]
|
||||
if let Some(acquire_point) = acquire_point {
|
||||
if let Ok((blocker, source)) = acquire_point.generate_blocker() {
|
||||
let client = surface.client().unwrap();
|
||||
let res = state.handle.insert_source(source, move |_, _, data| {
|
||||
let dh = data.display_handle.clone();
|
||||
data.client_compositor_state(&client).blocker_cleared(data, &dh);
|
||||
Ok(())
|
||||
});
|
||||
if res.is_ok() {
|
||||
add_blocker(surface, blocker);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Ok((blocker, source)) = dmabuf.generate_blocker(Interest::READ) {
|
||||
if let Some(client) = surface.client() {
|
||||
let res = state.handle.insert_source(source, move |_, _, data| {
|
||||
let dh = data.display_handle.clone();
|
||||
data.client_compositor_state(&client).blocker_cleared(data, &dh);
|
||||
Ok(())
|
||||
});
|
||||
if res.is_ok() {
|
||||
add_blocker(surface, blocker);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn commit(&mut self, surface: &WlSurface) {
|
||||
on_commit_buffer_handler::<Self>(surface);
|
||||
self.backend_data.early_import(surface);
|
||||
|
||||
if !is_sync_subsurface(surface) {
|
||||
let mut root = surface.clone();
|
||||
while let Some(parent) = get_parent(&root) {
|
||||
root = parent;
|
||||
}
|
||||
if let Some(window) = self.window_for_surface(&root) {
|
||||
window.0.on_commit();
|
||||
|
||||
if &root == surface {
|
||||
let buffer_offset = with_states(surface, |states| {
|
||||
states
|
||||
.cached_state
|
||||
.get::<SurfaceAttributes>()
|
||||
.current()
|
||||
.buffer_delta
|
||||
.take()
|
||||
});
|
||||
|
||||
if let Some(buffer_offset) = buffer_offset {
|
||||
let current_loc = self.space.element_location(&window).unwrap();
|
||||
self.space.map_element(window, current_loc + buffer_offset, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.popups.commit(surface);
|
||||
|
||||
if matches!(&self.cursor_status, CursorImageStatus::Surface(cursor_surface) if cursor_surface == surface)
|
||||
{
|
||||
with_states(surface, |states| {
|
||||
let cursor_image_attributes = states.data_map.get::<CursorImageSurfaceData>();
|
||||
|
||||
if let Some(mut cursor_image_attributes) =
|
||||
cursor_image_attributes.map(|attrs| attrs.lock().unwrap())
|
||||
{
|
||||
let buffer_delta = states
|
||||
.cached_state
|
||||
.get::<SurfaceAttributes>()
|
||||
.current()
|
||||
.buffer_delta
|
||||
.take();
|
||||
if let Some(buffer_delta) = buffer_delta {
|
||||
tracing::trace!(hotspot = ?cursor_image_attributes.hotspot, ?buffer_delta, "decrementing cursor hotspot");
|
||||
cursor_image_attributes.hotspot -= buffer_delta;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if matches!(&self.dnd_icon, Some(icon) if &icon.surface == surface) {
|
||||
let dnd_icon = self.dnd_icon.as_mut().unwrap();
|
||||
with_states(&dnd_icon.surface, |states| {
|
||||
let buffer_delta = states
|
||||
.cached_state
|
||||
.get::<SurfaceAttributes>()
|
||||
.current()
|
||||
.buffer_delta
|
||||
.take()
|
||||
.unwrap_or_default();
|
||||
tracing::trace!(offset = ?dnd_icon.offset, ?buffer_delta, "moving dnd offset");
|
||||
dnd_icon.offset += buffer_delta;
|
||||
});
|
||||
}
|
||||
|
||||
ensure_initial_configure(surface, &self.space, &mut self.popups)
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> WlrLayerShellHandler for AnvilState<BackendData> {
|
||||
fn shell_state(&mut self) -> &mut WlrLayerShellState {
|
||||
&mut self.layer_shell_state
|
||||
}
|
||||
|
||||
fn new_layer_surface(
|
||||
&mut self,
|
||||
surface: WlrLayerSurface,
|
||||
wl_output: Option<wl_output::WlOutput>,
|
||||
_layer: Layer,
|
||||
namespace: String,
|
||||
) {
|
||||
let output = wl_output
|
||||
.as_ref()
|
||||
.and_then(Output::from_resource)
|
||||
.unwrap_or_else(|| self.space.outputs().next().unwrap().clone());
|
||||
let mut map = layer_map_for_output(&output);
|
||||
map.map_layer(&LayerSurface::new(surface, namespace)).unwrap();
|
||||
}
|
||||
|
||||
fn layer_destroyed(&mut self, surface: WlrLayerSurface) {
|
||||
if let Some((mut map, layer)) = self.space.outputs().find_map(|o| {
|
||||
let map = layer_map_for_output(o);
|
||||
let layer = map
|
||||
.layers()
|
||||
.find(|&layer| layer.layer_surface() == &surface)
|
||||
.cloned();
|
||||
layer.map(|layer| (map, layer))
|
||||
}) {
|
||||
map.unmap_layer(&layer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> AnvilState<BackendData> {
|
||||
pub fn window_for_surface(&self, surface: &WlSurface) -> Option<WindowElement> {
|
||||
self.space
|
||||
.elements()
|
||||
.find(|window| window.wl_surface().map(|s| &*s == surface).unwrap_or(false))
|
||||
.cloned()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct SurfaceData {
|
||||
pub geometry: Option<Rectangle<i32, Logical>>,
|
||||
pub resize_state: ResizeState,
|
||||
}
|
||||
|
||||
fn ensure_initial_configure(surface: &WlSurface, space: &Space<WindowElement>, popups: &mut PopupManager) {
|
||||
with_surface_tree_upward(
|
||||
surface,
|
||||
(),
|
||||
|_, _, _| TraversalAction::DoChildren(()),
|
||||
|_, states, _| {
|
||||
states
|
||||
.data_map
|
||||
.insert_if_missing(|| RefCell::new(SurfaceData::default()));
|
||||
},
|
||||
|_, _, _| true,
|
||||
);
|
||||
|
||||
if let Some(window) = space
|
||||
.elements()
|
||||
.find(|window| window.wl_surface().map(|s| &*s == surface).unwrap_or(false))
|
||||
.cloned()
|
||||
{
|
||||
// send the initial configure if relevant
|
||||
#[cfg_attr(not(feature = "xwayland"), allow(irrefutable_let_patterns))]
|
||||
if let Some(toplevel) = window.0.toplevel() {
|
||||
let initial_configure_sent = with_states(surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.initial_configure_sent
|
||||
});
|
||||
if !initial_configure_sent {
|
||||
toplevel.send_configure();
|
||||
}
|
||||
}
|
||||
|
||||
with_states(surface, |states| {
|
||||
let mut data = states
|
||||
.data_map
|
||||
.get::<RefCell<SurfaceData>>()
|
||||
.unwrap()
|
||||
.borrow_mut();
|
||||
|
||||
// Finish resizing.
|
||||
if let ResizeState::WaitingForCommit(_) = data.resize_state {
|
||||
data.resize_state = ResizeState::NotResizing;
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(popup) = popups.find_popup(surface) {
|
||||
let popup = match popup {
|
||||
PopupKind::Xdg(ref popup) => popup,
|
||||
// Doesn't require configure
|
||||
PopupKind::InputMethod(ref _input_popup) => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
if !popup.is_initial_configure_sent() {
|
||||
// NOTE: This should never fail as the initial configure is always
|
||||
// allowed.
|
||||
popup.send_configure().expect("initial configure failed");
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(output) = space.outputs().find(|o| {
|
||||
let map = layer_map_for_output(o);
|
||||
map.layer_for_surface(surface, WindowSurfaceType::TOPLEVEL)
|
||||
.is_some()
|
||||
}) {
|
||||
let initial_configure_sent = with_states(surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<LayerSurfaceData>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.initial_configure_sent
|
||||
});
|
||||
|
||||
let mut map = layer_map_for_output(output);
|
||||
|
||||
// arrange the layers before sending the initial configure
|
||||
// to respect any size the client may have sent
|
||||
map.arrange();
|
||||
// send the initial configure if relevant
|
||||
if !initial_configure_sent {
|
||||
let layer = map
|
||||
.layer_for_surface(surface, WindowSurfaceType::TOPLEVEL)
|
||||
.unwrap();
|
||||
|
||||
layer.layer_surface().send_configure();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn place_new_window(
|
||||
space: &mut Space<WindowElement>,
|
||||
pointer_location: Point<f64, Logical>,
|
||||
window: &WindowElement,
|
||||
activate: bool,
|
||||
) {
|
||||
// place the window at a random location on same output as pointer
|
||||
// or if there is not output in a [0;800]x[0;800] square
|
||||
use rand::distributions::{Distribution, Uniform};
|
||||
|
||||
let output = space
|
||||
.output_under(pointer_location)
|
||||
.next()
|
||||
.or_else(|| space.outputs().next())
|
||||
.cloned();
|
||||
let output_geometry = output
|
||||
.and_then(|o| {
|
||||
let geo = space.output_geometry(&o)?;
|
||||
let map = layer_map_for_output(&o);
|
||||
let zone = map.non_exclusive_zone();
|
||||
Some(Rectangle::new(geo.loc + zone.loc, zone.size))
|
||||
})
|
||||
.unwrap_or_else(|| Rectangle::from_size((800, 800).into()));
|
||||
|
||||
// set the initial toplevel bounds
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
if let Some(toplevel) = window.0.toplevel() {
|
||||
toplevel.with_pending_state(|state| {
|
||||
state.bounds = Some(output_geometry.size);
|
||||
});
|
||||
}
|
||||
|
||||
let max_x = output_geometry.loc.x + (((output_geometry.size.w as f32) / 3.0) * 2.0) as i32;
|
||||
let max_y = output_geometry.loc.y + (((output_geometry.size.h as f32) / 3.0) * 2.0) as i32;
|
||||
let x_range = Uniform::new(output_geometry.loc.x, max_x);
|
||||
let y_range = Uniform::new(output_geometry.loc.y, max_y);
|
||||
let mut rng = rand::thread_rng();
|
||||
let x = x_range.sample(&mut rng);
|
||||
let y = y_range.sample(&mut rng);
|
||||
|
||||
space.map_element(window.clone(), (x, y), activate);
|
||||
}
|
||||
|
||||
pub fn fixup_positions(space: &mut Space<WindowElement>, pointer_location: Point<f64, Logical>) {
|
||||
// fixup outputs
|
||||
let mut offset = Point::<i32, Logical>::from((0, 0));
|
||||
for output in space.outputs().cloned().collect::<Vec<_>>().into_iter() {
|
||||
let size = space
|
||||
.output_geometry(&output)
|
||||
.map(|geo| geo.size)
|
||||
.unwrap_or_else(|| Size::from((0, 0)));
|
||||
space.map_output(&output, offset);
|
||||
layer_map_for_output(&output).arrange();
|
||||
offset.x += size.w;
|
||||
}
|
||||
|
||||
// fixup windows
|
||||
let mut orphaned_windows = Vec::new();
|
||||
let outputs = space
|
||||
.outputs()
|
||||
.flat_map(|o| {
|
||||
let geo = space.output_geometry(o)?;
|
||||
let map = layer_map_for_output(o);
|
||||
let zone = map.non_exclusive_zone();
|
||||
Some(Rectangle::new(geo.loc + zone.loc, zone.size))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
for window in space.elements() {
|
||||
let window_location = match space.element_location(window) {
|
||||
Some(loc) => loc,
|
||||
None => continue,
|
||||
};
|
||||
let geo_loc = window.bbox().loc + window_location;
|
||||
|
||||
if !outputs.iter().any(|o_geo| o_geo.contains(geo_loc)) {
|
||||
orphaned_windows.push(window.clone());
|
||||
}
|
||||
}
|
||||
for window in orphaned_windows.into_iter() {
|
||||
place_new_window(space, pointer_location, &window, false);
|
||||
}
|
||||
}
|
||||
+294
@@ -0,0 +1,294 @@
|
||||
use smithay::{
|
||||
backend::renderer::{
|
||||
Renderer,
|
||||
element::{
|
||||
AsRenderElements, Kind,
|
||||
solid::{SolidColorBuffer, SolidColorRenderElement},
|
||||
},
|
||||
},
|
||||
desktop::WindowSurface,
|
||||
input::Seat,
|
||||
utils::{Logical, Point, Serial},
|
||||
wayland::shell::xdg::XdgShellHandler,
|
||||
};
|
||||
|
||||
use std::cell::{RefCell, RefMut};
|
||||
|
||||
use crate::{AnvilState, state::Backend};
|
||||
|
||||
use super::WindowElement;
|
||||
|
||||
pub struct WindowState {
|
||||
pub is_ssd: bool,
|
||||
pub header_bar: HeaderBar,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HeaderBar {
|
||||
pub pointer_loc: Option<Point<f64, Logical>>,
|
||||
pub width: u32,
|
||||
pub close_button_hover: bool,
|
||||
pub maximize_button_hover: bool,
|
||||
pub background: SolidColorBuffer,
|
||||
pub close_button: SolidColorBuffer,
|
||||
pub maximize_button: SolidColorBuffer,
|
||||
}
|
||||
|
||||
const BG_COLOR: [f32; 4] = [0.75f32, 0.9f32, 0.78f32, 1f32];
|
||||
const MAX_COLOR: [f32; 4] = [1f32, 0.965f32, 0.71f32, 1f32];
|
||||
const CLOSE_COLOR: [f32; 4] = [1f32, 0.66f32, 0.612f32, 1f32];
|
||||
const MAX_COLOR_HOVER: [f32; 4] = [0.71f32, 0.624f32, 0f32, 1f32];
|
||||
const CLOSE_COLOR_HOVER: [f32; 4] = [0.75f32, 0.11f32, 0.016f32, 1f32];
|
||||
|
||||
pub const HEADER_BAR_HEIGHT: i32 = 32;
|
||||
const BUTTON_HEIGHT: u32 = HEADER_BAR_HEIGHT as u32;
|
||||
const BUTTON_WIDTH: u32 = 32;
|
||||
|
||||
impl HeaderBar {
|
||||
pub fn pointer_enter(&mut self, loc: Point<f64, Logical>) {
|
||||
self.pointer_loc = Some(loc);
|
||||
}
|
||||
|
||||
pub fn pointer_leave(&mut self) {
|
||||
self.pointer_loc = None;
|
||||
}
|
||||
|
||||
pub fn clicked<BackendData: Backend>(
|
||||
&mut self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
state: &mut AnvilState<BackendData>,
|
||||
window: &WindowElement,
|
||||
serial: Serial,
|
||||
) {
|
||||
match self.pointer_loc.as_ref() {
|
||||
Some(loc) if loc.x >= (self.width - BUTTON_WIDTH) as f64 => {
|
||||
match window.0.underlying_surface() {
|
||||
WindowSurface::Wayland(w) => w.send_close(),
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(w) => {
|
||||
let _ = w.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
Some(loc) if loc.x >= (self.width - (BUTTON_WIDTH * 2)) as f64 => {
|
||||
match window.0.underlying_surface() {
|
||||
WindowSurface::Wayland(w) => state.maximize_request(w.clone()),
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(w) => {
|
||||
let surface = w.clone();
|
||||
state
|
||||
.handle
|
||||
.insert_idle(move |data| data.maximize_request_x11(&surface));
|
||||
}
|
||||
};
|
||||
}
|
||||
Some(_) => {
|
||||
match window.0.underlying_surface() {
|
||||
WindowSurface::Wayland(w) => {
|
||||
let seat = seat.clone();
|
||||
let toplevel = w.clone();
|
||||
state
|
||||
.handle
|
||||
.insert_idle(move |data| data.move_request_xdg(&toplevel, &seat, serial));
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(w) => {
|
||||
let window = w.clone();
|
||||
state
|
||||
.handle
|
||||
.insert_idle(move |data| data.move_request_x11(&window));
|
||||
}
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn touch_down<BackendData: Backend>(
|
||||
&mut self,
|
||||
seat: &Seat<AnvilState<BackendData>>,
|
||||
state: &mut AnvilState<BackendData>,
|
||||
window: &WindowElement,
|
||||
serial: Serial,
|
||||
) {
|
||||
match self.pointer_loc.as_ref() {
|
||||
Some(loc) if loc.x >= (self.width - BUTTON_WIDTH) as f64 => {}
|
||||
Some(loc) if loc.x >= (self.width - (BUTTON_WIDTH * 2)) as f64 => {}
|
||||
Some(_) => {
|
||||
match window.0.underlying_surface() {
|
||||
WindowSurface::Wayland(w) => {
|
||||
let seat = seat.clone();
|
||||
let toplevel = w.clone();
|
||||
state
|
||||
.handle
|
||||
.insert_idle(move |data| data.move_request_xdg(&toplevel, &seat, serial));
|
||||
}
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(w) => {
|
||||
let window = w.clone();
|
||||
state
|
||||
.handle
|
||||
.insert_idle(move |data| data.move_request_x11(&window));
|
||||
}
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn touch_up<BackendData: Backend>(
|
||||
&mut self,
|
||||
_seat: &Seat<AnvilState<BackendData>>,
|
||||
state: &mut AnvilState<BackendData>,
|
||||
window: &WindowElement,
|
||||
) {
|
||||
match self.pointer_loc.as_ref() {
|
||||
Some(loc) if loc.x >= (self.width - BUTTON_WIDTH) as f64 => {
|
||||
match window.0.underlying_surface() {
|
||||
WindowSurface::Wayland(w) => w.send_close(),
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(w) => {
|
||||
let _ = w.close();
|
||||
}
|
||||
};
|
||||
}
|
||||
Some(loc) if loc.x >= (self.width - (BUTTON_WIDTH * 2)) as f64 => {
|
||||
match window.0.underlying_surface() {
|
||||
WindowSurface::Wayland(w) => state.maximize_request(w.clone()),
|
||||
#[cfg(feature = "xwayland")]
|
||||
WindowSurface::X11(w) => {
|
||||
let surface = w.clone();
|
||||
state
|
||||
.handle
|
||||
.insert_idle(move |data| data.maximize_request_x11(&surface));
|
||||
}
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
pub fn redraw(&mut self, width: u32) {
|
||||
if width == 0 {
|
||||
self.width = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
self.background
|
||||
.update((width as i32, HEADER_BAR_HEIGHT), BG_COLOR);
|
||||
|
||||
let mut needs_redraw_buttons = false;
|
||||
if width != self.width {
|
||||
needs_redraw_buttons = true;
|
||||
self.width = width;
|
||||
}
|
||||
|
||||
if self
|
||||
.pointer_loc
|
||||
.as_ref()
|
||||
.map(|l| l.x >= (width - BUTTON_WIDTH) as f64)
|
||||
.unwrap_or(false)
|
||||
&& (needs_redraw_buttons || !self.close_button_hover)
|
||||
{
|
||||
self.close_button
|
||||
.update((BUTTON_WIDTH as i32, BUTTON_HEIGHT as i32), CLOSE_COLOR_HOVER);
|
||||
self.close_button_hover = true;
|
||||
} else if !self
|
||||
.pointer_loc
|
||||
.as_ref()
|
||||
.map(|l| l.x >= (width - BUTTON_WIDTH) as f64)
|
||||
.unwrap_or(false)
|
||||
&& (needs_redraw_buttons || self.close_button_hover)
|
||||
{
|
||||
self.close_button
|
||||
.update((BUTTON_WIDTH as i32, BUTTON_HEIGHT as i32), CLOSE_COLOR);
|
||||
self.close_button_hover = false;
|
||||
}
|
||||
|
||||
if self
|
||||
.pointer_loc
|
||||
.as_ref()
|
||||
.map(|l| l.x >= (width - BUTTON_WIDTH * 2) as f64 && l.x <= (width - BUTTON_WIDTH) as f64)
|
||||
.unwrap_or(false)
|
||||
&& (needs_redraw_buttons || !self.maximize_button_hover)
|
||||
{
|
||||
self.maximize_button
|
||||
.update((BUTTON_WIDTH as i32, BUTTON_HEIGHT as i32), MAX_COLOR_HOVER);
|
||||
self.maximize_button_hover = true;
|
||||
} else if !self
|
||||
.pointer_loc
|
||||
.as_ref()
|
||||
.map(|l| l.x >= (width - BUTTON_WIDTH * 2) as f64 && l.x <= (width - BUTTON_WIDTH) as f64)
|
||||
.unwrap_or(false)
|
||||
&& (needs_redraw_buttons || self.maximize_button_hover)
|
||||
{
|
||||
self.maximize_button
|
||||
.update((BUTTON_WIDTH as i32, BUTTON_HEIGHT as i32), MAX_COLOR);
|
||||
self.maximize_button_hover = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Renderer> AsRenderElements<R> for HeaderBar {
|
||||
type RenderElement = SolidColorRenderElement;
|
||||
|
||||
fn render_elements<C: From<Self::RenderElement>>(
|
||||
&self,
|
||||
_renderer: &mut R,
|
||||
location: Point<i32, smithay::utils::Physical>,
|
||||
scale: smithay::utils::Scale<f64>,
|
||||
alpha: f32,
|
||||
) -> Vec<C> {
|
||||
let header_end_offset: Point<i32, Logical> = Point::from((self.width as i32, 0));
|
||||
let button_offset: Point<i32, Logical> = Point::from((BUTTON_WIDTH as i32, 0));
|
||||
|
||||
vec![
|
||||
SolidColorRenderElement::from_buffer(
|
||||
&self.close_button,
|
||||
location + (header_end_offset - button_offset).to_physical_precise_round(scale),
|
||||
scale,
|
||||
alpha,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
.into(),
|
||||
SolidColorRenderElement::from_buffer(
|
||||
&self.maximize_button,
|
||||
location + (header_end_offset - button_offset.upscale(2)).to_physical_precise_round(scale),
|
||||
scale,
|
||||
alpha,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
.into(),
|
||||
SolidColorRenderElement::from_buffer(&self.background, location, scale, alpha, Kind::Unspecified)
|
||||
.into(),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowElement {
|
||||
pub fn decoration_state(&self) -> RefMut<'_, WindowState> {
|
||||
self.user_data().insert_if_missing(|| {
|
||||
RefCell::new(WindowState {
|
||||
is_ssd: false,
|
||||
header_bar: HeaderBar {
|
||||
pointer_loc: None,
|
||||
width: 0,
|
||||
close_button_hover: false,
|
||||
maximize_button_hover: false,
|
||||
background: SolidColorBuffer::default(),
|
||||
close_button: SolidColorBuffer::default(),
|
||||
maximize_button: SolidColorBuffer::default(),
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
self.user_data()
|
||||
.get::<RefCell<WindowState>>()
|
||||
.unwrap()
|
||||
.borrow_mut()
|
||||
}
|
||||
|
||||
pub fn set_ssd(&self, ssd: bool) {
|
||||
self.decoration_state().is_ssd = ssd;
|
||||
}
|
||||
}
|
||||
+431
@@ -0,0 +1,431 @@
|
||||
use std::{cell::RefCell, os::unix::io::OwnedFd};
|
||||
|
||||
use smithay::{
|
||||
desktop::{Window, space::SpaceElement},
|
||||
input::pointer::Focus,
|
||||
utils::{Logical, Rectangle, SERIAL_COUNTER},
|
||||
wayland::{
|
||||
compositor::with_states,
|
||||
selection::{
|
||||
SelectionTarget,
|
||||
data_device::{
|
||||
clear_data_device_selection, current_data_device_selection_userdata,
|
||||
request_data_device_client_selection, set_data_device_selection,
|
||||
},
|
||||
primary_selection::{
|
||||
clear_primary_selection, current_primary_selection_userdata,
|
||||
request_primary_client_selection, set_primary_selection,
|
||||
},
|
||||
},
|
||||
xwayland_shell::{XWaylandShellHandler, XWaylandShellState},
|
||||
},
|
||||
xwayland::{
|
||||
X11Surface, X11Wm, XwmHandler,
|
||||
xwm::{Reorder, ResizeEdge as X11ResizeEdge, XwmId},
|
||||
},
|
||||
};
|
||||
use tracing::{error, trace};
|
||||
|
||||
use crate::{AnvilState, focus::KeyboardFocusTarget, state::Backend};
|
||||
|
||||
use super::{
|
||||
FullscreenSurface, PointerMoveSurfaceGrab, PointerResizeSurfaceGrab, ResizeData, ResizeState,
|
||||
SurfaceData, TouchMoveSurfaceGrab, WindowElement, place_new_window,
|
||||
};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct OldGeometry(RefCell<Option<Rectangle<i32, Logical>>>);
|
||||
impl OldGeometry {
|
||||
pub fn save(&self, geo: Rectangle<i32, Logical>) {
|
||||
*self.0.borrow_mut() = Some(geo);
|
||||
}
|
||||
|
||||
pub fn restore(&self) -> Option<Rectangle<i32, Logical>> {
|
||||
self.0.borrow_mut().take()
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> XWaylandShellHandler for AnvilState<BackendData> {
|
||||
fn xwayland_shell_state(&mut self) -> &mut XWaylandShellState {
|
||||
&mut self.xwayland_shell_state
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> XwmHandler for AnvilState<BackendData> {
|
||||
fn xwm_state(&mut self, _xwm: XwmId) -> &mut X11Wm {
|
||||
self.xwm.as_mut().unwrap()
|
||||
}
|
||||
|
||||
fn new_window(&mut self, _xwm: XwmId, _window: X11Surface) {}
|
||||
fn new_override_redirect_window(&mut self, _xwm: XwmId, _window: X11Surface) {}
|
||||
|
||||
fn map_window_request(&mut self, _xwm: XwmId, window: X11Surface) {
|
||||
window.set_mapped(true).unwrap();
|
||||
let window = WindowElement(Window::new_x11_window(window));
|
||||
place_new_window(&mut self.space, self.pointer.current_location(), &window, true);
|
||||
let bbox = self.space.element_bbox(&window).unwrap();
|
||||
let Some(xsurface) = window.0.x11_surface() else {
|
||||
unreachable!()
|
||||
};
|
||||
xsurface.configure(Some(bbox)).unwrap();
|
||||
window.set_ssd(!xsurface.is_decorated());
|
||||
}
|
||||
|
||||
fn mapped_override_redirect_window(&mut self, _xwm: XwmId, window: X11Surface) {
|
||||
let location = window.last_configure().loc;
|
||||
let window = WindowElement(Window::new_x11_window(window));
|
||||
self.space.map_element(window, location, true);
|
||||
}
|
||||
|
||||
fn unmapped_window(&mut self, _xwm: XwmId, window: X11Surface) {
|
||||
let maybe = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == &window))
|
||||
.cloned();
|
||||
if let Some(elem) = maybe {
|
||||
self.space.unmap_elem(&elem)
|
||||
}
|
||||
if !window.is_override_redirect() {
|
||||
window.set_mapped(false).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn destroyed_window(&mut self, _xwm: XwmId, _window: X11Surface) {}
|
||||
|
||||
fn configure_request(
|
||||
&mut self,
|
||||
_xwm: XwmId,
|
||||
window: X11Surface,
|
||||
_x: Option<i32>,
|
||||
_y: Option<i32>,
|
||||
w: Option<u32>,
|
||||
h: Option<u32>,
|
||||
_reorder: Option<Reorder>,
|
||||
) {
|
||||
// we just set the new size, but don't let windows move themselves around freely
|
||||
let mut geo = window.last_configure();
|
||||
if let Some(w) = w {
|
||||
geo.size.w = w as i32;
|
||||
}
|
||||
if let Some(h) = h {
|
||||
geo.size.h = h as i32;
|
||||
}
|
||||
let _ = window.configure(geo);
|
||||
}
|
||||
|
||||
fn configure_notify(
|
||||
&mut self,
|
||||
_xwm: XwmId,
|
||||
window: X11Surface,
|
||||
geometry: Rectangle<i32, Logical>,
|
||||
_above: Option<u32>,
|
||||
) {
|
||||
let Some(elem) = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == &window))
|
||||
.cloned()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
self.space.map_element(elem, geometry.loc, false);
|
||||
// TODO: We don't properly handle the order of override-redirect windows here,
|
||||
// they are always mapped top and then never reordered.
|
||||
}
|
||||
|
||||
fn maximize_request(&mut self, _xwm: XwmId, window: X11Surface) {
|
||||
self.maximize_request_x11(&window);
|
||||
}
|
||||
|
||||
fn unmaximize_request(&mut self, _xwm: XwmId, window: X11Surface) {
|
||||
let Some(elem) = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == &window))
|
||||
.cloned()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
window.set_maximized(false).unwrap();
|
||||
if let Some(old_geo) = window
|
||||
.user_data()
|
||||
.get::<OldGeometry>()
|
||||
.and_then(|data| data.restore())
|
||||
{
|
||||
window.configure(old_geo).unwrap();
|
||||
self.space.map_element(elem, old_geo.loc, false);
|
||||
}
|
||||
}
|
||||
|
||||
fn fullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
|
||||
if let Some(elem) = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == &window))
|
||||
{
|
||||
let outputs_for_window = self.space.outputs_for_element(elem);
|
||||
let output = outputs_for_window
|
||||
.first()
|
||||
// The window hasn't been mapped yet, use the primary output instead
|
||||
.or_else(|| self.space.outputs().next())
|
||||
// Assumes that at least one output exists
|
||||
.expect("No outputs found");
|
||||
let geometry = self.space.output_geometry(output).unwrap();
|
||||
|
||||
window.set_fullscreen(true).unwrap();
|
||||
elem.set_ssd(false);
|
||||
window.configure(geometry).unwrap();
|
||||
output.user_data().insert_if_missing(FullscreenSurface::default);
|
||||
output
|
||||
.user_data()
|
||||
.get::<FullscreenSurface>()
|
||||
.unwrap()
|
||||
.set(elem.clone());
|
||||
trace!("Fullscreening: {:?}", elem);
|
||||
}
|
||||
}
|
||||
|
||||
fn unfullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) {
|
||||
if let Some(elem) = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == &window))
|
||||
{
|
||||
window.set_fullscreen(false).unwrap();
|
||||
elem.set_ssd(!window.is_decorated());
|
||||
if let Some(output) = self.space.outputs().find(|o| {
|
||||
o.user_data()
|
||||
.get::<FullscreenSurface>()
|
||||
.and_then(|f| f.get())
|
||||
.map(|w| &w == elem)
|
||||
.unwrap_or(false)
|
||||
}) {
|
||||
trace!("Unfullscreening: {:?}", elem);
|
||||
output.user_data().get::<FullscreenSurface>().unwrap().clear();
|
||||
window.configure(self.space.element_bbox(elem)).unwrap();
|
||||
self.backend_data.reset_buffers(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resize_request(&mut self, _xwm: XwmId, window: X11Surface, _button: u32, edges: X11ResizeEdge) {
|
||||
// luckily anvil only supports one seat anyway...
|
||||
let start_data = self.pointer.grab_start_data().unwrap();
|
||||
|
||||
let Some(element) = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == &window))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let geometry = element.geometry();
|
||||
let loc = self.space.element_location(element).unwrap();
|
||||
let (initial_window_location, initial_window_size) = (loc, geometry.size);
|
||||
|
||||
with_states(&element.wl_surface().unwrap(), move |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<RefCell<SurfaceData>>()
|
||||
.unwrap()
|
||||
.borrow_mut()
|
||||
.resize_state = ResizeState::Resizing(ResizeData {
|
||||
edges: edges.into(),
|
||||
initial_window_location,
|
||||
initial_window_size,
|
||||
});
|
||||
});
|
||||
|
||||
let grab = PointerResizeSurfaceGrab {
|
||||
start_data,
|
||||
window: element.clone(),
|
||||
edges: edges.into(),
|
||||
initial_window_location,
|
||||
initial_window_size,
|
||||
last_window_size: initial_window_size,
|
||||
};
|
||||
|
||||
let pointer = self.pointer.clone();
|
||||
pointer.set_grab(self, grab, SERIAL_COUNTER.next_serial(), Focus::Clear);
|
||||
}
|
||||
|
||||
fn move_request(&mut self, _xwm: XwmId, window: X11Surface, _button: u32) {
|
||||
self.move_request_x11(&window)
|
||||
}
|
||||
|
||||
fn allow_selection_access(&mut self, xwm: XwmId, _selection: SelectionTarget) -> bool {
|
||||
if let Some(keyboard) = self.seat.get_keyboard() {
|
||||
// check that an X11 window is focused
|
||||
if let Some(KeyboardFocusTarget::Window(w)) = keyboard.current_focus() {
|
||||
if let Some(surface) = w.x11_surface() {
|
||||
if surface.xwm_id().unwrap() == xwm {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn send_selection(&mut self, _xwm: XwmId, selection: SelectionTarget, mime_type: String, fd: OwnedFd) {
|
||||
match selection {
|
||||
SelectionTarget::Clipboard => {
|
||||
if let Err(err) = request_data_device_client_selection(&self.seat, mime_type, fd) {
|
||||
error!(?err, "Failed to request current wayland clipboard for Xwayland",);
|
||||
}
|
||||
}
|
||||
SelectionTarget::Primary => {
|
||||
if let Err(err) = request_primary_client_selection(&self.seat, mime_type, fd) {
|
||||
error!(
|
||||
?err,
|
||||
"Failed to request current wayland primary selection for Xwayland",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn new_selection(&mut self, _xwm: XwmId, selection: SelectionTarget, mime_types: Vec<String>) {
|
||||
trace!(?selection, ?mime_types, "Got Selection from X11",);
|
||||
// TODO check, that focused windows is X11 window before doing this
|
||||
match selection {
|
||||
SelectionTarget::Clipboard => {
|
||||
set_data_device_selection(&self.display_handle, &self.seat, mime_types, ())
|
||||
}
|
||||
SelectionTarget::Primary => {
|
||||
set_primary_selection(&self.display_handle, &self.seat, mime_types, ())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn cleared_selection(&mut self, _xwm: XwmId, selection: SelectionTarget) {
|
||||
match selection {
|
||||
SelectionTarget::Clipboard => {
|
||||
if current_data_device_selection_userdata(&self.seat).is_some() {
|
||||
clear_data_device_selection(&self.display_handle, &self.seat)
|
||||
}
|
||||
}
|
||||
SelectionTarget::Primary => {
|
||||
if current_primary_selection_userdata(&self.seat).is_some() {
|
||||
clear_primary_selection(&self.display_handle, &self.seat)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn disconnected(&mut self, _xwm: XwmId) {
|
||||
self.xwm = None;
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> AnvilState<BackendData> {
|
||||
pub fn maximize_request_x11(&mut self, window: &X11Surface) {
|
||||
let Some(elem) = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == window))
|
||||
.cloned()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let old_geo = self.space.element_bbox(&elem).unwrap();
|
||||
let outputs_for_window = self.space.outputs_for_element(&elem);
|
||||
let output = outputs_for_window
|
||||
.first()
|
||||
// The window hasn't been mapped yet, use the primary output instead
|
||||
.or_else(|| self.space.outputs().next())
|
||||
// Assumes that at least one output exists
|
||||
.expect("No outputs found");
|
||||
let geometry = self.space.output_geometry(output).unwrap();
|
||||
|
||||
window.set_maximized(true).unwrap();
|
||||
window.configure(geometry).unwrap();
|
||||
window.user_data().insert_if_missing(OldGeometry::default);
|
||||
window.user_data().get::<OldGeometry>().unwrap().save(old_geo);
|
||||
self.space.map_element(elem, geometry.loc, false);
|
||||
}
|
||||
|
||||
pub fn move_request_x11(&mut self, window: &X11Surface) {
|
||||
if let Some(touch) = self.seat.get_touch() {
|
||||
if let Some(start_data) = touch.grab_start_data() {
|
||||
let element = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == window));
|
||||
|
||||
if let Some(element) = element {
|
||||
let mut initial_window_location = self.space.element_location(element).unwrap();
|
||||
|
||||
// If surface is maximized then unmaximize it
|
||||
if window.is_maximized() {
|
||||
window.set_maximized(false).unwrap();
|
||||
let pos = start_data.location;
|
||||
initial_window_location = (pos.x as i32, pos.y as i32).into();
|
||||
if let Some(old_geo) = window
|
||||
.user_data()
|
||||
.get::<OldGeometry>()
|
||||
.and_then(|data| data.restore())
|
||||
{
|
||||
window
|
||||
.configure(Rectangle::new(initial_window_location, old_geo.size))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let grab = TouchMoveSurfaceGrab {
|
||||
start_data,
|
||||
window: element.clone(),
|
||||
initial_window_location,
|
||||
};
|
||||
|
||||
touch.set_grab(self, grab, SERIAL_COUNTER.next_serial());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// luckily anvil only supports one seat anyway...
|
||||
let Some(start_data) = self.pointer.grab_start_data() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(element) = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|e| matches!(e.0.x11_surface(), Some(w) if w == window))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut initial_window_location = self.space.element_location(element).unwrap();
|
||||
|
||||
// If surface is maximized then unmaximize it
|
||||
if window.is_maximized() {
|
||||
window.set_maximized(false).unwrap();
|
||||
let pos = self.pointer.current_location();
|
||||
initial_window_location = (pos.x as i32, pos.y as i32).into();
|
||||
if let Some(old_geo) = window
|
||||
.user_data()
|
||||
.get::<OldGeometry>()
|
||||
.and_then(|data| data.restore())
|
||||
{
|
||||
window
|
||||
.configure(Rectangle::new(initial_window_location, old_geo.size))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let grab = PointerMoveSurfaceGrab {
|
||||
start_data,
|
||||
window: element.clone(),
|
||||
initial_window_location,
|
||||
};
|
||||
|
||||
let pointer = self.pointer.clone();
|
||||
pointer.set_grab(self, grab, SERIAL_COUNTER.next_serial(), Focus::Clear);
|
||||
}
|
||||
}
|
||||
+641
@@ -0,0 +1,641 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use smithay::{
|
||||
desktop::{
|
||||
PopupKeyboardGrab, PopupKind, PopupPointerGrab, PopupUngrabStrategy, Space, Window,
|
||||
WindowSurfaceType, find_popup_root_surface, get_popup_toplevel_coords, layer_map_for_output,
|
||||
space::SpaceElement,
|
||||
},
|
||||
input::{Seat, pointer::Focus},
|
||||
output::Output,
|
||||
reexports::{
|
||||
wayland_protocols::xdg::{decoration as xdg_decoration, shell::server::xdg_toplevel},
|
||||
wayland_server::{
|
||||
Resource,
|
||||
protocol::{wl_output, wl_seat, wl_surface::WlSurface},
|
||||
},
|
||||
},
|
||||
utils::{Logical, Point, Serial},
|
||||
wayland::{
|
||||
compositor::{self, with_states},
|
||||
seat::WaylandFocus,
|
||||
shell::xdg::{
|
||||
Configure, PopupSurface, PositionerState, ToplevelCachedState, ToplevelSurface, XdgShellHandler,
|
||||
XdgShellState,
|
||||
},
|
||||
},
|
||||
};
|
||||
use tracing::{trace, warn};
|
||||
|
||||
use crate::{
|
||||
focus::KeyboardFocusTarget,
|
||||
shell::{TouchMoveSurfaceGrab, TouchResizeSurfaceGrab},
|
||||
state::{AnvilState, Backend},
|
||||
};
|
||||
|
||||
use super::{
|
||||
FullscreenSurface, PointerMoveSurfaceGrab, PointerResizeSurfaceGrab, ResizeData, ResizeEdge, ResizeState,
|
||||
SurfaceData, WindowElement, fullscreen_output_geometry, place_new_window,
|
||||
};
|
||||
|
||||
impl<BackendData: Backend> XdgShellHandler for AnvilState<BackendData> {
|
||||
fn xdg_shell_state(&mut self) -> &mut XdgShellState {
|
||||
&mut self.xdg_shell_state
|
||||
}
|
||||
|
||||
fn new_toplevel(&mut self, surface: ToplevelSurface) {
|
||||
// Do not send a configure here, the initial configure
|
||||
// of a xdg_surface has to be sent during the commit if
|
||||
// the surface is not already configured
|
||||
let window = WindowElement(Window::new_wayland_window(surface.clone()));
|
||||
place_new_window(&mut self.space, self.pointer.current_location(), &window, true);
|
||||
|
||||
compositor::add_post_commit_hook(surface.wl_surface(), |state: &mut Self, _, surface| {
|
||||
handle_toplevel_commit(&mut state.space, surface);
|
||||
});
|
||||
}
|
||||
|
||||
fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) {
|
||||
// Do not send a configure here, the initial configure
|
||||
// of a xdg_surface has to be sent during the commit if
|
||||
// the surface is not already configured
|
||||
|
||||
self.unconstrain_popup(&surface);
|
||||
|
||||
if let Err(err) = self.popups.track_popup(PopupKind::from(surface)) {
|
||||
warn!("Failed to track popup: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn reposition_request(&mut self, surface: PopupSurface, positioner: PositionerState, token: u32) {
|
||||
surface.with_pending_state(|state| {
|
||||
let geometry = positioner.get_geometry();
|
||||
state.geometry = geometry;
|
||||
state.positioner = positioner;
|
||||
});
|
||||
self.unconstrain_popup(&surface);
|
||||
surface.send_repositioned(token);
|
||||
}
|
||||
|
||||
fn move_request(&mut self, surface: ToplevelSurface, seat: wl_seat::WlSeat, serial: Serial) {
|
||||
let seat: Seat<AnvilState<BackendData>> = Seat::from_resource(&seat).unwrap();
|
||||
self.move_request_xdg(&surface, &seat, serial)
|
||||
}
|
||||
|
||||
fn resize_request(
|
||||
&mut self,
|
||||
surface: ToplevelSurface,
|
||||
seat: wl_seat::WlSeat,
|
||||
serial: Serial,
|
||||
edges: xdg_toplevel::ResizeEdge,
|
||||
) {
|
||||
let seat: Seat<AnvilState<BackendData>> = Seat::from_resource(&seat).unwrap();
|
||||
|
||||
if let Some(touch) = seat.get_touch() {
|
||||
if touch.has_grab(serial) {
|
||||
let start_data = touch.grab_start_data().unwrap();
|
||||
tracing::info!(?start_data);
|
||||
|
||||
// If the client disconnects after requesting a move
|
||||
// we can just ignore the request
|
||||
let Some(window) = self.window_for_surface(surface.wl_surface()) else {
|
||||
tracing::info!("no window");
|
||||
return;
|
||||
};
|
||||
|
||||
// If the focus was for a different surface, ignore the request.
|
||||
if start_data.focus.is_none()
|
||||
|| !start_data
|
||||
.focus
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.0
|
||||
.same_client_as(&surface.wl_surface().id())
|
||||
{
|
||||
tracing::info!("different surface");
|
||||
return;
|
||||
}
|
||||
let geometry = window.geometry();
|
||||
let loc = self.space.element_location(&window).unwrap();
|
||||
let (initial_window_location, initial_window_size) = (loc, geometry.size);
|
||||
|
||||
with_states(surface.wl_surface(), move |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<RefCell<SurfaceData>>()
|
||||
.unwrap()
|
||||
.borrow_mut()
|
||||
.resize_state = ResizeState::Resizing(ResizeData {
|
||||
edges: edges.into(),
|
||||
initial_window_location,
|
||||
initial_window_size,
|
||||
});
|
||||
});
|
||||
|
||||
let grab = TouchResizeSurfaceGrab {
|
||||
start_data,
|
||||
window,
|
||||
edges: edges.into(),
|
||||
initial_window_location,
|
||||
initial_window_size,
|
||||
last_window_size: initial_window_size,
|
||||
};
|
||||
|
||||
touch.set_grab(self, grab, serial);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let pointer = seat.get_pointer().unwrap();
|
||||
|
||||
// Check that this surface has a click grab.
|
||||
if !pointer.has_grab(serial) {
|
||||
return;
|
||||
}
|
||||
|
||||
let start_data = pointer.grab_start_data().unwrap();
|
||||
|
||||
let window = self.window_for_surface(surface.wl_surface()).unwrap();
|
||||
|
||||
// If the focus was for a different surface, ignore the request.
|
||||
if start_data.focus.is_none()
|
||||
|| !start_data
|
||||
.focus
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.0
|
||||
.same_client_as(&surface.wl_surface().id())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let geometry = window.geometry();
|
||||
let loc = self.space.element_location(&window).unwrap();
|
||||
let (initial_window_location, initial_window_size) = (loc, geometry.size);
|
||||
|
||||
with_states(surface.wl_surface(), move |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<RefCell<SurfaceData>>()
|
||||
.unwrap()
|
||||
.borrow_mut()
|
||||
.resize_state = ResizeState::Resizing(ResizeData {
|
||||
edges: edges.into(),
|
||||
initial_window_location,
|
||||
initial_window_size,
|
||||
});
|
||||
});
|
||||
|
||||
let grab = PointerResizeSurfaceGrab {
|
||||
start_data,
|
||||
window,
|
||||
edges: edges.into(),
|
||||
initial_window_location,
|
||||
initial_window_size,
|
||||
last_window_size: initial_window_size,
|
||||
};
|
||||
|
||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||
}
|
||||
|
||||
fn ack_configure(&mut self, surface: WlSurface, configure: Configure) {
|
||||
if let Configure::Toplevel(configure) = configure {
|
||||
if let Some(serial) = with_states(&surface, |states| {
|
||||
if let Some(data) = states.data_map.get::<RefCell<SurfaceData>>() {
|
||||
if let ResizeState::WaitingForFinalAck(_, serial) = data.borrow().resize_state {
|
||||
return Some(serial);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}) {
|
||||
// When the resize grab is released the surface
|
||||
// resize state will be set to WaitingForFinalAck
|
||||
// and the client will receive a configure request
|
||||
// without the resize state to inform the client
|
||||
// resizing has finished. Here we will wait for
|
||||
// the client to acknowledge the end of the
|
||||
// resizing. To check if the surface was resizing
|
||||
// before sending the configure we need to use
|
||||
// the current state as the received acknowledge
|
||||
// will no longer have the resize state set
|
||||
let is_resizing = with_states(&surface, |states| {
|
||||
states
|
||||
.cached_state
|
||||
.get::<ToplevelCachedState>()
|
||||
.current()
|
||||
.last_acked
|
||||
.as_ref()
|
||||
.is_some_and(|c| c.state.states.contains(xdg_toplevel::State::Resizing))
|
||||
});
|
||||
|
||||
if configure.serial >= serial && is_resizing {
|
||||
with_states(&surface, |states| {
|
||||
let mut data = states
|
||||
.data_map
|
||||
.get::<RefCell<SurfaceData>>()
|
||||
.unwrap()
|
||||
.borrow_mut();
|
||||
if let ResizeState::WaitingForFinalAck(resize_data, _) = data.resize_state {
|
||||
data.resize_state = ResizeState::WaitingForCommit(resize_data);
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let window = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|element| element.wl_surface().as_deref() == Some(&surface));
|
||||
if let Some(window) = window {
|
||||
use xdg_decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode;
|
||||
let is_ssd = configure
|
||||
.state
|
||||
.decoration_mode
|
||||
.map(|mode| mode == Mode::ServerSide)
|
||||
.unwrap_or(false);
|
||||
window.set_ssd(is_ssd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fullscreen_request(&mut self, surface: ToplevelSurface, mut wl_output: Option<wl_output::WlOutput>) {
|
||||
// NOTE: This is only one part of the solution. We can set the
|
||||
// location and configure size here, but the surface should be rendered fullscreen
|
||||
// independently from its buffer size
|
||||
let wl_surface = surface.wl_surface();
|
||||
|
||||
let output_geometry = fullscreen_output_geometry(wl_surface, wl_output.as_ref(), &mut self.space);
|
||||
|
||||
if let Some(geometry) = output_geometry {
|
||||
let output = wl_output
|
||||
.as_ref()
|
||||
.and_then(Output::from_resource)
|
||||
.unwrap_or_else(|| self.space.outputs().next().unwrap().clone());
|
||||
let client = match self.display_handle.get_client(wl_surface.id()) {
|
||||
Ok(client) => client,
|
||||
Err(_) => return,
|
||||
};
|
||||
for output in output.client_outputs(&client) {
|
||||
wl_output = Some(output);
|
||||
}
|
||||
let window = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|window| window.wl_surface().map(|s| &*s == wl_surface).unwrap_or(false))
|
||||
.unwrap();
|
||||
|
||||
surface.with_pending_state(|state| {
|
||||
state.states.set(xdg_toplevel::State::Fullscreen);
|
||||
state.size = Some(geometry.size);
|
||||
state.fullscreen_output = wl_output;
|
||||
});
|
||||
output.user_data().insert_if_missing(FullscreenSurface::default);
|
||||
output
|
||||
.user_data()
|
||||
.get::<FullscreenSurface>()
|
||||
.unwrap()
|
||||
.set(window.clone());
|
||||
trace!("Fullscreening: {:?}", window);
|
||||
}
|
||||
|
||||
// The protocol demands us to always reply with a configure,
|
||||
// regardless of we fulfilled the request or not
|
||||
if surface.is_initial_configure_sent() {
|
||||
surface.send_configure();
|
||||
} else {
|
||||
// Will be sent during initial configure
|
||||
}
|
||||
}
|
||||
|
||||
fn unfullscreen_request(&mut self, surface: ToplevelSurface) {
|
||||
let ret = surface.with_pending_state(|state| {
|
||||
state.states.unset(xdg_toplevel::State::Fullscreen);
|
||||
state.size = None;
|
||||
state.fullscreen_output.take()
|
||||
});
|
||||
if let Some(output) = ret {
|
||||
let output = Output::from_resource(&output).unwrap();
|
||||
if let Some(fullscreen) = output.user_data().get::<FullscreenSurface>() {
|
||||
trace!("Unfullscreening: {:?}", fullscreen.get());
|
||||
fullscreen.clear();
|
||||
self.backend_data.reset_buffers(&output);
|
||||
}
|
||||
}
|
||||
|
||||
// The protocol demands us to always reply with a configure,
|
||||
// regardless of we fulfilled the request or not
|
||||
if surface.is_initial_configure_sent() {
|
||||
surface.send_configure();
|
||||
} else {
|
||||
// Will be sent during initial configure
|
||||
}
|
||||
}
|
||||
|
||||
fn maximize_request(&mut self, surface: ToplevelSurface) {
|
||||
// NOTE: This should use layer-shell when it is implemented to
|
||||
// get the correct maximum size
|
||||
let window = self.window_for_surface(surface.wl_surface()).unwrap();
|
||||
let outputs_for_window = self.space.outputs_for_element(&window);
|
||||
let output = outputs_for_window
|
||||
.first()
|
||||
// The window hasn't been mapped yet, use the primary output instead
|
||||
.or_else(|| self.space.outputs().next())
|
||||
// Assumes that at least one output exists
|
||||
.expect("No outputs found");
|
||||
let geometry = self.space.output_geometry(output).unwrap();
|
||||
|
||||
surface.with_pending_state(|state| {
|
||||
state.states.set(xdg_toplevel::State::Maximized);
|
||||
state.size = Some(geometry.size);
|
||||
});
|
||||
self.space.map_element(window, geometry.loc, true);
|
||||
|
||||
// The protocol demands us to always reply with a configure,
|
||||
// regardless of we fulfilled the request or not
|
||||
if surface.is_initial_configure_sent() {
|
||||
surface.send_configure();
|
||||
} else {
|
||||
// Will be sent during initial configure
|
||||
}
|
||||
}
|
||||
|
||||
fn unmaximize_request(&mut self, surface: ToplevelSurface) {
|
||||
surface.with_pending_state(|state| {
|
||||
state.states.unset(xdg_toplevel::State::Maximized);
|
||||
state.size = None;
|
||||
});
|
||||
|
||||
// The protocol demands us to always reply with a configure,
|
||||
// regardless of we fulfilled the request or not
|
||||
if surface.is_initial_configure_sent() {
|
||||
surface.send_configure();
|
||||
} else {
|
||||
// Will be sent during initial configure
|
||||
}
|
||||
}
|
||||
|
||||
fn grab(&mut self, surface: PopupSurface, seat: wl_seat::WlSeat, serial: Serial) {
|
||||
let seat: Seat<AnvilState<BackendData>> = Seat::from_resource(&seat).unwrap();
|
||||
let kind = PopupKind::Xdg(surface);
|
||||
if let Some(root) = find_popup_root_surface(&kind).ok().and_then(|root| {
|
||||
self.space
|
||||
.elements()
|
||||
.find(|w| w.wl_surface().map(|s| *s == root).unwrap_or(false))
|
||||
.cloned()
|
||||
.map(KeyboardFocusTarget::from)
|
||||
.or_else(|| {
|
||||
self.space
|
||||
.outputs()
|
||||
.find_map(|o| {
|
||||
let map = layer_map_for_output(o);
|
||||
map.layer_for_surface(&root, WindowSurfaceType::TOPLEVEL).cloned()
|
||||
})
|
||||
.map(KeyboardFocusTarget::LayerSurface)
|
||||
})
|
||||
}) {
|
||||
let ret = self.popups.grab_popup(root, kind, &seat, serial);
|
||||
|
||||
if let Ok(mut grab) = ret {
|
||||
if let Some(keyboard) = seat.get_keyboard() {
|
||||
if keyboard.is_grabbed()
|
||||
&& !(keyboard.has_grab(serial)
|
||||
|| keyboard.has_grab(grab.previous_serial().unwrap_or(serial)))
|
||||
{
|
||||
grab.ungrab(PopupUngrabStrategy::All);
|
||||
return;
|
||||
}
|
||||
keyboard.set_focus(self, grab.current_grab(), serial);
|
||||
keyboard.set_grab(self, PopupKeyboardGrab::new(&grab), serial);
|
||||
}
|
||||
if let Some(pointer) = seat.get_pointer() {
|
||||
if pointer.is_grabbed()
|
||||
&& !(pointer.has_grab(serial)
|
||||
|| pointer.has_grab(grab.previous_serial().unwrap_or_else(|| grab.serial())))
|
||||
{
|
||||
grab.ungrab(PopupUngrabStrategy::All);
|
||||
return;
|
||||
}
|
||||
pointer.set_grab(self, PopupPointerGrab::new(&grab), serial, Focus::Keep);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<BackendData: Backend> AnvilState<BackendData> {
|
||||
pub fn move_request_xdg(&mut self, surface: &ToplevelSurface, seat: &Seat<Self>, serial: Serial) {
|
||||
if let Some(touch) = seat.get_touch() {
|
||||
if touch.has_grab(serial) {
|
||||
let start_data = touch.grab_start_data().unwrap();
|
||||
|
||||
// If the client disconnects after requesting a move
|
||||
// we can just ignore the request
|
||||
let Some(window) = self.window_for_surface(surface.wl_surface()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// If the focus was for a different surface, ignore the request.
|
||||
if start_data.focus.is_none()
|
||||
|| !start_data
|
||||
.focus
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.0
|
||||
.same_client_as(&surface.wl_surface().id())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut initial_window_location = self.space.element_location(&window).unwrap();
|
||||
|
||||
// If surface is maximized then unmaximize it
|
||||
let changed = surface.with_pending_state(|state| {
|
||||
if state.states.unset(xdg_toplevel::State::Maximized) {
|
||||
state.size = None;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
if changed {
|
||||
surface.send_configure();
|
||||
|
||||
// NOTE: In real compositor mouse location should be mapped to a new window size
|
||||
// For example, you could:
|
||||
// 1) transform mouse pointer position from compositor space to window space (location relative)
|
||||
// 2) divide the x coordinate by width of the window to get the percentage
|
||||
// - 0.0 would be on the far left of the window
|
||||
// - 0.5 would be in middle of the window
|
||||
// - 1.0 would be on the far right of the window
|
||||
// 3) multiply the percentage by new window width
|
||||
// 4) by doing that, drag will look a lot more natural
|
||||
//
|
||||
// but for anvil needs setting location to pointer location is fine
|
||||
initial_window_location = start_data.location.to_i32_round();
|
||||
}
|
||||
|
||||
let grab = TouchMoveSurfaceGrab {
|
||||
start_data,
|
||||
window,
|
||||
initial_window_location,
|
||||
};
|
||||
|
||||
touch.set_grab(self, grab, serial);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let pointer = seat.get_pointer().unwrap();
|
||||
|
||||
// Check that this surface has a click grab.
|
||||
if !pointer.has_grab(serial) {
|
||||
return;
|
||||
}
|
||||
|
||||
let start_data = pointer.grab_start_data().unwrap();
|
||||
|
||||
// If the client disconnects after requesting a move
|
||||
// we can just ignore the request
|
||||
let Some(window) = self.window_for_surface(surface.wl_surface()) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// If the focus was for a different surface, ignore the request.
|
||||
if start_data.focus.is_none()
|
||||
|| !start_data
|
||||
.focus
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.0
|
||||
.same_client_as(&surface.wl_surface().id())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
let mut initial_window_location = self.space.element_location(&window).unwrap();
|
||||
|
||||
// If surface is maximized then unmaximize it
|
||||
let changed = surface.with_pending_state(|state| {
|
||||
if state.states.unset(xdg_toplevel::State::Maximized) {
|
||||
state.size = None;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
if changed {
|
||||
surface.send_configure();
|
||||
|
||||
// NOTE: In real compositor mouse location should be mapped to a new window size
|
||||
// For example, you could:
|
||||
// 1) transform mouse pointer position from compositor space to window space (location relative)
|
||||
// 2) divide the x coordinate by width of the window to get the percentage
|
||||
// - 0.0 would be on the far left of the window
|
||||
// - 0.5 would be in middle of the window
|
||||
// - 1.0 would be on the far right of the window
|
||||
// 3) multiply the percentage by new window width
|
||||
// 4) by doing that, drag will look a lot more natural
|
||||
//
|
||||
// but for anvil needs setting location to pointer location is fine
|
||||
let pos = pointer.current_location();
|
||||
initial_window_location = (pos.x as i32, pos.y as i32).into();
|
||||
}
|
||||
|
||||
let grab = PointerMoveSurfaceGrab {
|
||||
start_data,
|
||||
window,
|
||||
initial_window_location,
|
||||
};
|
||||
|
||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||
}
|
||||
|
||||
fn unconstrain_popup(&self, popup: &PopupSurface) {
|
||||
let Ok(root) = find_popup_root_surface(&PopupKind::Xdg(popup.clone())) else {
|
||||
return;
|
||||
};
|
||||
let Some(window) = self.window_for_surface(&root) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut outputs_for_window = self.space.outputs_for_element(&window);
|
||||
if outputs_for_window.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get a union of all outputs' geometries.
|
||||
let mut outputs_geo = self
|
||||
.space
|
||||
.output_geometry(&outputs_for_window.pop().unwrap())
|
||||
.unwrap();
|
||||
for output in outputs_for_window {
|
||||
outputs_geo = outputs_geo.merge(self.space.output_geometry(&output).unwrap());
|
||||
}
|
||||
|
||||
let window_geo = self.space.element_geometry(&window).unwrap();
|
||||
|
||||
// The target geometry for the positioner should be relative to its parent's geometry, so
|
||||
// we will compute that here.
|
||||
let mut target = outputs_geo;
|
||||
target.loc -= get_popup_toplevel_coords(&PopupKind::Xdg(popup.clone()));
|
||||
target.loc -= window_geo.loc;
|
||||
|
||||
popup.with_pending_state(|state| {
|
||||
state.geometry = state.positioner.get_unconstrained_geometry(target);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Should be called on `WlSurface::commit` of xdg toplevel
|
||||
fn handle_toplevel_commit(space: &mut Space<WindowElement>, surface: &WlSurface) -> Option<()> {
|
||||
let window = space
|
||||
.elements()
|
||||
.find(|w| w.wl_surface().as_deref() == Some(surface))
|
||||
.cloned()?;
|
||||
|
||||
let mut window_loc = space.element_location(&window)?;
|
||||
let geometry = window.geometry();
|
||||
|
||||
let new_loc: Point<Option<i32>, Logical> = with_states(window.wl_surface().as_deref()?, |states| {
|
||||
let data = states.data_map.get::<RefCell<SurfaceData>>()?.borrow_mut();
|
||||
|
||||
if let ResizeState::Resizing(resize_data) = data.resize_state {
|
||||
let edges = resize_data.edges;
|
||||
let loc = resize_data.initial_window_location;
|
||||
let size = resize_data.initial_window_size;
|
||||
|
||||
// If the window is being resized by top or left, its location must be adjusted
|
||||
// accordingly.
|
||||
edges.intersects(ResizeEdge::TOP_LEFT).then(|| {
|
||||
let new_x = edges
|
||||
.intersects(ResizeEdge::LEFT)
|
||||
.then_some(loc.x + (size.w - geometry.size.w));
|
||||
|
||||
let new_y = edges
|
||||
.intersects(ResizeEdge::TOP)
|
||||
.then_some(loc.y + (size.h - geometry.size.h));
|
||||
|
||||
(new_x, new_y).into()
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})?;
|
||||
|
||||
if let Some(new_x) = new_loc.x {
|
||||
window_loc.x = new_x;
|
||||
}
|
||||
if let Some(new_y) = new_loc.y {
|
||||
window_loc.y = new_y;
|
||||
}
|
||||
|
||||
if new_loc.x.is_some() || new_loc.y.is_some() {
|
||||
// If TOP or LEFT side of the window got resized, we have to move it
|
||||
space.map_element(window, window_loc, false);
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
Vendored
+1215
File diff suppressed because it is too large.
Load diff
Vendored
+1688
File diff suppressed because it is too large.
Load diff
Vendored
+463
@@ -0,0 +1,463 @@
|
||||
use std::{
|
||||
sync::{Mutex, atomic::Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[cfg(feature = "egl")]
|
||||
use smithay::backend::renderer::ImportEgl;
|
||||
#[cfg(feature = "debug")]
|
||||
use smithay::{
|
||||
backend::{allocator::Fourcc, renderer::ImportMem},
|
||||
reexports::winit::raw_window_handle::{HasWindowHandle, RawWindowHandle},
|
||||
};
|
||||
|
||||
use smithay::{
|
||||
backend::{
|
||||
SwapBuffersError,
|
||||
allocator::dmabuf::Dmabuf,
|
||||
egl::EGLDevice,
|
||||
renderer::{
|
||||
ImportDma, ImportMemWl,
|
||||
damage::{Error as OutputDamageTrackerError, OutputDamageTracker},
|
||||
element::AsRenderElements,
|
||||
gles::GlesRenderer,
|
||||
},
|
||||
winit::{self, WinitEvent, WinitGraphicsBackend},
|
||||
},
|
||||
input::{
|
||||
keyboard::LedState,
|
||||
pointer::{CursorImageAttributes, CursorImageStatus},
|
||||
},
|
||||
output::{Mode, Output, PhysicalProperties, Subpixel},
|
||||
reexports::{
|
||||
calloop::EventLoop,
|
||||
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback,
|
||||
wayland_server::{Display, protocol::wl_surface},
|
||||
winit::event_loop::pump_events::PumpStatus,
|
||||
},
|
||||
utils::{IsAlive, Scale, Transform},
|
||||
wayland::{
|
||||
compositor,
|
||||
dmabuf::{
|
||||
DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier,
|
||||
},
|
||||
presentation::Refresh,
|
||||
},
|
||||
};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use crate::state::{AnvilState, Backend, take_presentation_feedback, update_primary_scanout_output};
|
||||
use crate::{drawing::*, render::*};
|
||||
|
||||
pub const OUTPUT_NAME: &str = "winit";
|
||||
|
||||
pub struct WinitData {
|
||||
backend: WinitGraphicsBackend<GlesRenderer>,
|
||||
damage_tracker: OutputDamageTracker,
|
||||
dmabuf_state: (DmabufState, DmabufGlobal, Option<DmabufFeedback>),
|
||||
full_redraw: u8,
|
||||
#[cfg(feature = "debug")]
|
||||
pub fps: fps_ticker::Fps,
|
||||
}
|
||||
|
||||
impl DmabufHandler for AnvilState<WinitData> {
|
||||
fn dmabuf_state(&mut self) -> &mut DmabufState {
|
||||
&mut self.backend_data.dmabuf_state.0
|
||||
}
|
||||
|
||||
fn dmabuf_imported(&mut self, _global: &DmabufGlobal, dmabuf: Dmabuf, notifier: ImportNotifier) {
|
||||
if self
|
||||
.backend_data
|
||||
.backend
|
||||
.renderer()
|
||||
.import_dmabuf(&dmabuf, None)
|
||||
.is_ok()
|
||||
{
|
||||
let _ = notifier.successful::<AnvilState<WinitData>>();
|
||||
} else {
|
||||
notifier.failed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Backend for WinitData {
|
||||
const HAS_GESTURES: bool = true;
|
||||
|
||||
fn seat_name(&self) -> String {
|
||||
String::from("winit")
|
||||
}
|
||||
fn reset_buffers(&mut self, _output: &Output) {
|
||||
self.full_redraw = 4;
|
||||
}
|
||||
fn early_import(&mut self, _surface: &wl_surface::WlSurface) {}
|
||||
fn update_led_state(&mut self, _led_state: LedState) {}
|
||||
}
|
||||
|
||||
pub fn run_winit() {
|
||||
let mut event_loop = EventLoop::try_new().unwrap();
|
||||
let display = Display::new().unwrap();
|
||||
let mut display_handle = display.handle();
|
||||
|
||||
#[cfg_attr(not(feature = "egl"), allow(unused_mut))]
|
||||
let (mut backend, mut winit) = match winit::init::<GlesRenderer>() {
|
||||
Ok(ret) => ret,
|
||||
Err(err) => {
|
||||
error!("Failed to initialize Winit backend: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let size = backend.window_size();
|
||||
|
||||
let mode = Mode {
|
||||
size,
|
||||
refresh: 60_000,
|
||||
};
|
||||
let output = Output::new(
|
||||
OUTPUT_NAME.to_string(),
|
||||
PhysicalProperties {
|
||||
size: (0, 0).into(),
|
||||
subpixel: Subpixel::Unknown,
|
||||
make: "Smithay".into(),
|
||||
model: "Winit".into(),
|
||||
serial_number: "Unknown".into(),
|
||||
},
|
||||
);
|
||||
let _global = output.create_global::<AnvilState<WinitData>>(&display.handle());
|
||||
output.change_current_state(Some(mode), Some(Transform::Flipped180), None, Some((0, 0).into()));
|
||||
output.set_preferred(mode);
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
#[allow(deprecated)]
|
||||
let fps_image =
|
||||
image::io::Reader::with_format(std::io::Cursor::new(FPS_NUMBERS_PNG), image::ImageFormat::Png)
|
||||
.decode()
|
||||
.unwrap();
|
||||
#[cfg(feature = "debug")]
|
||||
let fps_texture = backend
|
||||
.renderer()
|
||||
.import_memory(
|
||||
&fps_image.to_rgba8(),
|
||||
Fourcc::Abgr8888,
|
||||
(fps_image.width() as i32, fps_image.height() as i32).into(),
|
||||
false,
|
||||
)
|
||||
.expect("Unable to upload FPS texture");
|
||||
#[cfg(feature = "debug")]
|
||||
let mut fps_element = FpsElement::new(fps_texture);
|
||||
|
||||
let render_node = EGLDevice::device_for_display(backend.renderer().egl_context().display())
|
||||
.and_then(|device| device.try_get_render_node());
|
||||
|
||||
let dmabuf_default_feedback = match render_node {
|
||||
Ok(Some(node)) => {
|
||||
let dmabuf_formats = backend.renderer().dmabuf_formats();
|
||||
let dmabuf_default_feedback = DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats)
|
||||
.build()
|
||||
.unwrap();
|
||||
Some(dmabuf_default_feedback)
|
||||
}
|
||||
Ok(None) => {
|
||||
warn!("failed to query render node, dmabuf will use v3");
|
||||
None
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(?err, "failed to egl device for display, dmabuf will use v3");
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
// if we failed to build dmabuf feedback we fall back to dmabuf v3
|
||||
// Note: egl on Mesa requires either v4 or wl_drm (initialized with bind_wl_display)
|
||||
let dmabuf_state = if let Some(default_feedback) = dmabuf_default_feedback {
|
||||
let mut dmabuf_state = DmabufState::new();
|
||||
let dmabuf_global = dmabuf_state.create_global_with_default_feedback::<AnvilState<WinitData>>(
|
||||
&display.handle(),
|
||||
&default_feedback,
|
||||
);
|
||||
(dmabuf_state, dmabuf_global, Some(default_feedback))
|
||||
} else {
|
||||
let dmabuf_formats = backend.renderer().dmabuf_formats();
|
||||
let mut dmabuf_state = DmabufState::new();
|
||||
let dmabuf_global =
|
||||
dmabuf_state.create_global::<AnvilState<WinitData>>(&display.handle(), dmabuf_formats);
|
||||
(dmabuf_state, dmabuf_global, None)
|
||||
};
|
||||
|
||||
#[cfg(feature = "egl")]
|
||||
if backend.renderer().bind_wl_display(&display.handle()).is_ok() {
|
||||
info!("EGL hardware-acceleration enabled");
|
||||
};
|
||||
|
||||
let data = {
|
||||
let damage_tracker = OutputDamageTracker::from_output(&output);
|
||||
|
||||
WinitData {
|
||||
backend,
|
||||
damage_tracker,
|
||||
dmabuf_state,
|
||||
full_redraw: 0,
|
||||
#[cfg(feature = "debug")]
|
||||
fps: fps_ticker::Fps::default(),
|
||||
}
|
||||
};
|
||||
let mut state = AnvilState::init(display, event_loop.handle(), data, true);
|
||||
state
|
||||
.shm_state
|
||||
.update_formats(state.backend_data.backend.renderer().shm_formats());
|
||||
state.space.map_output(&output, (0, 0));
|
||||
|
||||
#[cfg(feature = "xwayland")]
|
||||
state.start_xwayland();
|
||||
|
||||
info!("Initialization completed, starting the main loop.");
|
||||
|
||||
let mut pointer_element = PointerElement::default();
|
||||
|
||||
while state.running.load(Ordering::SeqCst) {
|
||||
let status = winit.dispatch_new_events(|event| match event {
|
||||
WinitEvent::Resized { size, .. } => {
|
||||
// We only have one output
|
||||
let output = state.space.outputs().next().unwrap().clone();
|
||||
state.space.map_output(&output, (0, 0));
|
||||
let mode = Mode {
|
||||
size,
|
||||
refresh: 60_000,
|
||||
};
|
||||
output.change_current_state(Some(mode), None, None, None);
|
||||
output.set_preferred(mode);
|
||||
crate::shell::fixup_positions(&mut state.space, state.pointer.current_location());
|
||||
}
|
||||
WinitEvent::Input(event) => state.process_input_event_windowed(event, OUTPUT_NAME),
|
||||
_ => (),
|
||||
});
|
||||
|
||||
if let PumpStatus::Exit(_) = status {
|
||||
state.running.store(false, Ordering::SeqCst);
|
||||
break;
|
||||
}
|
||||
|
||||
// drawing logic
|
||||
{
|
||||
let now = state.clock.now();
|
||||
let frame_target = now
|
||||
+ output
|
||||
.current_mode()
|
||||
.map(|mode| Duration::from_secs_f64(1_000f64 / mode.refresh as f64))
|
||||
.unwrap_or_default();
|
||||
state.pre_repaint(&output, frame_target);
|
||||
|
||||
let backend = &mut state.backend_data.backend;
|
||||
|
||||
// draw the cursor as relevant
|
||||
// reset the cursor if the surface is no longer alive
|
||||
let mut reset = false;
|
||||
if let CursorImageStatus::Surface(ref surface) = state.cursor_status {
|
||||
reset = !surface.alive();
|
||||
}
|
||||
if reset {
|
||||
state.cursor_status = CursorImageStatus::default_named();
|
||||
}
|
||||
let cursor_visible = !matches!(state.cursor_status, CursorImageStatus::Surface(_));
|
||||
|
||||
pointer_element.set_status(state.cursor_status.clone());
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
let fps = state.backend_data.fps.avg().round() as u32;
|
||||
#[cfg(feature = "debug")]
|
||||
fps_element.update_fps(fps);
|
||||
|
||||
let full_redraw = &mut state.backend_data.full_redraw;
|
||||
*full_redraw = full_redraw.saturating_sub(1);
|
||||
let space = &mut state.space;
|
||||
let damage_tracker = &mut state.backend_data.damage_tracker;
|
||||
let show_window_preview = state.show_window_preview;
|
||||
|
||||
let dnd_icon = state.dnd_icon.as_ref();
|
||||
|
||||
let scale = Scale::from(output.current_scale().fractional_scale());
|
||||
let cursor_hotspot = if let CursorImageStatus::Surface(ref surface) = state.cursor_status {
|
||||
compositor::with_states(surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<Mutex<CursorImageAttributes>>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.hotspot
|
||||
})
|
||||
} else {
|
||||
(0, 0).into()
|
||||
};
|
||||
let cursor_pos = state.pointer.current_location();
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
let mut renderdoc = state.renderdoc.as_mut();
|
||||
|
||||
let age = if *full_redraw > 0 {
|
||||
0
|
||||
} else {
|
||||
backend.buffer_age().unwrap_or(0)
|
||||
};
|
||||
#[cfg(feature = "debug")]
|
||||
let window_handle = backend
|
||||
.window()
|
||||
.window_handle()
|
||||
.map(|handle| {
|
||||
if let RawWindowHandle::Wayland(handle) = handle.as_raw() {
|
||||
handle.surface.as_ptr()
|
||||
} else {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|_| std::ptr::null_mut());
|
||||
let render_res = backend.bind().and_then(|(renderer, mut fb)| {
|
||||
#[cfg(feature = "debug")]
|
||||
if let Some(renderdoc) = renderdoc.as_mut() {
|
||||
renderdoc.start_frame_capture(renderer.egl_context().get_context_handle(), window_handle);
|
||||
}
|
||||
|
||||
let mut elements = Vec::<CustomRenderElements<GlesRenderer>>::new();
|
||||
|
||||
elements.extend(
|
||||
pointer_element.render_elements(
|
||||
renderer,
|
||||
(cursor_pos - cursor_hotspot.to_f64())
|
||||
.to_physical(scale)
|
||||
.to_i32_round(),
|
||||
scale,
|
||||
1.0,
|
||||
),
|
||||
);
|
||||
|
||||
// draw the dnd icon if any
|
||||
if let Some(icon) = dnd_icon {
|
||||
let dnd_icon_pos = (cursor_pos + icon.offset.to_f64())
|
||||
.to_physical(scale)
|
||||
.to_i32_round();
|
||||
if icon.surface.alive() {
|
||||
elements.extend(AsRenderElements::<GlesRenderer>::render_elements(
|
||||
&smithay::desktop::space::SurfaceTree::from_surface(&icon.surface),
|
||||
renderer,
|
||||
dnd_icon_pos,
|
||||
scale,
|
||||
1.0,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
elements.push(CustomRenderElements::Fps(fps_element.clone()));
|
||||
|
||||
render_output(
|
||||
&output,
|
||||
space,
|
||||
elements,
|
||||
renderer,
|
||||
&mut fb,
|
||||
damage_tracker,
|
||||
age,
|
||||
show_window_preview,
|
||||
)
|
||||
.map_err(|err| match err {
|
||||
OutputDamageTrackerError::Rendering(err) => err.into(),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
});
|
||||
|
||||
match render_res {
|
||||
Ok(render_output_result) => {
|
||||
let has_rendered = render_output_result.damage.is_some();
|
||||
if let Some(damage) = render_output_result.damage {
|
||||
if let Err(err) = backend.submit(Some(damage)) {
|
||||
warn!("Failed to submit buffer: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
if let Some(renderdoc) = renderdoc.as_mut() {
|
||||
renderdoc.end_frame_capture(
|
||||
backend.renderer().egl_context().get_context_handle(),
|
||||
backend
|
||||
.window()
|
||||
.window_handle()
|
||||
.map(|handle| {
|
||||
if let RawWindowHandle::Wayland(handle) = handle.as_raw() {
|
||||
handle.surface.as_ptr()
|
||||
} else {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|_| std::ptr::null_mut()),
|
||||
);
|
||||
}
|
||||
|
||||
backend.window().set_cursor_visible(cursor_visible);
|
||||
|
||||
let states = render_output_result.states;
|
||||
|
||||
update_primary_scanout_output(
|
||||
&state.space,
|
||||
&output,
|
||||
&state.dnd_icon,
|
||||
&state.cursor_status,
|
||||
&states,
|
||||
);
|
||||
|
||||
if has_rendered {
|
||||
let mut output_presentation_feedback =
|
||||
take_presentation_feedback(&output, &state.space, &states);
|
||||
output_presentation_feedback.presented(
|
||||
frame_target,
|
||||
output
|
||||
.current_mode()
|
||||
.map(|mode| {
|
||||
Refresh::fixed(Duration::from_secs_f64(1_000f64 / mode.refresh as f64))
|
||||
})
|
||||
.unwrap_or(Refresh::Unknown),
|
||||
0,
|
||||
wp_presentation_feedback::Kind::Vsync,
|
||||
)
|
||||
}
|
||||
|
||||
// Send frame events so that client start drawing their next frame
|
||||
state.post_repaint(&output, frame_target, None, &states);
|
||||
}
|
||||
Err(SwapBuffersError::ContextLost(err)) => {
|
||||
#[cfg(feature = "debug")]
|
||||
if let Some(renderdoc) = renderdoc.as_mut() {
|
||||
renderdoc.discard_frame_capture(
|
||||
backend.renderer().egl_context().get_context_handle(),
|
||||
backend
|
||||
.window()
|
||||
.window_handle()
|
||||
.map(|handle| {
|
||||
if let RawWindowHandle::Wayland(handle) = handle.as_raw() {
|
||||
handle.surface.as_ptr()
|
||||
} else {
|
||||
std::ptr::null_mut()
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|_| std::ptr::null_mut()),
|
||||
);
|
||||
}
|
||||
|
||||
error!("Critical Rendering Error: {}", err);
|
||||
state.running.store(false, Ordering::SeqCst);
|
||||
}
|
||||
Err(err) => warn!("Rendering error: {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
let result = event_loop.dispatch(Some(Duration::from_millis(1)), &mut state);
|
||||
if result.is_err() {
|
||||
state.running.store(false, Ordering::SeqCst);
|
||||
} else {
|
||||
state.space.refresh();
|
||||
state.popups.cleanup();
|
||||
display_handle.flush_clients().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
state.backend_data.fps.tick();
|
||||
}
|
||||
}
|
||||
Vendored
+494
@@ -0,0 +1,494 @@
|
||||
use std::{
|
||||
sync::{Mutex, atomic::Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
drawing::*,
|
||||
render::*,
|
||||
state::{AnvilState, Backend, take_presentation_feedback, update_primary_scanout_output},
|
||||
};
|
||||
#[cfg(feature = "egl")]
|
||||
use smithay::backend::renderer::ImportEgl;
|
||||
#[cfg(feature = "debug")]
|
||||
use smithay::backend::{allocator::Fourcc, renderer::ImportMem};
|
||||
|
||||
use smithay::{
|
||||
backend::{
|
||||
allocator::{
|
||||
dmabuf::{Dmabuf, DmabufAllocator},
|
||||
gbm::{GbmAllocator, GbmBufferFlags},
|
||||
vulkan::{ImageUsageFlags, VulkanAllocator},
|
||||
},
|
||||
egl::{EGLContext, EGLDisplay},
|
||||
renderer::{
|
||||
Bind, ImportDma, ImportMemWl, damage::OutputDamageTracker, element::AsRenderElements,
|
||||
gles::GlesRenderer,
|
||||
},
|
||||
vulkan::{Instance, PhysicalDevice, version::Version},
|
||||
x11::{WindowBuilder, X11Backend, X11Event, X11Surface},
|
||||
},
|
||||
input::{
|
||||
keyboard::LedState,
|
||||
pointer::{CursorImageAttributes, CursorImageStatus},
|
||||
},
|
||||
output::{Mode, Output, PhysicalProperties, Subpixel},
|
||||
reexports::{
|
||||
ash::ext,
|
||||
calloop::EventLoop,
|
||||
gbm,
|
||||
wayland_protocols::wp::presentation_time::server::wp_presentation_feedback,
|
||||
wayland_server::{Display, protocol::wl_surface},
|
||||
},
|
||||
utils::{DeviceFd, IsAlive, Scale},
|
||||
wayland::{
|
||||
compositor,
|
||||
dmabuf::{
|
||||
DmabufFeedback, DmabufFeedbackBuilder, DmabufGlobal, DmabufHandler, DmabufState, ImportNotifier,
|
||||
},
|
||||
presentation::Refresh,
|
||||
},
|
||||
};
|
||||
use tracing::{error, info, trace, warn};
|
||||
|
||||
pub const OUTPUT_NAME: &str = "x11";
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct X11Data {
|
||||
render: bool,
|
||||
mode: Mode,
|
||||
// FIXME: If GlesRenderer is dropped before X11Surface, then the MakeCurrent call inside Gles2Renderer will
|
||||
// fail because the X11Surface is keeping gbm alive.
|
||||
renderer: GlesRenderer,
|
||||
damage_tracker: OutputDamageTracker,
|
||||
surface: X11Surface,
|
||||
dmabuf_state: DmabufState,
|
||||
_dmabuf_global: DmabufGlobal,
|
||||
_dmabuf_default_feedback: DmabufFeedback,
|
||||
#[cfg(feature = "debug")]
|
||||
fps: fps_ticker::Fps,
|
||||
}
|
||||
|
||||
impl DmabufHandler for AnvilState<X11Data> {
|
||||
fn dmabuf_state(&mut self) -> &mut DmabufState {
|
||||
&mut self.backend_data.dmabuf_state
|
||||
}
|
||||
|
||||
fn dmabuf_imported(&mut self, _global: &DmabufGlobal, dmabuf: Dmabuf, notifier: ImportNotifier) {
|
||||
if self.backend_data.renderer.import_dmabuf(&dmabuf, None).is_ok() {
|
||||
let _ = notifier.successful::<AnvilState<X11Data>>();
|
||||
} else {
|
||||
notifier.failed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Backend for X11Data {
|
||||
fn seat_name(&self) -> String {
|
||||
"x11".to_owned()
|
||||
}
|
||||
fn reset_buffers(&mut self, _output: &Output) {
|
||||
self.surface.reset_buffers();
|
||||
}
|
||||
fn early_import(&mut self, _surface: &wl_surface::WlSurface) {}
|
||||
fn update_led_state(&mut self, _led_state: LedState) {}
|
||||
}
|
||||
|
||||
pub fn run_x11() {
|
||||
let mut event_loop = EventLoop::try_new().unwrap();
|
||||
let display = Display::new().unwrap();
|
||||
let mut display_handle = display.handle();
|
||||
|
||||
let backend = X11Backend::new().expect("Failed to initialize X11 backend");
|
||||
let handle = backend.handle();
|
||||
|
||||
// Obtain the DRM node the X server uses for direct rendering.
|
||||
let (node, fd) = handle
|
||||
.drm_node()
|
||||
.expect("Could not get DRM node used by X server");
|
||||
|
||||
// Create the gbm device for buffer allocation.
|
||||
let device = gbm::Device::new(DeviceFd::from(fd)).expect("Failed to create gbm device");
|
||||
// Initialize EGL using the GBM device.
|
||||
let egl = unsafe { EGLDisplay::new(device.clone()).expect("Failed to create EGLDisplay") };
|
||||
// Create the OpenGL context
|
||||
let context = EGLContext::new(&egl).expect("Failed to create EGLContext");
|
||||
|
||||
let window = WindowBuilder::new()
|
||||
.title("Anvil")
|
||||
.build(&handle)
|
||||
.expect("Failed to create first window");
|
||||
|
||||
let skip_vulkan = std::env::var("ANVIL_NO_VULKAN")
|
||||
.map(|x| {
|
||||
x == "1" || x.to_lowercase() == "true" || x.to_lowercase() == "yes" || x.to_lowercase() == "y"
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
let vulkan_allocator = if !skip_vulkan {
|
||||
Instance::new(Version::VERSION_1_2, None)
|
||||
.ok()
|
||||
.and_then(|instance| {
|
||||
PhysicalDevice::enumerate(&instance).ok().and_then(|devices| {
|
||||
devices
|
||||
.filter(|phd| phd.has_device_extension(ext::physical_device_drm::NAME))
|
||||
.find(|phd| {
|
||||
phd.primary_node().unwrap() == Some(node)
|
||||
|| phd.render_node().unwrap() == Some(node)
|
||||
})
|
||||
})
|
||||
})
|
||||
.and_then(|physical_device| {
|
||||
VulkanAllocator::new(
|
||||
&physical_device,
|
||||
ImageUsageFlags::COLOR_ATTACHMENT | ImageUsageFlags::SAMPLED,
|
||||
)
|
||||
.ok()
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let surface = match vulkan_allocator {
|
||||
// Create the surface for the window.
|
||||
Some(vulkan_allocator) => handle
|
||||
.create_surface(
|
||||
&window,
|
||||
DmabufAllocator(vulkan_allocator),
|
||||
context
|
||||
.dmabuf_render_formats()
|
||||
.iter()
|
||||
.map(|format| format.modifier),
|
||||
)
|
||||
.expect("Failed to create X11 surface"),
|
||||
None => handle
|
||||
.create_surface(
|
||||
&window,
|
||||
DmabufAllocator(GbmAllocator::new(device, GbmBufferFlags::RENDERING)),
|
||||
context
|
||||
.dmabuf_render_formats()
|
||||
.iter()
|
||||
.map(|format| format.modifier),
|
||||
)
|
||||
.expect("Failed to create X11 surface"),
|
||||
};
|
||||
|
||||
#[cfg_attr(not(feature = "egl"), allow(unused_mut))]
|
||||
let mut renderer = unsafe { GlesRenderer::new(context) }.expect("Failed to initialize renderer");
|
||||
|
||||
#[cfg(feature = "egl")]
|
||||
if renderer.bind_wl_display(&display.handle()).is_ok() {
|
||||
info!("EGL hardware-acceleration enabled");
|
||||
}
|
||||
|
||||
let dmabuf_formats = renderer.dmabuf_formats();
|
||||
let dmabuf_default_feedback = DmabufFeedbackBuilder::new(node.dev_id(), dmabuf_formats)
|
||||
.build()
|
||||
.unwrap();
|
||||
let mut dmabuf_state = DmabufState::new();
|
||||
let dmabuf_global = dmabuf_state.create_global_with_default_feedback::<AnvilState<X11Data>>(
|
||||
&display.handle(),
|
||||
&dmabuf_default_feedback,
|
||||
);
|
||||
|
||||
let size = {
|
||||
let s = window.size();
|
||||
|
||||
(s.w as i32, s.h as i32).into()
|
||||
};
|
||||
|
||||
let mode = Mode {
|
||||
size,
|
||||
refresh: 60_000,
|
||||
};
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
#[allow(deprecated)]
|
||||
let fps_image =
|
||||
image::io::Reader::with_format(std::io::Cursor::new(FPS_NUMBERS_PNG), image::ImageFormat::Png)
|
||||
.decode()
|
||||
.unwrap();
|
||||
#[cfg(feature = "debug")]
|
||||
let fps_texture = renderer
|
||||
.import_memory(
|
||||
&fps_image.to_rgba8(),
|
||||
Fourcc::Abgr8888,
|
||||
(fps_image.width() as i32, fps_image.height() as i32).into(),
|
||||
false,
|
||||
)
|
||||
.expect("Unable to upload FPS texture");
|
||||
#[cfg(feature = "debug")]
|
||||
let mut fps_element = FpsElement::new(fps_texture);
|
||||
let output = Output::new(
|
||||
OUTPUT_NAME.to_string(),
|
||||
PhysicalProperties {
|
||||
size: (0, 0).into(),
|
||||
subpixel: Subpixel::Unknown,
|
||||
make: "Smithay".into(),
|
||||
model: "X11".into(),
|
||||
serial_number: "Unknown".into(),
|
||||
},
|
||||
);
|
||||
let _global = output.create_global::<AnvilState<X11Data>>(&display.handle());
|
||||
output.change_current_state(Some(mode), None, None, Some((0, 0).into()));
|
||||
output.set_preferred(mode);
|
||||
|
||||
let damage_tracker = OutputDamageTracker::from_output(&output);
|
||||
|
||||
let data = X11Data {
|
||||
render: true,
|
||||
mode,
|
||||
surface,
|
||||
renderer,
|
||||
damage_tracker,
|
||||
dmabuf_state,
|
||||
_dmabuf_global: dmabuf_global,
|
||||
_dmabuf_default_feedback: dmabuf_default_feedback,
|
||||
#[cfg(feature = "debug")]
|
||||
fps: fps_ticker::Fps::default(),
|
||||
};
|
||||
|
||||
let mut state = AnvilState::init(display, event_loop.handle(), data, true);
|
||||
state
|
||||
.shm_state
|
||||
.update_formats(state.backend_data.renderer.shm_formats());
|
||||
state.space.map_output(&output, (0, 0));
|
||||
|
||||
let output_clone = output.clone();
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(backend, move |event, _, data| match event {
|
||||
X11Event::CloseRequested { .. } => {
|
||||
data.running.store(false, Ordering::SeqCst);
|
||||
}
|
||||
X11Event::Resized { new_size, .. } => {
|
||||
let output = &output_clone;
|
||||
let size = { (new_size.w as i32, new_size.h as i32).into() };
|
||||
|
||||
data.backend_data.mode = Mode {
|
||||
size,
|
||||
refresh: 60_000,
|
||||
};
|
||||
output.delete_mode(output.current_mode().unwrap());
|
||||
output.change_current_state(Some(data.backend_data.mode), None, None, None);
|
||||
output.set_preferred(data.backend_data.mode);
|
||||
crate::shell::fixup_positions(&mut data.space, data.pointer.current_location());
|
||||
|
||||
data.backend_data.render = true;
|
||||
}
|
||||
X11Event::PresentCompleted { .. } | X11Event::Refresh { .. } => {
|
||||
data.backend_data.render = true;
|
||||
}
|
||||
X11Event::Input { event, .. } => data.process_input_event_windowed(event, OUTPUT_NAME),
|
||||
X11Event::Focus { focused: false, .. } => {
|
||||
data.release_all_keys();
|
||||
}
|
||||
_ => {}
|
||||
})
|
||||
.expect("Failed to insert X11 Backend into event loop");
|
||||
|
||||
#[cfg(feature = "xwayland")]
|
||||
state.start_xwayland();
|
||||
|
||||
info!("Initialization completed, starting the main loop.");
|
||||
|
||||
let mut pointer_element = PointerElement::default();
|
||||
|
||||
while state.running.load(Ordering::SeqCst) {
|
||||
if state.backend_data.render {
|
||||
profiling::scope!("render_frame");
|
||||
|
||||
let now = state.clock.now();
|
||||
let frame_target = now
|
||||
+ output
|
||||
.current_mode()
|
||||
.map(|mode| Duration::from_secs_f64(1_000f64 / mode.refresh as f64))
|
||||
.unwrap_or_default();
|
||||
state.pre_repaint(&output, frame_target);
|
||||
|
||||
let backend_data = &mut state.backend_data;
|
||||
// We need to borrow everything we want to refer to inside the renderer callback otherwise rustc is unhappy.
|
||||
#[cfg(feature = "debug")]
|
||||
let fps = backend_data.fps.avg().round() as u32;
|
||||
#[cfg(feature = "debug")]
|
||||
fps_element.update_fps(fps);
|
||||
|
||||
let (mut buffer, age) = backend_data.surface.buffer().expect("gbm device was destroyed");
|
||||
let mut fb = match backend_data.renderer.bind(&mut buffer) {
|
||||
Ok(fb) => fb,
|
||||
Err(err) => {
|
||||
error!("Error while binding buffer: {}", err);
|
||||
profiling::finish_frame!();
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
if let Some(renderdoc) = state.renderdoc.as_mut() {
|
||||
renderdoc.start_frame_capture(
|
||||
backend_data.renderer.egl_context().get_context_handle(),
|
||||
std::ptr::null(),
|
||||
);
|
||||
}
|
||||
|
||||
let mut elements: Vec<CustomRenderElements<GlesRenderer>> = Vec::new();
|
||||
|
||||
// draw the cursor as relevant
|
||||
// reset the cursor if the surface is no longer alive
|
||||
let mut reset = false;
|
||||
if let CursorImageStatus::Surface(ref surface) = state.cursor_status {
|
||||
reset = !surface.alive();
|
||||
}
|
||||
if reset {
|
||||
state.cursor_status = CursorImageStatus::default_named();
|
||||
}
|
||||
let cursor_visible = !matches!(state.cursor_status, CursorImageStatus::Surface(_));
|
||||
|
||||
let scale = Scale::from(output.current_scale().fractional_scale());
|
||||
let cursor_hotspot = if let CursorImageStatus::Surface(ref surface) = state.cursor_status {
|
||||
compositor::with_states(surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<Mutex<CursorImageAttributes>>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.hotspot
|
||||
})
|
||||
} else {
|
||||
(0, 0).into()
|
||||
};
|
||||
let cursor_pos = state.pointer.current_location();
|
||||
|
||||
pointer_element.set_status(state.cursor_status.clone());
|
||||
elements.extend(
|
||||
pointer_element.render_elements(
|
||||
&mut backend_data.renderer,
|
||||
(cursor_pos - cursor_hotspot.to_f64())
|
||||
.to_physical(scale)
|
||||
.to_i32_round(),
|
||||
scale,
|
||||
1.0,
|
||||
),
|
||||
);
|
||||
|
||||
// draw the dnd icon if any
|
||||
if let Some(icon) = state.dnd_icon.as_ref() {
|
||||
let dnd_icon_pos = (cursor_pos + icon.offset.to_f64())
|
||||
.to_physical(scale)
|
||||
.to_i32_round();
|
||||
if icon.surface.alive() {
|
||||
elements.extend(AsRenderElements::<GlesRenderer>::render_elements(
|
||||
&smithay::desktop::space::SurfaceTree::from_surface(&icon.surface),
|
||||
&mut backend_data.renderer,
|
||||
dnd_icon_pos,
|
||||
scale,
|
||||
1.0,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
elements.push(CustomRenderElements::Fps(fps_element.clone()));
|
||||
|
||||
let render_res = render_output(
|
||||
&output,
|
||||
&state.space,
|
||||
elements,
|
||||
&mut backend_data.renderer,
|
||||
&mut fb,
|
||||
&mut backend_data.damage_tracker,
|
||||
age.into(),
|
||||
state.show_window_preview,
|
||||
);
|
||||
|
||||
match render_res {
|
||||
Ok(render_output_result) => {
|
||||
trace!("Finished rendering");
|
||||
let submitted = if let Err(err) = backend_data.surface.submit() {
|
||||
backend_data.surface.reset_buffers();
|
||||
warn!("Failed to submit buffer: {}. Retrying", err);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
let states = render_output_result.states;
|
||||
|
||||
update_primary_scanout_output(
|
||||
&state.space,
|
||||
&output,
|
||||
&state.dnd_icon,
|
||||
&state.cursor_status,
|
||||
&states,
|
||||
);
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
let rendered = render_output_result.damage.is_some();
|
||||
if render_output_result.damage.is_some() {
|
||||
let mut output_presentation_feedback =
|
||||
take_presentation_feedback(&output, &state.space, &states);
|
||||
output_presentation_feedback.presented(
|
||||
frame_target,
|
||||
output
|
||||
.current_mode()
|
||||
.map(|mode| {
|
||||
Refresh::fixed(Duration::from_secs_f64(1_000f64 / mode.refresh as f64))
|
||||
})
|
||||
.unwrap_or(Refresh::Unknown),
|
||||
0,
|
||||
wp_presentation_feedback::Kind::Vsync,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
if rendered {
|
||||
if let Some(renderdoc) = state.renderdoc.as_mut() {
|
||||
renderdoc.end_frame_capture(
|
||||
state.backend_data.renderer.egl_context().get_context_handle(),
|
||||
std::ptr::null(),
|
||||
);
|
||||
}
|
||||
} else if let Some(renderdoc) = state.renderdoc.as_mut() {
|
||||
renderdoc.discard_frame_capture(
|
||||
state.backend_data.renderer.egl_context().get_context_handle(),
|
||||
std::ptr::null(),
|
||||
);
|
||||
}
|
||||
|
||||
state.backend_data.render = !submitted;
|
||||
|
||||
// Send frame events so that client start drawing their next frame
|
||||
state.post_repaint(&output, frame_target, None, &states);
|
||||
}
|
||||
Err(err) => {
|
||||
#[cfg(feature = "debug")]
|
||||
if let Some(renderdoc) = state.renderdoc.as_mut() {
|
||||
renderdoc.discard_frame_capture(
|
||||
backend_data.renderer.egl_context().get_context_handle(),
|
||||
std::ptr::null(),
|
||||
);
|
||||
}
|
||||
|
||||
backend_data.surface.reset_buffers();
|
||||
error!("Rendering error: {}", err);
|
||||
// TODO: convert RenderError into SwapBuffersError and skip temporary (will retry) and panic on ContextLost or recreate
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug")]
|
||||
state.backend_data.fps.tick();
|
||||
window.set_cursor_visible(cursor_visible);
|
||||
profiling::finish_frame!();
|
||||
}
|
||||
|
||||
let result = event_loop.dispatch(Some(Duration::from_millis(16)), &mut state);
|
||||
if result.is_err() {
|
||||
state.running.store(false, Ordering::SeqCst);
|
||||
} else {
|
||||
state.space.refresh();
|
||||
state.popups.cleanup();
|
||||
display_handle.flush_clients().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use smithay::utils::user_data::UserDataMap;
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
c.bench_function("UserDataMap::get", |b| {
|
||||
let udata_map = UserDataMap::new();
|
||||
udata_map.insert_if_missing(|| 17i32);
|
||||
b.iter(|| udata_map.get::<i32>())
|
||||
});
|
||||
c.bench_function("UserDataMap::get threadsafe", |b| {
|
||||
let udata_map = UserDataMap::new();
|
||||
udata_map.insert_if_missing_threadsafe(|| 17i32);
|
||||
b.iter(|| udata_map.get::<i32>())
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
Vendored
+50
@@ -0,0 +1,50 @@
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
use rand::RngExt;
|
||||
use smithay::utils::{Physical, Rectangle, Size};
|
||||
|
||||
fn element_visible_size(test_element: Rectangle<i32, Physical>, opaque_regions: &[Rectangle<i32, Physical>]) {
|
||||
let mut workhouse = Vec::with_capacity(2048 * 4);
|
||||
workhouse.push(test_element);
|
||||
workhouse = Rectangle::subtract_rects_many_in_place(workhouse, opaque_regions.iter().copied());
|
||||
workhouse
|
||||
.iter()
|
||||
.fold(0usize, |acc, item| acc + (item.size.w * item.size.h) as usize);
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let stage: Size<i32, Physical> = Size::from((800, 600));
|
||||
let element_size: Size<i32, Physical> = Size::from((200, 100));
|
||||
let max_x = stage.w - element_size.w;
|
||||
let max_y = stage.h - element_size.h;
|
||||
|
||||
let mut rand = rand::rng();
|
||||
let x = rand.random_range(0..max_x);
|
||||
let y = rand.random_range(0..max_y);
|
||||
let test_element = Rectangle::new((x, y).into(), element_size);
|
||||
|
||||
let x_min = (test_element.loc.x - element_size.w) + 1;
|
||||
let x_max = (test_element.loc.x + element_size.w) - 1;
|
||||
let y_min = (test_element.loc.y - element_size.h) + 1;
|
||||
let y_max = (test_element.loc.y + element_size.h) - 1;
|
||||
// let x_min = 0;
|
||||
// let x_max = stage.w - element_size.w;
|
||||
// let y_min = 0;
|
||||
// let y_max = stage.h - element_size.h;
|
||||
|
||||
let opaque_regions = (0..2048)
|
||||
.map(|_| {
|
||||
let x = rand.random_range(x_min..=x_max);
|
||||
let y = rand.random_range(y_min..=y_max);
|
||||
Rectangle::new((x, y).into(), element_size)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
c.bench_function("element_visible_size", |b| {
|
||||
b.iter(|| {
|
||||
element_visible_size(test_element, &opaque_regions);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
Vendored
+139
@@ -0,0 +1,139 @@
|
||||
#![allow(clippy::disallowed_macros)]
|
||||
#[cfg(any(feature = "backend_egl", feature = "renderer_gl"))]
|
||||
fn gl_generate() {
|
||||
use gl_generator::{Api, Fallbacks, Profile, Registry};
|
||||
use std::{env, fs::File, path::PathBuf};
|
||||
|
||||
let dest = PathBuf::from(&env::var("OUT_DIR").unwrap());
|
||||
|
||||
if env::var_os("CARGO_FEATURE_BACKEND_EGL").is_some() {
|
||||
let mut file = File::create(dest.join("egl_bindings.rs")).unwrap();
|
||||
Registry::new(
|
||||
Api::Egl,
|
||||
(1, 5),
|
||||
Profile::Core,
|
||||
Fallbacks::All,
|
||||
[
|
||||
"EGL_KHR_create_context",
|
||||
"EGL_EXT_create_context_robustness",
|
||||
"EGL_KHR_create_context_no_error",
|
||||
"EGL_KHR_no_config_context",
|
||||
"EGL_EXT_pixel_format_float",
|
||||
"EGL_EXT_device_base",
|
||||
"EGL_EXT_device_enumeration",
|
||||
"EGL_EXT_device_query",
|
||||
"EGL_EXT_device_drm",
|
||||
"EGL_EXT_device_drm_render_node",
|
||||
"EGL_KHR_stream",
|
||||
"EGL_KHR_stream_producer_eglsurface",
|
||||
"EGL_EXT_platform_base",
|
||||
"EGL_KHR_platform_x11",
|
||||
"EGL_EXT_platform_x11",
|
||||
"EGL_KHR_platform_wayland",
|
||||
"EGL_EXT_platform_wayland",
|
||||
"EGL_KHR_platform_gbm",
|
||||
"EGL_MESA_platform_gbm",
|
||||
"EGL_MESA_platform_surfaceless",
|
||||
"EGL_EXT_platform_device",
|
||||
"EGL_WL_bind_wayland_display",
|
||||
"EGL_KHR_image_base",
|
||||
"EGL_EXT_image_dma_buf_import",
|
||||
"EGL_EXT_image_dma_buf_import_modifiers",
|
||||
"EGL_MESA_image_dma_buf_export",
|
||||
"EGL_KHR_gl_image",
|
||||
"EGL_EXT_buffer_age",
|
||||
"EGL_EXT_swap_buffers_with_damage",
|
||||
"EGL_KHR_swap_buffers_with_damage",
|
||||
"EGL_KHR_fence_sync",
|
||||
"EGL_ANDROID_native_fence_sync",
|
||||
"EGL_IMG_context_priority",
|
||||
],
|
||||
)
|
||||
.write_bindings(gl_generator::GlobalGenerator, &mut file)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
if env::var_os("CARGO_FEATURE_RENDERER_GL").is_some() {
|
||||
let mut file = File::create(dest.join("gl_bindings.rs")).unwrap();
|
||||
Registry::new(
|
||||
Api::Gles2,
|
||||
(3, 2),
|
||||
Profile::Compatibility,
|
||||
Fallbacks::None,
|
||||
[
|
||||
"GL_OES_EGL_image",
|
||||
"GL_OES_EGL_image_external",
|
||||
"GL_EXT_texture_format_BGRA8888",
|
||||
"GL_EXT_unpack_subimage",
|
||||
"GL_OES_EGL_sync",
|
||||
"GL_EXT_disjoint_timer_query",
|
||||
],
|
||||
)
|
||||
.write_bindings(gl_generator::StructGenerator, &mut file)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "backend_gbm", not(feature = "backend_gbm_has_fd_for_plane")))]
|
||||
fn test_gbm_bo_fd_for_plane() {
|
||||
let gbm = match pkg_config::probe_library("gbm") {
|
||||
Ok(lib) => lib,
|
||||
Err(_) => {
|
||||
println!("cargo:warning=failed to find gbm, assuming gbm_bo_get_fd_for_plane is unavailable");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let has_gbm_bo_get_fd_for_plane = cc::Build::new()
|
||||
.file("test_gbm_bo_get_fd_for_plane.c")
|
||||
.includes(gbm.include_paths)
|
||||
.warnings_into_errors(true)
|
||||
.cargo_metadata(false)
|
||||
.try_compile("test_gbm_bo_get_fd_for_plane")
|
||||
.is_ok();
|
||||
|
||||
if has_gbm_bo_get_fd_for_plane {
|
||||
println!("cargo:rustc-cfg=feature=\"backend_gbm_has_fd_for_plane\"");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(
|
||||
feature = "backend_gbm",
|
||||
not(feature = "backend_gbm_has_create_with_modifiers2")
|
||||
))]
|
||||
fn test_gbm_bo_create_with_modifiers2() {
|
||||
let gbm = match pkg_config::probe_library("gbm") {
|
||||
Ok(lib) => lib,
|
||||
Err(_) => {
|
||||
println!(
|
||||
"cargo:warning=failed to find gbm, assuming gbm_bo_create_with_modifiers2 is unavailable"
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let has_gbm_bo_create_with_modifiers2 = cc::Build::new()
|
||||
.file("test_gbm_bo_create_with_modifiers2.c")
|
||||
.includes(gbm.include_paths)
|
||||
.warnings_into_errors(true)
|
||||
.cargo_metadata(false)
|
||||
.try_compile("test_gbm_bo_create_with_modifiers2")
|
||||
.is_ok();
|
||||
|
||||
if has_gbm_bo_create_with_modifiers2 {
|
||||
println!("cargo:rustc-cfg=feature=\"backend_gbm_has_create_with_modifiers2\"");
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
#[cfg(any(feature = "backend_egl", feature = "renderer_gl"))]
|
||||
gl_generate();
|
||||
|
||||
#[cfg(all(feature = "backend_gbm", not(feature = "backend_gbm_has_fd_for_plane")))]
|
||||
test_gbm_bo_fd_for_plane();
|
||||
#[cfg(all(
|
||||
feature = "backend_gbm",
|
||||
not(feature = "backend_gbm_has_create_with_modifiers2")
|
||||
))]
|
||||
test_gbm_bo_create_with_modifiers2();
|
||||
}
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
msrv = "1.87"
|
||||
type-complexity-threshold = 400
|
||||
|
||||
disallowed-macros = [
|
||||
{ path = "std::print", reason = "We use tracing for logging" },
|
||||
{ path = "std::println", reason = "We use tracing for logging" },
|
||||
{ path = "std::eprint", reason = "We use tracing for logging" },
|
||||
{ path = "std::eprintln", reason = "We use tracing for logging" },
|
||||
]
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
#!/bin/sh
|
||||
|
||||
WLCS_SHA=2b385328366de5db19521b9b3eef81861430d984
|
||||
|
||||
if [ -f "./wlcs/wlcs" ] && [ "$(cd wlcs; git rev-parse HEAD)" = "${WLCS_SHA}" ] ; then
|
||||
echo "Using cached WLCS."
|
||||
else
|
||||
echo "Compiling WLCS."
|
||||
git clone https://github.com/MirServer/wlcs.git
|
||||
cd wlcs || exit
|
||||
# checkout a specific revision
|
||||
git reset --hard "${WLCS_SHA}"
|
||||
cmake -DWLCS_BUILD_ASAN=False -DWLCS_BUILD_TSAN=False -DWLCS_BUILD_UBSAN=False -DCMAKE_EXPORT_COMPILE_COMMANDS=1 .
|
||||
make
|
||||
fi
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv=refresh content=0;url=smithay/index.html />
|
||||
</head>
|
||||
</html>
|
||||
+501
@@ -0,0 +1,501 @@
|
||||
use std::{fmt, fs::File, os::unix::io::OwnedFd, path::PathBuf};
|
||||
|
||||
use clap::{Args, Parser, Subcommand, ValueEnum};
|
||||
use smithay::{
|
||||
backend::{
|
||||
allocator::{
|
||||
Allocator, Fourcc, Modifier,
|
||||
dmabuf::{AnyError, Dmabuf, DmabufAllocator},
|
||||
dumb::DumbAllocator,
|
||||
gbm::{GbmAllocator, GbmBufferFlags, GbmDevice},
|
||||
vulkan::{ImageUsageFlags, VulkanAllocator},
|
||||
},
|
||||
drm::{DrmDeviceFd, DrmNode},
|
||||
egl::{EGLContext, EGLDevice, EGLDisplay},
|
||||
renderer::{
|
||||
Bind, Color32F, ExportMem, Frame, ImportDma, Offscreen, Renderer,
|
||||
gles::{GlesRenderbuffer, GlesRenderer},
|
||||
},
|
||||
vulkan::{Instance, PhysicalDevice, version::Version},
|
||||
},
|
||||
utils::{DeviceFd, Rectangle, Transform},
|
||||
};
|
||||
use tracing::info;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
subcommand: Subcommands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Subcommands {
|
||||
/// Intersect formats to find usable combinations
|
||||
#[command(arg_required_else_help = true)]
|
||||
Formats {
|
||||
/// Devices that should be able to render a format
|
||||
#[arg(short, long)]
|
||||
render: Vec<String>,
|
||||
/// Devices that should be able to sample from a format
|
||||
#[arg(short, long)]
|
||||
sample: Vec<String>,
|
||||
},
|
||||
/// Test exporting and importing buffers with various apis and devices
|
||||
Test(TestArgs),
|
||||
}
|
||||
|
||||
#[derive(Args, Debug)]
|
||||
struct TestArgs {
|
||||
/// Allocator used for creating the buffer
|
||||
#[arg(short, long)]
|
||||
allocator: AllocatorType,
|
||||
/// Usage flags for allocating the image, when using the vulkan api
|
||||
#[arg(short, long, value_parser = usage_flags_from_string, required_if_eq("allocator", "vulkan"))]
|
||||
usage_flags: Option<ImageUsageFlags>,
|
||||
|
||||
/// Exporting device node
|
||||
#[arg(short, long, required_if_eq_any([("allocator", "dumb"), ("allocator", "gbm"), ("allocator", "vulkan")]))]
|
||||
export: Option<String>,
|
||||
/// Importing device node (may be equal to export)
|
||||
#[arg(short, long)]
|
||||
import: String,
|
||||
/// Renderer used to draw into buffer before exporting
|
||||
#[arg(short = 'r', long)]
|
||||
export_renderer: Option<RendererType>,
|
||||
#[arg(short = 't', long)]
|
||||
import_renderer: Option<RendererType>,
|
||||
#[arg(short, long, requires("export_renderer"), requires("import_renderer"))]
|
||||
dump: Option<String>,
|
||||
|
||||
#[arg(long, default_value_t = 256)]
|
||||
width: u32,
|
||||
#[arg(long, default_value_t = 256)]
|
||||
height: u32,
|
||||
#[arg(short, long, default_value = "Abgr8888", value_parser = fourcc_from_string)]
|
||||
fourcc: Fourcc,
|
||||
#[arg(short, long, default_value = "Linear", value_parser = modifier_from_string)]
|
||||
modifier: Modifier,
|
||||
}
|
||||
|
||||
#[derive(ValueEnum, Debug, Clone, Copy)]
|
||||
pub enum AllocatorType {
|
||||
#[value(name = "dumb")]
|
||||
DumbBuffer,
|
||||
#[value(name = "gbm")]
|
||||
Gbm,
|
||||
#[value(name = "vulkan")]
|
||||
Vulkan,
|
||||
}
|
||||
|
||||
#[derive(ValueEnum, Debug, Clone, Copy)]
|
||||
pub enum RendererType {
|
||||
#[value(name = "gles")]
|
||||
Gles,
|
||||
}
|
||||
|
||||
impl fmt::Display for RendererType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("Gles")
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = Cli::parse();
|
||||
|
||||
if let Ok(env_filter) = tracing_subscriber::EnvFilter::try_from_default_env() {
|
||||
tracing_subscriber::fmt().with_env_filter(env_filter).init();
|
||||
} else {
|
||||
tracing_subscriber::fmt().init();
|
||||
}
|
||||
|
||||
match args.subcommand {
|
||||
Subcommands::Formats { render, sample } => format_test(render, sample),
|
||||
Subcommands::Test(args) => buffer_test(args),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_test(render: Vec<String>, sample: Vec<String>) {
|
||||
for format in render
|
||||
.iter()
|
||||
.map(|path| {
|
||||
let path = PathBuf::from(&path);
|
||||
let device = EGLDevice::enumerate()
|
||||
.expect("Failed to enumerate EGL devices")
|
||||
.find(|device| {
|
||||
device.drm_device_path().ok().as_ref() == Some(&path)
|
||||
|| device.render_device_path().ok().as_ref() == Some(&path)
|
||||
})
|
||||
.expect("Unable to find egl device");
|
||||
let display = unsafe { EGLDisplay::new(device).expect("Failed to create EGL display") };
|
||||
display.dmabuf_render_formats().clone()
|
||||
})
|
||||
.chain(sample.iter().map(|path| {
|
||||
let path = PathBuf::from(&path);
|
||||
let device = EGLDevice::enumerate()
|
||||
.expect("Failed to enumerate EGL devices")
|
||||
.find(|device| {
|
||||
device.drm_device_path().ok().as_ref() == Some(&path)
|
||||
|| device.render_device_path().ok().as_ref() == Some(&path)
|
||||
})
|
||||
.expect("Unable to find egl device");
|
||||
let display = unsafe { EGLDisplay::new(device).expect("Failed to create EGL display") };
|
||||
display.dmabuf_texture_formats().clone()
|
||||
}))
|
||||
.fold(None, |set, formats| match set {
|
||||
None => Some(formats),
|
||||
Some(set) => Some(set.intersection(&formats).copied().collect()),
|
||||
})
|
||||
.unwrap_or_default()
|
||||
{
|
||||
println!(
|
||||
"Format: Fourcc {} / Modifier {}",
|
||||
format.code,
|
||||
Into::<u64>::into(format.modifier)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn open_device(path: &str) -> DrmDeviceFd {
|
||||
let file = File::options()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(path)
|
||||
.expect("Failed to open device node");
|
||||
DrmDeviceFd::new(DeviceFd::from(Into::<OwnedFd>::into(file)))
|
||||
}
|
||||
|
||||
fn buffer_test(args: TestArgs) {
|
||||
// 1. create allocator
|
||||
let path = args
|
||||
.export
|
||||
.expect("Dumb buffer allocator requires an export device");
|
||||
info!("Export device: {}", path);
|
||||
let mut allocator = match args.allocator {
|
||||
AllocatorType::DumbBuffer => {
|
||||
let fd = open_device(&path);
|
||||
let dumb_allocator = DumbAllocator::new(fd);
|
||||
Box::new(DmabufAllocator(dumb_allocator)) as Box<dyn Allocator<Buffer = Dmabuf, Error = AnyError>>
|
||||
}
|
||||
AllocatorType::Gbm => {
|
||||
let fd = open_device(&path);
|
||||
let gbm = GbmDevice::new(fd).expect("Failed to init gbm device");
|
||||
let gbm_allocator = GbmAllocator::new(gbm, GbmBufferFlags::RENDERING);
|
||||
Box::new(DmabufAllocator(gbm_allocator)) as Box<_>
|
||||
}
|
||||
AllocatorType::Vulkan => {
|
||||
let node = DrmNode::from_path(&path).expect("Failed to find drm node");
|
||||
let instance =
|
||||
Instance::new(Version::VERSION_1_2, None).expect("Unable to create vulkan instance");
|
||||
let physical_device = PhysicalDevice::enumerate(&instance)
|
||||
.expect("Failed to enumerate physical devices")
|
||||
.filter(|phd| phd.has_device_extension(c"VK_EXT_physical_device_drm"))
|
||||
.find(|phd| {
|
||||
phd.primary_node().unwrap() == Some(node) || phd.render_node().unwrap() == Some(node)
|
||||
})
|
||||
.expect("Unable to find physical device");
|
||||
Box::new(DmabufAllocator(
|
||||
VulkanAllocator::new(&physical_device, args.usage_flags.unwrap())
|
||||
.expect("Failed to create vulkan allocator"),
|
||||
)) as Box<_>
|
||||
}
|
||||
};
|
||||
|
||||
// 2. alloc buffer
|
||||
let mut buffer = allocator
|
||||
.create_buffer(args.width, args.height, args.fourcc, &[args.modifier])
|
||||
.expect("Failed to allocate buffer");
|
||||
|
||||
// 3. render into buffer on src device
|
||||
match args.export_renderer {
|
||||
None => {}
|
||||
Some(RendererType::Gles) => {
|
||||
let path = PathBuf::from(&path);
|
||||
let device = EGLDevice::enumerate()
|
||||
.expect("Failed to enumerate EGL devices")
|
||||
.find(|device| {
|
||||
device.drm_device_path().ok().as_ref() == Some(&path)
|
||||
|| device.render_device_path().ok().as_ref() == Some(&path)
|
||||
})
|
||||
.expect("Unable to find egl device");
|
||||
let display = unsafe { EGLDisplay::new(device).expect("Failed to create EGL display") };
|
||||
|
||||
let context = EGLContext::new(&display).expect("Failed to create EGL context");
|
||||
let mut renderer = unsafe { GlesRenderer::new(context).expect("Failed to init GL ES renderer") };
|
||||
|
||||
render_into(&mut renderer, &mut buffer, args.width as i32, args.height as i32);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. import / render buffer on dst device
|
||||
let path = PathBuf::from(args.import);
|
||||
info!("Import device: {}", path.display());
|
||||
match args.import_renderer {
|
||||
None => {
|
||||
let device = EGLDevice::enumerate()
|
||||
.expect("Failed to enumerate EGL devices")
|
||||
.find(|device| {
|
||||
device.drm_device_path().ok().as_ref() == Some(&path)
|
||||
|| device.render_device_path().ok().as_ref() == Some(&path)
|
||||
})
|
||||
.expect("Unable to find egl device");
|
||||
let display = unsafe { EGLDisplay::new(device).expect("Failed to create EGL display") };
|
||||
|
||||
display
|
||||
.create_image_from_dmabuf(&buffer)
|
||||
.expect("Failed to import dmabuf");
|
||||
}
|
||||
Some(RendererType::Gles) => {
|
||||
let device = EGLDevice::enumerate()
|
||||
.expect("Failed to enumerate EGL devices")
|
||||
.find(|device| {
|
||||
device.drm_device_path().ok().as_ref() == Some(&path)
|
||||
|| device.render_device_path().ok().as_ref() == Some(&path)
|
||||
})
|
||||
.expect("Unable to find egl device");
|
||||
let display = unsafe { EGLDisplay::new(device).expect("Failed to create EGL display") };
|
||||
|
||||
let context = EGLContext::new(&display).expect("Failed to create EGL context");
|
||||
let mut renderer = unsafe { GlesRenderer::new(context).expect("Failed to init GL ES renderer") };
|
||||
|
||||
render_from::<_, GlesRenderbuffer>(
|
||||
&mut renderer,
|
||||
buffer,
|
||||
args.width as i32,
|
||||
args.height as i32,
|
||||
args.dump,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_into<R, T>(renderer: &mut R, buffer: &mut T, w: i32, h: i32)
|
||||
where
|
||||
R: Renderer + Bind<T>,
|
||||
{
|
||||
// Bind it as a framebuffer
|
||||
let mut framebuffer = renderer.bind(buffer).expect("Failed to bind dmabuf");
|
||||
|
||||
let mut frame = renderer
|
||||
.render(&mut framebuffer, (w, h).into(), Transform::Normal)
|
||||
.expect("Failed to create render frame");
|
||||
frame
|
||||
.clear(
|
||||
Color32F::new(1.0, 0.0, 0.0, 1.0),
|
||||
&[Rectangle::from_size((w / 2, h / 2).into())],
|
||||
)
|
||||
.expect("Render error");
|
||||
frame
|
||||
.clear(
|
||||
Color32F::new(0.0, 1.0, 0.0, 1.0),
|
||||
&[Rectangle::new((w / 2, 0).into(), (w / 2, h / 2).into())],
|
||||
)
|
||||
.expect("Render error");
|
||||
frame
|
||||
.clear(
|
||||
Color32F::new(0.0, 0.0, 1.0, 1.0),
|
||||
&[Rectangle::new((0, h / 2).into(), (w / 2, h / 2).into())],
|
||||
)
|
||||
.expect("Render error");
|
||||
frame
|
||||
.clear(
|
||||
Color32F::new(1.0, 1.0, 0.0, 1.0),
|
||||
&[Rectangle::new((w / 2, h / 2).into(), (w / 2, h / 2).into())],
|
||||
)
|
||||
.expect("Render error");
|
||||
frame
|
||||
.finish()
|
||||
.expect("Failed to finish render frame")
|
||||
.wait()
|
||||
.expect("Synchronization error");
|
||||
}
|
||||
|
||||
fn render_from<R, T>(renderer: &mut R, buffer: Dmabuf, w: i32, h: i32, dump: Option<String>)
|
||||
where
|
||||
R: Renderer + ImportDma + ExportMem + Offscreen<T>,
|
||||
{
|
||||
let texture = renderer
|
||||
.import_dmabuf(&buffer, None)
|
||||
.expect("Failed to import dmabuf");
|
||||
let mut offscreen = Offscreen::<T>::create_buffer(renderer, Fourcc::Abgr8888, (w, h).into())
|
||||
.expect("Failed to create offscreen buffer");
|
||||
let mut framebuffer = renderer
|
||||
.bind(&mut offscreen)
|
||||
.expect("Failed to bind offscreen buffer");
|
||||
let mut frame = renderer
|
||||
.render(&mut framebuffer, (w, h).into(), Transform::Normal)
|
||||
.expect("Failed to create render frame");
|
||||
frame
|
||||
.render_texture_at(
|
||||
&texture,
|
||||
(0, 0).into(),
|
||||
1,
|
||||
1.,
|
||||
Transform::Normal,
|
||||
&[Rectangle::from_size((w, h).into())],
|
||||
&[],
|
||||
1.0,
|
||||
)
|
||||
.expect("Failed to sample dmabuf");
|
||||
frame
|
||||
.finish()
|
||||
.expect("Failed to finish render frame")
|
||||
.wait()
|
||||
.expect("Synchronization error");
|
||||
|
||||
if let Some(path) = dump {
|
||||
let mapping = renderer
|
||||
.copy_framebuffer(
|
||||
&framebuffer,
|
||||
Rectangle::from_size((w, h).into()),
|
||||
Fourcc::Abgr8888,
|
||||
)
|
||||
.expect("Failed to map framebuffer");
|
||||
let copy = renderer.map_texture(&mapping).expect("Failed to read mapping");
|
||||
image::save_buffer(path, copy, w as u32, h as u32, image::ColorType::Rgba8)
|
||||
.expect("Failed to save image");
|
||||
}
|
||||
}
|
||||
|
||||
fn fourcc_from_string(name: &str) -> Result<Fourcc, &'static str> {
|
||||
Ok(match name {
|
||||
"Abgr1555" => Fourcc::Abgr1555,
|
||||
"Abgr16161616f" => Fourcc::Abgr16161616f,
|
||||
"Abgr2101010" => Fourcc::Abgr2101010,
|
||||
"Abgr4444" => Fourcc::Abgr4444,
|
||||
"Abgr8888" => Fourcc::Abgr8888,
|
||||
"Argb1555" => Fourcc::Argb1555,
|
||||
"Argb16161616f" => Fourcc::Argb16161616f,
|
||||
"Argb2101010" => Fourcc::Argb2101010,
|
||||
"Argb4444" => Fourcc::Argb4444,
|
||||
"Argb8888" => Fourcc::Argb8888,
|
||||
"Axbxgxrx106106106106" => Fourcc::Axbxgxrx106106106106,
|
||||
"Ayuv" => Fourcc::Ayuv,
|
||||
"Bgr233" => Fourcc::Bgr233,
|
||||
"Bgr565" => Fourcc::Bgr565,
|
||||
"Bgr565_a8" => Fourcc::Bgr565_a8,
|
||||
"Bgr888" => Fourcc::Bgr888,
|
||||
"Bgr888_a8" => Fourcc::Bgr888_a8,
|
||||
"Bgra1010102" => Fourcc::Bgra1010102,
|
||||
"Bgra4444" => Fourcc::Bgra4444,
|
||||
"Bgra5551" => Fourcc::Bgra5551,
|
||||
"Bgra8888" => Fourcc::Bgra8888,
|
||||
"Bgrx1010102" => Fourcc::Bgrx1010102,
|
||||
"Bgrx4444" => Fourcc::Bgrx4444,
|
||||
"Bgrx5551" => Fourcc::Bgrx5551,
|
||||
"Bgrx8888" => Fourcc::Bgrx8888,
|
||||
"Bgrx8888_a8" => Fourcc::Bgrx8888_a8,
|
||||
"Big_endian" => Fourcc::Big_endian,
|
||||
"C8" => Fourcc::C8,
|
||||
"Gr1616" => Fourcc::Gr1616,
|
||||
"Gr88" => Fourcc::Gr88,
|
||||
"Nv12" => Fourcc::Nv12,
|
||||
"Nv15" => Fourcc::Nv15,
|
||||
"Nv16" => Fourcc::Nv16,
|
||||
"Nv21" => Fourcc::Nv21,
|
||||
"Nv24" => Fourcc::Nv24,
|
||||
"Nv42" => Fourcc::Nv42,
|
||||
"Nv61" => Fourcc::Nv61,
|
||||
"P010" => Fourcc::P010,
|
||||
"P012" => Fourcc::P012,
|
||||
"P016" => Fourcc::P016,
|
||||
"P210" => Fourcc::P210,
|
||||
"Q401" => Fourcc::Q401,
|
||||
"Q410" => Fourcc::Q410,
|
||||
"R16" => Fourcc::R16,
|
||||
"R8" => Fourcc::R8,
|
||||
"Rg1616" => Fourcc::Rg1616,
|
||||
"Rg88" => Fourcc::Rg88,
|
||||
"Rgb332" => Fourcc::Rgb332,
|
||||
"Rgb565" => Fourcc::Rgb565,
|
||||
"Rgb565_a8" => Fourcc::Rgb565_a8,
|
||||
"Rgb888" => Fourcc::Rgb888,
|
||||
"Rgb888_a8" => Fourcc::Rgb888_a8,
|
||||
"Rgba1010102" => Fourcc::Rgba1010102,
|
||||
"Rgba4444" => Fourcc::Rgba4444,
|
||||
"Rgba5551" => Fourcc::Rgba5551,
|
||||
"Rgba8888" => Fourcc::Rgba8888,
|
||||
"Rgbx1010102" => Fourcc::Rgbx1010102,
|
||||
"Rgbx4444" => Fourcc::Rgbx4444,
|
||||
"Rgbx5551" => Fourcc::Rgbx5551,
|
||||
"Rgbx8888" => Fourcc::Rgbx8888,
|
||||
"Rgbx8888_a8" => Fourcc::Rgbx8888_a8,
|
||||
"Uyvy" => Fourcc::Uyvy,
|
||||
"Vuy101010" => Fourcc::Vuy101010,
|
||||
"Vuy888" => Fourcc::Vuy888,
|
||||
"Vyuy" => Fourcc::Vyuy,
|
||||
"X0l0" => Fourcc::X0l0,
|
||||
"X0l2" => Fourcc::X0l2,
|
||||
"Xbgr1555" => Fourcc::Xbgr1555,
|
||||
"Xbgr16161616f" => Fourcc::Xbgr16161616f,
|
||||
"Xbgr2101010" => Fourcc::Xbgr2101010,
|
||||
"Xbgr4444" => Fourcc::Xbgr4444,
|
||||
"Xbgr8888" => Fourcc::Xbgr8888,
|
||||
"Xbgr8888_a8" => Fourcc::Xbgr8888_a8,
|
||||
"Xrgb1555" => Fourcc::Xrgb1555,
|
||||
"Xrgb16161616f" => Fourcc::Xrgb16161616f,
|
||||
"Xrgb2101010" => Fourcc::Xrgb2101010,
|
||||
"Xrgb4444" => Fourcc::Xrgb4444,
|
||||
"Xrgb8888" => Fourcc::Xrgb8888,
|
||||
"Xrgb8888_a8" => Fourcc::Xrgb8888_a8,
|
||||
"Xvyu12_16161616" => Fourcc::Xvyu12_16161616,
|
||||
"Xvyu16161616" => Fourcc::Xvyu16161616,
|
||||
"Xvyu2101010" => Fourcc::Xvyu2101010,
|
||||
"Xyuv8888" => Fourcc::Xyuv8888,
|
||||
"Y0l0" => Fourcc::Y0l0,
|
||||
"Y0l2" => Fourcc::Y0l2,
|
||||
"Y210" => Fourcc::Y210,
|
||||
"Y212" => Fourcc::Y212,
|
||||
"Y216" => Fourcc::Y216,
|
||||
"Y410" => Fourcc::Y410,
|
||||
"Y412" => Fourcc::Y412,
|
||||
"Y416" => Fourcc::Y416,
|
||||
"Yuv410" => Fourcc::Yuv410,
|
||||
"Yuv411" => Fourcc::Yuv411,
|
||||
"Yuv420" => Fourcc::Yuv420,
|
||||
"Yuv420_10bit" => Fourcc::Yuv420_10bit,
|
||||
"Yuv420_8bit" => Fourcc::Yuv420_8bit,
|
||||
"Yuv422" => Fourcc::Yuv422,
|
||||
"Yuv444" => Fourcc::Yuv444,
|
||||
"Yuyv" => Fourcc::Yuyv,
|
||||
"Yvu410" => Fourcc::Yvu410,
|
||||
"Yvu411" => Fourcc::Yvu411,
|
||||
"Yvu420" => Fourcc::Yvu420,
|
||||
"Yvu422" => Fourcc::Yvu422,
|
||||
"Yvu444" => Fourcc::Yvu444,
|
||||
"Yvyu" => Fourcc::Yvyu,
|
||||
_ => {
|
||||
return Err("Unknown pixel format");
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn modifier_from_string(modifier: &str) -> Result<Modifier, std::num::ParseIntError> {
|
||||
Ok(match modifier {
|
||||
"Invalid" => Modifier::Invalid,
|
||||
"Linear" => Modifier::Linear,
|
||||
x => Modifier::from(str::parse::<u64>(x)?),
|
||||
})
|
||||
}
|
||||
|
||||
fn usage_flags_from_string(flags: &str) -> Result<ImageUsageFlags, String> {
|
||||
let flags = flags
|
||||
.split('+')
|
||||
.map(|f| {
|
||||
Ok(match f.to_lowercase().trim() {
|
||||
"all" => ImageUsageFlags::all(),
|
||||
"empty" | "none" => ImageUsageFlags::empty(),
|
||||
"color_attachment" => ImageUsageFlags::COLOR_ATTACHMENT,
|
||||
"sampled" => ImageUsageFlags::SAMPLED,
|
||||
"transfer_src" => ImageUsageFlags::TRANSFER_SRC,
|
||||
"transfer_dst" => ImageUsageFlags::TRANSFER_DST,
|
||||
x => {
|
||||
return Err(format!("Unknown Usage Flag: {}", x));
|
||||
}
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<ImageUsageFlags>, String>>()?;
|
||||
Ok(flags
|
||||
.into_iter()
|
||||
.fold(ImageUsageFlags::empty(), |akk, val| akk | val))
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use smithay::delegate_dispatch2;
|
||||
use smithay::reexports::wayland_server::Display;
|
||||
|
||||
use smithay::wayland::compositor::{CompositorClientState, CompositorHandler, CompositorState};
|
||||
|
||||
use wayland_server::backend::{ClientData, ClientId, DisconnectReason};
|
||||
use wayland_server::protocol::wl_surface::WlSurface;
|
||||
use wayland_server::{Client, ListeningSocket};
|
||||
|
||||
struct App {
|
||||
compositor_state: CompositorState,
|
||||
}
|
||||
|
||||
impl CompositorHandler for App {
|
||||
fn compositor_state(&mut self) -> &mut CompositorState {
|
||||
&mut self.compositor_state
|
||||
}
|
||||
|
||||
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
|
||||
&client.get_data::<ClientState>().unwrap().compositor_state
|
||||
}
|
||||
|
||||
fn commit(&mut self, surface: &WlSurface) {
|
||||
dbg!("Commit", surface);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut display: Display<App> = Display::new()?;
|
||||
let dh = display.handle();
|
||||
|
||||
let compositor_state = CompositorState::new::<App>(&dh);
|
||||
|
||||
let mut state = App { compositor_state };
|
||||
|
||||
let listener = ListeningSocket::bind("wayland-5").unwrap();
|
||||
|
||||
let mut clients = Vec::new();
|
||||
|
||||
loop {
|
||||
if let Some(stream) = listener.accept().unwrap() {
|
||||
println!("Got a client: {:?}", stream);
|
||||
|
||||
let client = display
|
||||
.handle()
|
||||
.insert_client(stream, Arc::new(ClientState::default()))
|
||||
.unwrap();
|
||||
clients.push(client);
|
||||
}
|
||||
|
||||
display.dispatch_clients(&mut state)?;
|
||||
display.flush_clients()?;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ClientState {
|
||||
compositor_state: CompositorClientState,
|
||||
}
|
||||
impl ClientData for ClientState {
|
||||
fn initialized(&self, _client_id: ClientId) {
|
||||
println!("initialized");
|
||||
}
|
||||
|
||||
fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {
|
||||
println!("disconnected");
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<CompositorState> for App {
|
||||
fn as_mut(&mut self) -> &mut CompositorState {
|
||||
&mut self.compositor_state
|
||||
}
|
||||
}
|
||||
|
||||
delegate_dispatch2!(App);
|
||||
Vendored
+295
@@ -0,0 +1,295 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use ::winit::event_loop::pump_events::PumpStatus;
|
||||
use smithay::{
|
||||
backend::{
|
||||
input::{InputEvent, InputTime, KeyboardKeyEvent},
|
||||
renderer::{
|
||||
Color32F, Frame, Renderer,
|
||||
element::{
|
||||
Kind,
|
||||
surface::{WaylandSurfaceRenderElement, render_elements_from_surface_tree},
|
||||
},
|
||||
gles::GlesRenderer,
|
||||
utils::{draw_render_elements, on_commit_buffer_handler},
|
||||
},
|
||||
winit::{self, WinitEvent},
|
||||
},
|
||||
input::{Seat, SeatHandler, SeatState, keyboard::FilterResult},
|
||||
reexports::wayland_server::{Display, protocol::wl_seat},
|
||||
utils::{Rectangle, Serial, Transform},
|
||||
wayland::{
|
||||
buffer::BufferHandler,
|
||||
compositor::{
|
||||
CompositorClientState, CompositorHandler, CompositorState, SurfaceAttributes, TraversalAction,
|
||||
with_surface_tree_downward,
|
||||
},
|
||||
pointer_constraints::PointerConstraintsHandler,
|
||||
selection::{
|
||||
SelectionHandler,
|
||||
data_device::{DataDeviceHandler, DataDeviceState, WaylandDndGrabHandler},
|
||||
},
|
||||
shell::xdg::{PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState},
|
||||
shm::{ShmHandler, ShmState},
|
||||
},
|
||||
};
|
||||
use wayland_protocols::xdg::shell::server::xdg_toplevel;
|
||||
use wayland_server::{
|
||||
Client, ListeningSocket,
|
||||
backend::{ClientData, ClientId, DisconnectReason},
|
||||
protocol::{
|
||||
wl_buffer,
|
||||
wl_surface::{self, WlSurface},
|
||||
},
|
||||
};
|
||||
|
||||
impl BufferHandler for App {
|
||||
fn buffer_destroyed(&mut self, _buffer: &wl_buffer::WlBuffer) {}
|
||||
}
|
||||
|
||||
impl XdgShellHandler for App {
|
||||
fn xdg_shell_state(&mut self) -> &mut XdgShellState {
|
||||
&mut self.xdg_shell_state
|
||||
}
|
||||
|
||||
fn new_toplevel(&mut self, surface: ToplevelSurface) {
|
||||
surface.with_pending_state(|state| {
|
||||
state.states.set(xdg_toplevel::State::Activated);
|
||||
});
|
||||
surface.send_configure();
|
||||
}
|
||||
|
||||
fn new_popup(&mut self, _surface: PopupSurface, _positioner: PositionerState) {
|
||||
// Handle popup creation here
|
||||
}
|
||||
|
||||
fn grab(&mut self, _surface: PopupSurface, _seat: wl_seat::WlSeat, _serial: Serial) {
|
||||
// Handle popup grab here
|
||||
}
|
||||
|
||||
fn reposition_request(&mut self, _surface: PopupSurface, _positioner: PositionerState, _token: u32) {
|
||||
// Handle popup reposition here
|
||||
}
|
||||
}
|
||||
|
||||
impl SelectionHandler for App {
|
||||
type SelectionUserData = ();
|
||||
}
|
||||
|
||||
impl DataDeviceHandler for App {
|
||||
fn data_device_state(&mut self) -> &mut DataDeviceState {
|
||||
&mut self.data_device_state
|
||||
}
|
||||
}
|
||||
|
||||
impl WaylandDndGrabHandler for App {}
|
||||
|
||||
impl CompositorHandler for App {
|
||||
fn compositor_state(&mut self) -> &mut CompositorState {
|
||||
&mut self.compositor_state
|
||||
}
|
||||
|
||||
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
|
||||
&client.get_data::<ClientState>().unwrap().compositor_state
|
||||
}
|
||||
|
||||
fn commit(&mut self, surface: &WlSurface) {
|
||||
on_commit_buffer_handler::<Self>(surface);
|
||||
}
|
||||
}
|
||||
|
||||
impl ShmHandler for App {
|
||||
fn shm_state(&self) -> &ShmState {
|
||||
&self.shm_state
|
||||
}
|
||||
}
|
||||
|
||||
impl SeatHandler for App {
|
||||
type KeyboardFocus = WlSurface;
|
||||
type PointerFocus = WlSurface;
|
||||
type TouchFocus = WlSurface;
|
||||
|
||||
fn seat_state(&mut self) -> &mut SeatState<Self> {
|
||||
&mut self.seat_state
|
||||
}
|
||||
|
||||
fn focus_changed(&mut self, _seat: &Seat<Self>, _focused: Option<&WlSurface>) {}
|
||||
fn cursor_image(&mut self, _seat: &Seat<Self>, _image: smithay::input::pointer::CursorImageStatus) {}
|
||||
}
|
||||
|
||||
impl PointerConstraintsHandler for App {}
|
||||
|
||||
struct App {
|
||||
compositor_state: CompositorState,
|
||||
xdg_shell_state: XdgShellState,
|
||||
shm_state: ShmState,
|
||||
seat_state: SeatState<Self>,
|
||||
data_device_state: DataDeviceState,
|
||||
|
||||
seat: Seat<Self>,
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Ok(env_filter) = tracing_subscriber::EnvFilter::try_from_default_env() {
|
||||
tracing_subscriber::fmt().with_env_filter(env_filter).init();
|
||||
} else {
|
||||
tracing_subscriber::fmt().init();
|
||||
}
|
||||
|
||||
run_winit()
|
||||
}
|
||||
|
||||
pub fn run_winit() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut display: Display<App> = Display::new()?;
|
||||
let dh = display.handle();
|
||||
|
||||
let compositor_state = CompositorState::new::<App>(&dh);
|
||||
let shm_state = ShmState::new::<App>(&dh, vec![]);
|
||||
let mut seat_state = SeatState::new();
|
||||
let seat = seat_state.new_wl_seat(&dh, "winit");
|
||||
|
||||
let mut state = {
|
||||
App {
|
||||
compositor_state,
|
||||
xdg_shell_state: XdgShellState::new::<App>(&dh),
|
||||
shm_state,
|
||||
seat_state,
|
||||
data_device_state: DataDeviceState::new::<App>(&dh),
|
||||
seat,
|
||||
}
|
||||
};
|
||||
|
||||
let listener = ListeningSocket::bind("wayland-5").unwrap();
|
||||
let mut clients = Vec::new();
|
||||
|
||||
let (mut backend, mut winit) = winit::init::<GlesRenderer>()?;
|
||||
|
||||
let start_time = std::time::Instant::now();
|
||||
|
||||
let keyboard = state.seat.add_keyboard(Default::default(), 200, 200).unwrap();
|
||||
|
||||
unsafe { std::env::set_var("WAYLAND_DISPLAY", "wayland-5") };
|
||||
std::process::Command::new("weston-terminal").spawn().ok();
|
||||
|
||||
loop {
|
||||
let status = winit.dispatch_new_events(|event| match event {
|
||||
WinitEvent::Resized { .. } => {}
|
||||
WinitEvent::Input(event) => match event {
|
||||
InputEvent::Keyboard { event } => {
|
||||
keyboard.input::<(), _>(
|
||||
&mut state,
|
||||
event.key_code(),
|
||||
event.state(),
|
||||
0.into(),
|
||||
InputTime::now(),
|
||||
|_, _, _| {
|
||||
//
|
||||
FilterResult::Forward
|
||||
},
|
||||
);
|
||||
}
|
||||
InputEvent::PointerMotionAbsolute { .. } => {
|
||||
if let Some(surface) = state.xdg_shell_state.toplevel_surfaces().iter().next().cloned() {
|
||||
let surface = surface.wl_surface().clone();
|
||||
keyboard.set_focus(&mut state, Some(surface), 0.into());
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => (),
|
||||
});
|
||||
|
||||
match status {
|
||||
PumpStatus::Continue => (),
|
||||
PumpStatus::Exit(_) => return Ok(()),
|
||||
};
|
||||
|
||||
let size = backend.window_size();
|
||||
let damage = Rectangle::from_size(size);
|
||||
{
|
||||
let (renderer, mut framebuffer) = backend.bind().unwrap();
|
||||
let elements = state
|
||||
.xdg_shell_state
|
||||
.toplevel_surfaces()
|
||||
.iter()
|
||||
.flat_map(|surface| {
|
||||
render_elements_from_surface_tree(
|
||||
renderer,
|
||||
surface.wl_surface(),
|
||||
(0, 0),
|
||||
1.0,
|
||||
1.0,
|
||||
Kind::Unspecified,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<WaylandSurfaceRenderElement<GlesRenderer>>>();
|
||||
|
||||
let mut frame = renderer
|
||||
.render(&mut framebuffer, size, Transform::Flipped180)
|
||||
.unwrap();
|
||||
frame.clear(Color32F::new(0.1, 0.0, 0.0, 1.0), &[damage]).unwrap();
|
||||
draw_render_elements(&mut frame, 1.0, &elements, &[damage]).unwrap();
|
||||
// We rely on the nested compositor to do the sync for us
|
||||
let _ = frame.finish().unwrap();
|
||||
|
||||
for surface in state.xdg_shell_state.toplevel_surfaces() {
|
||||
send_frames_surface_tree(surface.wl_surface(), start_time.elapsed().as_millis() as u32);
|
||||
}
|
||||
|
||||
if let Some(stream) = listener.accept()? {
|
||||
println!("Got a client: {:?}", stream);
|
||||
|
||||
let client = display
|
||||
.handle()
|
||||
.insert_client(stream, Arc::new(ClientState::default()))
|
||||
.unwrap();
|
||||
clients.push(client);
|
||||
}
|
||||
|
||||
display.dispatch_clients(&mut state)?;
|
||||
display.flush_clients()?;
|
||||
}
|
||||
|
||||
// It is important that all events on the display have been dispatched and flushed to clients before
|
||||
// swapping buffers because this operation may block.
|
||||
backend.submit(Some(&[damage])).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_frames_surface_tree(surface: &wl_surface::WlSurface, time: u32) {
|
||||
with_surface_tree_downward(
|
||||
surface,
|
||||
(),
|
||||
|_, _, &()| TraversalAction::DoChildren(()),
|
||||
|_surf, states, &()| {
|
||||
// the surface may not have any user_data if it is a subsurface and has not
|
||||
// yet been committed
|
||||
for callback in states
|
||||
.cached_state
|
||||
.get::<SurfaceAttributes>()
|
||||
.current()
|
||||
.frame_callbacks
|
||||
.drain(..)
|
||||
{
|
||||
callback.done(time);
|
||||
}
|
||||
},
|
||||
|_, _, &()| true,
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ClientState {
|
||||
compositor_state: CompositorClientState,
|
||||
}
|
||||
impl ClientData for ClientState {
|
||||
fn initialized(&self, _client_id: ClientId) {
|
||||
println!("initialized");
|
||||
}
|
||||
|
||||
fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {
|
||||
println!("disconnected");
|
||||
}
|
||||
}
|
||||
|
||||
smithay::delegate_dispatch2!(App);
|
||||
Vendored
+110
@@ -0,0 +1,110 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use smithay::backend::input::InputTime;
|
||||
use smithay::input::{Seat, SeatHandler, SeatState, keyboard::FilterResult};
|
||||
use smithay::reexports::wayland_server::{
|
||||
Display, ListeningSocket,
|
||||
backend::{ClientData, ClientId, DisconnectReason},
|
||||
protocol::wl_surface::WlSurface,
|
||||
};
|
||||
use smithay::wayland::compositor::{CompositorClientState, CompositorHandler, CompositorState};
|
||||
use smithay::wayland::pointer_constraints::PointerConstraintsHandler;
|
||||
|
||||
struct App {
|
||||
compositor_state: CompositorState,
|
||||
seat_state: SeatState<Self>,
|
||||
seat: Seat<Self>,
|
||||
}
|
||||
|
||||
impl SeatHandler for App {
|
||||
type KeyboardFocus = WlSurface;
|
||||
type PointerFocus = WlSurface;
|
||||
type TouchFocus = WlSurface;
|
||||
|
||||
fn seat_state(&mut self) -> &mut SeatState<Self> {
|
||||
&mut self.seat_state
|
||||
}
|
||||
|
||||
fn focus_changed(&mut self, _seat: &Seat<Self>, _focused: Option<&WlSurface>) {}
|
||||
fn cursor_image(&mut self, _seat: &Seat<Self>, _image: smithay::input::pointer::CursorImageStatus) {}
|
||||
}
|
||||
|
||||
impl PointerConstraintsHandler for App {}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut display: Display<App> = Display::new()?;
|
||||
let dh = display.handle();
|
||||
|
||||
let compositor_state = CompositorState::new::<App>(&dh);
|
||||
let mut seat_state = SeatState::new();
|
||||
let seat = seat_state.new_wl_seat(&dh, "Example");
|
||||
|
||||
let mut state = App {
|
||||
compositor_state,
|
||||
seat_state,
|
||||
seat,
|
||||
};
|
||||
|
||||
let keyboard = state.seat.add_keyboard(Default::default(), 25, 600)?;
|
||||
|
||||
let listener = ListeningSocket::bind("wayland-5").unwrap();
|
||||
|
||||
let mut clients = Vec::new();
|
||||
|
||||
loop {
|
||||
if let Some(stream) = listener.accept().unwrap() {
|
||||
println!("Got a client: {:?}", stream);
|
||||
|
||||
let client = display
|
||||
.handle()
|
||||
.insert_client(stream, Arc::new(ClientState(CompositorClientState::default())))
|
||||
.unwrap();
|
||||
clients.push(client);
|
||||
}
|
||||
|
||||
keyboard.input(
|
||||
&mut state,
|
||||
smithay::backend::input::Keycode::from(9u32),
|
||||
smithay::backend::input::KeyState::Pressed,
|
||||
0.into(),
|
||||
InputTime::now(),
|
||||
|_, _, _| {
|
||||
if false {
|
||||
FilterResult::Intercept(0)
|
||||
} else {
|
||||
FilterResult::Forward
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
keyboard.set_focus(&mut state, Option::<WlSurface>::None, 0.into());
|
||||
|
||||
display.dispatch_clients(&mut state)?;
|
||||
display.flush_clients()?;
|
||||
}
|
||||
}
|
||||
|
||||
struct ClientState(CompositorClientState);
|
||||
impl ClientData for ClientState {
|
||||
fn initialized(&self, _client_id: ClientId) {
|
||||
println!("initialized");
|
||||
}
|
||||
|
||||
fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {
|
||||
println!("disconnected");
|
||||
}
|
||||
}
|
||||
|
||||
impl CompositorHandler for App {
|
||||
fn compositor_state(&mut self) -> &mut CompositorState {
|
||||
&mut self.compositor_state
|
||||
}
|
||||
|
||||
fn client_compositor_state<'a>(&self, client: &'a wayland_server::Client) -> &'a CompositorClientState {
|
||||
&client.get_data::<ClientState>().unwrap().0
|
||||
}
|
||||
|
||||
fn commit(&mut self, _surface: &WlSurface) {}
|
||||
}
|
||||
|
||||
smithay::delegate_dispatch2!(App);
|
||||
Vendored
+61
@@ -0,0 +1,61 @@
|
||||
use drm_fourcc::{DrmFourcc, DrmModifier};
|
||||
use smithay::backend::{
|
||||
allocator::{
|
||||
Allocator, Buffer,
|
||||
dmabuf::AsDmabuf,
|
||||
vulkan::{ImageUsageFlags, VulkanAllocator},
|
||||
},
|
||||
vulkan::{Instance, PhysicalDevice, version::Version},
|
||||
};
|
||||
|
||||
fn main() {
|
||||
if let Ok(env_filter) = tracing_subscriber::EnvFilter::try_from_default_env() {
|
||||
tracing_subscriber::fmt().with_env_filter(env_filter).init();
|
||||
} else {
|
||||
tracing_subscriber::fmt().init();
|
||||
}
|
||||
|
||||
println!(
|
||||
"Available instance extensions: {:?}",
|
||||
Instance::enumerate_extensions().unwrap().collect::<Vec<_>>()
|
||||
);
|
||||
println!();
|
||||
|
||||
let instance = Instance::new(Version::VERSION_1_3, None).unwrap();
|
||||
|
||||
for (idx, phy) in PhysicalDevice::enumerate(&instance).unwrap().enumerate() {
|
||||
println!(
|
||||
"Device #{}: {} v{}, {:?}",
|
||||
idx,
|
||||
phy.name(),
|
||||
phy.api_version(),
|
||||
phy.driver()
|
||||
);
|
||||
}
|
||||
|
||||
let physical_device = PhysicalDevice::enumerate(&instance)
|
||||
.unwrap()
|
||||
.next()
|
||||
.expect("No physical devices");
|
||||
|
||||
// The allocator should create buffers that are suitable as render targets.
|
||||
let mut allocator = VulkanAllocator::new(&physical_device, ImageUsageFlags::COLOR_ATTACHMENT).unwrap();
|
||||
|
||||
let image = allocator
|
||||
.create_buffer(100, 200, DrmFourcc::Argb8888, &[DrmModifier::Linear])
|
||||
.expect("create");
|
||||
|
||||
assert_eq!(image.width(), 100);
|
||||
assert_eq!(image.height(), 200);
|
||||
|
||||
let image_dmabuf = image.export().expect("Export dmabuf");
|
||||
|
||||
drop(image);
|
||||
|
||||
let _image2 = allocator
|
||||
.create_buffer(200, 200, DrmFourcc::Argb8888, &[DrmModifier::Linear])
|
||||
.expect("create");
|
||||
|
||||
drop(allocator);
|
||||
drop(image_dmabuf);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
--- a/src/backend/renderer/gles/mod.rs
|
||||
+++ b/src/backend/renderer/gles/mod.rs
|
||||
@@ -883,6 +883,8 @@
|
||||
// why not store a `GlesTexture`? because the user might do so.
|
||||
// this is guaranteed a non-public internal type, so we are good.
|
||||
type CacheMap = HashMap<ContextId<GlesTexture>, Arc<GlesTextureInternal>>;
|
||||
+ static SHM_FORMAT_CHANGE_LOGS: std::sync::atomic::AtomicUsize =
|
||||
+ std::sync::atomic::AtomicUsize::new(0);
|
||||
|
||||
let mut surface_lock = surface.as_ref().map(|surface_data| {
|
||||
surface_data
|
||||
@@ -937,7 +939,33 @@
|
||||
surface_lock
|
||||
.as_ref()
|
||||
.and_then(|cache| cache.get(&id).cloned())
|
||||
- .filter(|texture| texture.size == (width, height).into())
|
||||
+ .filter(|texture| {
|
||||
+ let format_matches =
|
||||
+ texture.format == Some(internal_format) && texture.has_alpha == has_alpha;
|
||||
+ if !format_matches
|
||||
+ && tracing::enabled!(tracing::Level::DEBUG)
|
||||
+ && SHM_FORMAT_CHANGE_LOGS
|
||||
+ .fetch_update(
|
||||
+ std::sync::atomic::Ordering::Relaxed,
|
||||
+ std::sync::atomic::Ordering::Relaxed,
|
||||
+ |count| (count < 16).then_some(count + 1),
|
||||
+ )
|
||||
+ .is_ok()
|
||||
+ {
|
||||
+ tracing::debug!(
|
||||
+ old_width = texture.size.w,
|
||||
+ old_height = texture.size.h,
|
||||
+ new_width = width,
|
||||
+ new_height = height,
|
||||
+ old_internal_format = texture.format.unwrap_or(0),
|
||||
+ new_internal_format = internal_format,
|
||||
+ old_has_alpha = texture.has_alpha,
|
||||
+ new_has_alpha = has_alpha,
|
||||
+ "SHM texture cache format changed"
|
||||
+ );
|
||||
+ }
|
||||
+ texture.size == (width, height).into() && format_matches
|
||||
+ })
|
||||
.unwrap_or_else(|| {
|
||||
let mut tex = 0;
|
||||
unsafe { self.gl.GenTextures(1, &mut tex) };
|
||||
Vendored
+17
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "smallvil"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
|
||||
bitflags = "2.2.1"
|
||||
|
||||
[dependencies.smithay]
|
||||
path = "../"
|
||||
default-features = false
|
||||
features = [
|
||||
"backend_winit",
|
||||
"wayland_frontend",
|
||||
"desktop",
|
||||
]
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
pub mod move_grab;
|
||||
pub use move_grab::MoveSurfaceGrab;
|
||||
|
||||
pub mod resize_grab;
|
||||
pub use resize_grab::ResizeSurfaceGrab;
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
//! Move grab is the state of a composer during which the client window is being dragged around.
|
||||
//!
|
||||
//! eg. Usually whenever a user clicks on the app's titlebar and starts dragging, the compositors
|
||||
//! enters a MoveSurfaceGrab state.
|
||||
|
||||
use crate::Smallvil;
|
||||
use smithay::{
|
||||
desktop::Window,
|
||||
input::pointer::{
|
||||
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent,
|
||||
GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab,
|
||||
PointerInnerHandle, RelativeMotionEvent,
|
||||
},
|
||||
reexports::wayland_server::protocol::wl_surface::WlSurface,
|
||||
utils::{Logical, Point},
|
||||
};
|
||||
|
||||
pub struct MoveSurfaceGrab {
|
||||
pub start_data: PointerGrabStartData<Smallvil>,
|
||||
pub window: Window,
|
||||
pub initial_window_location: Point<i32, Logical>,
|
||||
}
|
||||
|
||||
impl PointerGrab<Smallvil> for MoveSurfaceGrab {
|
||||
fn motion(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
_focus: Option<(WlSurface, Point<f64, Logical>)>,
|
||||
event: &MotionEvent,
|
||||
) {
|
||||
// While the grab is active, no client has pointer focus
|
||||
handle.motion(data, None, event);
|
||||
|
||||
let delta = event.location - self.start_data.location;
|
||||
let new_location = self.initial_window_location.to_f64() + delta;
|
||||
data.space
|
||||
.map_element(self.window.clone(), new_location.to_i32_round(), true);
|
||||
}
|
||||
|
||||
fn relative_motion(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
focus: Option<(WlSurface, Point<f64, Logical>)>,
|
||||
event: &RelativeMotionEvent,
|
||||
) {
|
||||
handle.relative_motion(data, focus, event);
|
||||
}
|
||||
|
||||
fn button(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
event: &ButtonEvent,
|
||||
) {
|
||||
handle.button(data, event);
|
||||
|
||||
// The button is a button code as defined in the
|
||||
// Linux kernel's linux/input-event-codes.h header file, e.g. BTN_LEFT.
|
||||
const BTN_LEFT: u32 = 0x110;
|
||||
|
||||
if !handle.current_pressed().contains(&BTN_LEFT) {
|
||||
// No more buttons are pressed, release the grab.
|
||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||
}
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
details: AxisFrame,
|
||||
) {
|
||||
handle.axis(data, details)
|
||||
}
|
||||
|
||||
fn frame(&mut self, data: &mut Smallvil, handle: &mut PointerInnerHandle<'_, Smallvil>) {
|
||||
handle.frame(data);
|
||||
}
|
||||
|
||||
fn gesture_swipe_begin(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
event: &GestureSwipeBeginEvent,
|
||||
) {
|
||||
handle.gesture_swipe_begin(data, event)
|
||||
}
|
||||
|
||||
fn gesture_swipe_update(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
event: &GestureSwipeUpdateEvent,
|
||||
) {
|
||||
handle.gesture_swipe_update(data, event)
|
||||
}
|
||||
|
||||
fn gesture_swipe_end(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
event: &GestureSwipeEndEvent,
|
||||
) {
|
||||
handle.gesture_swipe_end(data, event)
|
||||
}
|
||||
|
||||
fn gesture_pinch_begin(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
event: &GesturePinchBeginEvent,
|
||||
) {
|
||||
handle.gesture_pinch_begin(data, event)
|
||||
}
|
||||
|
||||
fn gesture_pinch_update(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
event: &GesturePinchUpdateEvent,
|
||||
) {
|
||||
handle.gesture_pinch_update(data, event)
|
||||
}
|
||||
|
||||
fn gesture_pinch_end(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
event: &GesturePinchEndEvent,
|
||||
) {
|
||||
handle.gesture_pinch_end(data, event)
|
||||
}
|
||||
|
||||
fn gesture_hold_begin(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
event: &GestureHoldBeginEvent,
|
||||
) {
|
||||
handle.gesture_hold_begin(data, event)
|
||||
}
|
||||
|
||||
fn gesture_hold_end(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
event: &GestureHoldEndEvent,
|
||||
) {
|
||||
handle.gesture_hold_end(data, event)
|
||||
}
|
||||
|
||||
fn start_data(&self) -> &PointerGrabStartData<Smallvil> {
|
||||
&self.start_data
|
||||
}
|
||||
|
||||
fn unset(&mut self, _data: &mut Smallvil) {}
|
||||
}
|
||||
+365
@@ -0,0 +1,365 @@
|
||||
//! Resize grab is the state of a composer during which the client window is being resized.
|
||||
//!
|
||||
//! eg. Usually whenever a user clicks on the app's border and starts dragging, the compositors
|
||||
//! enters a ResizeSurfaceGrab state.
|
||||
|
||||
use crate::Smallvil;
|
||||
use smithay::{
|
||||
desktop::{Space, Window},
|
||||
input::pointer::{
|
||||
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent,
|
||||
GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab,
|
||||
PointerInnerHandle, RelativeMotionEvent,
|
||||
},
|
||||
reexports::{
|
||||
wayland_protocols::xdg::shell::server::xdg_toplevel, wayland_server::protocol::wl_surface::WlSurface,
|
||||
},
|
||||
utils::{Logical, Point, Rectangle, Size},
|
||||
wayland::{compositor, shell::xdg::SurfaceCachedState},
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
|
||||
bitflags::bitflags! {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ResizeEdge: u32 {
|
||||
const TOP = 0b0001;
|
||||
const BOTTOM = 0b0010;
|
||||
const LEFT = 0b0100;
|
||||
const RIGHT = 0b1000;
|
||||
|
||||
const TOP_LEFT = Self::TOP.bits() | Self::LEFT.bits();
|
||||
const BOTTOM_LEFT = Self::BOTTOM.bits() | Self::LEFT.bits();
|
||||
|
||||
const TOP_RIGHT = Self::TOP.bits() | Self::RIGHT.bits();
|
||||
const BOTTOM_RIGHT = Self::BOTTOM.bits() | Self::RIGHT.bits();
|
||||
}
|
||||
}
|
||||
|
||||
impl From<xdg_toplevel::ResizeEdge> for ResizeEdge {
|
||||
#[inline]
|
||||
fn from(x: xdg_toplevel::ResizeEdge) -> Self {
|
||||
Self::from_bits(x as u32).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ResizeSurfaceGrab {
|
||||
start_data: PointerGrabStartData<Smallvil>,
|
||||
window: Window,
|
||||
|
||||
edges: ResizeEdge,
|
||||
|
||||
initial_rect: Rectangle<i32, Logical>,
|
||||
last_window_size: Size<i32, Logical>,
|
||||
}
|
||||
|
||||
impl ResizeSurfaceGrab {
|
||||
pub fn start(
|
||||
start_data: PointerGrabStartData<Smallvil>,
|
||||
window: Window,
|
||||
edges: ResizeEdge,
|
||||
initial_window_rect: Rectangle<i32, Logical>,
|
||||
) -> Self {
|
||||
let initial_rect = initial_window_rect;
|
||||
|
||||
ResizeSurfaceState::with(window.toplevel().unwrap().wl_surface(), |state| {
|
||||
*state = ResizeSurfaceState::Resizing { edges, initial_rect };
|
||||
});
|
||||
|
||||
Self {
|
||||
start_data,
|
||||
window,
|
||||
edges,
|
||||
initial_rect,
|
||||
last_window_size: initial_rect.size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PointerGrab<Smallvil> for ResizeSurfaceGrab {
|
||||
fn motion(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
_focus: Option<(WlSurface, Point<f64, Logical>)>,
|
||||
event: &MotionEvent,
|
||||
) {
|
||||
// While the grab is active, no client has pointer focus
|
||||
handle.motion(data, None, event);
|
||||
|
||||
let mut delta = event.location - self.start_data.location;
|
||||
|
||||
let mut new_window_width = self.initial_rect.size.w;
|
||||
let mut new_window_height = self.initial_rect.size.h;
|
||||
|
||||
if self.edges.intersects(ResizeEdge::LEFT | ResizeEdge::RIGHT) {
|
||||
if self.edges.intersects(ResizeEdge::LEFT) {
|
||||
delta.x = -delta.x;
|
||||
}
|
||||
|
||||
new_window_width = (self.initial_rect.size.w as f64 + delta.x) as i32;
|
||||
}
|
||||
|
||||
if self.edges.intersects(ResizeEdge::TOP | ResizeEdge::BOTTOM) {
|
||||
if self.edges.intersects(ResizeEdge::TOP) {
|
||||
delta.y = -delta.y;
|
||||
}
|
||||
|
||||
new_window_height = (self.initial_rect.size.h as f64 + delta.y) as i32;
|
||||
}
|
||||
|
||||
let (min_size, max_size) =
|
||||
compositor::with_states(self.window.toplevel().unwrap().wl_surface(), |states| {
|
||||
let mut guard = states.cached_state.get::<SurfaceCachedState>();
|
||||
let data = guard.current();
|
||||
(data.min_size, data.max_size)
|
||||
});
|
||||
|
||||
let min_width = min_size.w.max(1);
|
||||
let min_height = min_size.h.max(1);
|
||||
|
||||
let max_width = if max_size.w == 0 { i32::MAX } else { max_size.w };
|
||||
let max_height = if max_size.h == 0 { i32::MAX } else { max_size.h };
|
||||
|
||||
self.last_window_size = Size::from((
|
||||
new_window_width.max(min_width).min(max_width),
|
||||
new_window_height.max(min_height).min(max_height),
|
||||
));
|
||||
|
||||
let xdg = self.window.toplevel().unwrap();
|
||||
xdg.with_pending_state(|state| {
|
||||
state.states.set(xdg_toplevel::State::Resizing);
|
||||
state.size = Some(self.last_window_size);
|
||||
});
|
||||
|
||||
xdg.send_pending_configure();
|
||||
}
|
||||
|
||||
fn relative_motion(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
focus: Option<(WlSurface, Point<f64, Logical>)>,
|
||||
event: &RelativeMotionEvent,
|
||||
) {
|
||||
handle.relative_motion(data, focus, event);
|
||||
}
|
||||
|
||||
fn button(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
event: &ButtonEvent,
|
||||
) {
|
||||
handle.button(data, event);
|
||||
|
||||
// The button is a button code as defined in the
|
||||
// Linux kernel's linux/input-event-codes.h header file, e.g. BTN_LEFT.
|
||||
const BTN_LEFT: u32 = 0x110;
|
||||
|
||||
if !handle.current_pressed().contains(&BTN_LEFT) {
|
||||
// No more buttons are pressed, release the grab.
|
||||
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||
|
||||
let xdg = self.window.toplevel().unwrap();
|
||||
xdg.with_pending_state(|state| {
|
||||
state.states.unset(xdg_toplevel::State::Resizing);
|
||||
state.size = Some(self.last_window_size);
|
||||
});
|
||||
|
||||
xdg.send_pending_configure();
|
||||
|
||||
ResizeSurfaceState::with(xdg.wl_surface(), |state| {
|
||||
*state = ResizeSurfaceState::WaitingForLastCommit {
|
||||
edges: self.edges,
|
||||
initial_rect: self.initial_rect,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn axis(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
details: AxisFrame,
|
||||
) {
|
||||
handle.axis(data, details)
|
||||
}
|
||||
|
||||
fn frame(&mut self, data: &mut Smallvil, handle: &mut PointerInnerHandle<'_, Smallvil>) {
|
||||
handle.frame(data);
|
||||
}
|
||||
|
||||
fn gesture_swipe_begin(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
event: &GestureSwipeBeginEvent,
|
||||
) {
|
||||
handle.gesture_swipe_begin(data, event)
|
||||
}
|
||||
|
||||
fn gesture_swipe_update(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
event: &GestureSwipeUpdateEvent,
|
||||
) {
|
||||
handle.gesture_swipe_update(data, event)
|
||||
}
|
||||
|
||||
fn gesture_swipe_end(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
event: &GestureSwipeEndEvent,
|
||||
) {
|
||||
handle.gesture_swipe_end(data, event)
|
||||
}
|
||||
|
||||
fn gesture_pinch_begin(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
event: &GesturePinchBeginEvent,
|
||||
) {
|
||||
handle.gesture_pinch_begin(data, event)
|
||||
}
|
||||
|
||||
fn gesture_pinch_update(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
event: &GesturePinchUpdateEvent,
|
||||
) {
|
||||
handle.gesture_pinch_update(data, event)
|
||||
}
|
||||
|
||||
fn gesture_pinch_end(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
event: &GesturePinchEndEvent,
|
||||
) {
|
||||
handle.gesture_pinch_end(data, event)
|
||||
}
|
||||
|
||||
fn gesture_hold_begin(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
event: &GestureHoldBeginEvent,
|
||||
) {
|
||||
handle.gesture_hold_begin(data, event)
|
||||
}
|
||||
|
||||
fn gesture_hold_end(
|
||||
&mut self,
|
||||
data: &mut Smallvil,
|
||||
handle: &mut PointerInnerHandle<'_, Smallvil>,
|
||||
event: &GestureHoldEndEvent,
|
||||
) {
|
||||
handle.gesture_hold_end(data, event)
|
||||
}
|
||||
|
||||
fn start_data(&self) -> &PointerGrabStartData<Smallvil> {
|
||||
&self.start_data
|
||||
}
|
||||
|
||||
fn unset(&mut self, _data: &mut Smallvil) {}
|
||||
}
|
||||
|
||||
/// State of the resize operation.
|
||||
///
|
||||
/// It is stored inside of WlSurface,
|
||||
/// and can be accessed using [`ResizeSurfaceState::with`]
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
|
||||
enum ResizeSurfaceState {
|
||||
#[default]
|
||||
Idle,
|
||||
Resizing {
|
||||
edges: ResizeEdge,
|
||||
/// The initial window size and location.
|
||||
initial_rect: Rectangle<i32, Logical>,
|
||||
},
|
||||
/// Resize is done, we are now waiting for last commit, to do the final move
|
||||
WaitingForLastCommit {
|
||||
edges: ResizeEdge,
|
||||
/// The initial window size and location.
|
||||
initial_rect: Rectangle<i32, Logical>,
|
||||
},
|
||||
}
|
||||
|
||||
impl ResizeSurfaceState {
|
||||
fn with<F, T>(surface: &WlSurface, cb: F) -> T
|
||||
where
|
||||
F: FnOnce(&mut Self) -> T,
|
||||
{
|
||||
compositor::with_states(surface, |states| {
|
||||
states.data_map.insert_if_missing(RefCell::<Self>::default);
|
||||
let state = states.data_map.get::<RefCell<Self>>().unwrap();
|
||||
|
||||
cb(&mut state.borrow_mut())
|
||||
})
|
||||
}
|
||||
|
||||
fn commit(&mut self) -> Option<(ResizeEdge, Rectangle<i32, Logical>)> {
|
||||
match *self {
|
||||
Self::Resizing { edges, initial_rect } => Some((edges, initial_rect)),
|
||||
Self::WaitingForLastCommit { edges, initial_rect } => {
|
||||
// The resize is done, let's go back to idle
|
||||
*self = Self::Idle;
|
||||
|
||||
Some((edges, initial_rect))
|
||||
}
|
||||
Self::Idle => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Should be called on `WlSurface::commit`
|
||||
pub fn handle_commit(space: &mut Space<Window>, surface: &WlSurface) -> Option<()> {
|
||||
let window = space
|
||||
.elements()
|
||||
.find(|w| w.toplevel().unwrap().wl_surface() == surface)
|
||||
.cloned()?;
|
||||
|
||||
let mut window_loc = space.element_location(&window)?;
|
||||
let geometry = window.geometry();
|
||||
|
||||
let new_loc: Point<Option<i32>, Logical> = ResizeSurfaceState::with(surface, |state| {
|
||||
state
|
||||
.commit()
|
||||
.and_then(|(edges, initial_rect)| {
|
||||
// If the window is being resized by top or left, its location must be adjusted
|
||||
// accordingly.
|
||||
edges.intersects(ResizeEdge::TOP_LEFT).then(|| {
|
||||
let new_x = edges
|
||||
.intersects(ResizeEdge::LEFT)
|
||||
.then_some(initial_rect.loc.x + (initial_rect.size.w - geometry.size.w));
|
||||
|
||||
let new_y = edges
|
||||
.intersects(ResizeEdge::TOP)
|
||||
.then_some(initial_rect.loc.y + (initial_rect.size.h - geometry.size.h));
|
||||
|
||||
(new_x, new_y).into()
|
||||
})
|
||||
})
|
||||
.unwrap_or_default()
|
||||
});
|
||||
|
||||
if let Some(new_x) = new_loc.x {
|
||||
window_loc.x = new_x;
|
||||
}
|
||||
if let Some(new_y) = new_loc.y {
|
||||
window_loc.y = new_y;
|
||||
}
|
||||
|
||||
if new_loc.x.is_some() || new_loc.y.is_some() {
|
||||
// If TOP or LEFT side of the window got resized, we have to move it
|
||||
space.map_element(window, window_loc, false);
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
use crate::{Smallvil, grabs::resize_grab, state::ClientState};
|
||||
use smithay::{
|
||||
backend::renderer::utils::on_commit_buffer_handler,
|
||||
reexports::wayland_server::{
|
||||
Client,
|
||||
protocol::{wl_buffer, wl_surface::WlSurface},
|
||||
},
|
||||
wayland::{
|
||||
buffer::BufferHandler,
|
||||
compositor::{
|
||||
CompositorClientState, CompositorHandler, CompositorState, get_parent, is_sync_subsurface,
|
||||
},
|
||||
shm::{ShmHandler, ShmState},
|
||||
},
|
||||
};
|
||||
|
||||
use super::xdg_shell;
|
||||
|
||||
impl CompositorHandler for Smallvil {
|
||||
fn compositor_state(&mut self) -> &mut CompositorState {
|
||||
&mut self.compositor_state
|
||||
}
|
||||
|
||||
fn client_compositor_state<'a>(&self, client: &'a Client) -> &'a CompositorClientState {
|
||||
&client.get_data::<ClientState>().unwrap().compositor_state
|
||||
}
|
||||
|
||||
fn commit(&mut self, surface: &WlSurface) {
|
||||
on_commit_buffer_handler::<Self>(surface);
|
||||
if !is_sync_subsurface(surface) {
|
||||
let mut root = surface.clone();
|
||||
while let Some(parent) = get_parent(&root) {
|
||||
root = parent;
|
||||
}
|
||||
if let Some(window) = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|w| w.toplevel().unwrap().wl_surface() == &root)
|
||||
{
|
||||
window.on_commit();
|
||||
}
|
||||
};
|
||||
|
||||
xdg_shell::handle_commit(&mut self.popups, &self.space, surface);
|
||||
resize_grab::handle_commit(&mut self.space, surface);
|
||||
}
|
||||
}
|
||||
|
||||
impl BufferHandler for Smallvil {
|
||||
fn buffer_destroyed(&mut self, _buffer: &wl_buffer::WlBuffer) {}
|
||||
}
|
||||
|
||||
impl ShmHandler for Smallvil {
|
||||
fn shm_state(&self) -> &ShmState {
|
||||
&self.shm_state
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
mod compositor;
|
||||
mod xdg_shell;
|
||||
|
||||
use crate::Smallvil;
|
||||
|
||||
//
|
||||
// Wl Seat
|
||||
//
|
||||
|
||||
use smithay::input::dnd::{DnDGrab, DndGrabHandler, GrabType, Source};
|
||||
use smithay::input::pointer::Focus;
|
||||
use smithay::input::{Seat, SeatHandler, SeatState};
|
||||
use smithay::reexports::wayland_server::Resource;
|
||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||
use smithay::utils::Serial;
|
||||
use smithay::wayland::output::OutputHandler;
|
||||
use smithay::wayland::pointer_constraints::PointerConstraintsHandler;
|
||||
use smithay::wayland::selection::SelectionHandler;
|
||||
use smithay::wayland::selection::data_device::{
|
||||
DataDeviceHandler, DataDeviceState, WaylandDndGrabHandler, set_data_device_focus,
|
||||
};
|
||||
|
||||
impl SeatHandler for Smallvil {
|
||||
type KeyboardFocus = WlSurface;
|
||||
type PointerFocus = WlSurface;
|
||||
type TouchFocus = WlSurface;
|
||||
|
||||
fn seat_state(&mut self) -> &mut SeatState<Smallvil> {
|
||||
&mut self.seat_state
|
||||
}
|
||||
|
||||
fn cursor_image(&mut self, _seat: &Seat<Self>, _image: smithay::input::pointer::CursorImageStatus) {}
|
||||
|
||||
fn focus_changed(&mut self, seat: &Seat<Self>, focused: Option<&WlSurface>) {
|
||||
let dh = &self.display_handle;
|
||||
let client = focused.and_then(|s| dh.get_client(s.id()).ok());
|
||||
set_data_device_focus(dh, seat, client);
|
||||
}
|
||||
}
|
||||
|
||||
impl PointerConstraintsHandler for Smallvil {}
|
||||
|
||||
//
|
||||
// Wl Data Device
|
||||
//
|
||||
|
||||
impl SelectionHandler for Smallvil {
|
||||
type SelectionUserData = ();
|
||||
}
|
||||
|
||||
impl DataDeviceHandler for Smallvil {
|
||||
fn data_device_state(&mut self) -> &mut DataDeviceState {
|
||||
&mut self.data_device_state
|
||||
}
|
||||
}
|
||||
|
||||
impl DndGrabHandler for Smallvil {}
|
||||
impl WaylandDndGrabHandler for Smallvil {
|
||||
fn dnd_requested<S: Source>(
|
||||
&mut self,
|
||||
source: S,
|
||||
_icon: Option<WlSurface>,
|
||||
seat: Seat<Self>,
|
||||
serial: Serial,
|
||||
type_: GrabType,
|
||||
) {
|
||||
match type_ {
|
||||
GrabType::Pointer => {
|
||||
let ptr = seat.get_pointer().unwrap();
|
||||
let start_data = ptr.grab_start_data().unwrap();
|
||||
|
||||
// create a dnd grab to start the operation
|
||||
let grab = DnDGrab::new_pointer(&self.display_handle, start_data, source, seat);
|
||||
ptr.set_grab(self, grab, serial, Focus::Keep);
|
||||
}
|
||||
GrabType::Touch => {
|
||||
// smallvil lacks touch handling
|
||||
source.cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Wl Output & Xdg Output
|
||||
//
|
||||
|
||||
impl OutputHandler for Smallvil {}
|
||||
|
||||
smithay::delegate_dispatch2!(Smallvil);
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
use smithay::{
|
||||
desktop::{PopupKind, PopupManager, Space, Window, find_popup_root_surface, get_popup_toplevel_coords},
|
||||
input::{
|
||||
Seat,
|
||||
pointer::{Focus, GrabStartData as PointerGrabStartData},
|
||||
},
|
||||
reexports::{
|
||||
wayland_protocols::xdg::shell::server::xdg_toplevel,
|
||||
wayland_server::{
|
||||
Resource,
|
||||
protocol::{wl_seat, wl_surface::WlSurface},
|
||||
},
|
||||
},
|
||||
utils::{Rectangle, Serial},
|
||||
wayland::{
|
||||
compositor::with_states,
|
||||
shell::xdg::{
|
||||
PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState,
|
||||
XdgToplevelSurfaceData,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
Smallvil,
|
||||
grabs::{MoveSurfaceGrab, ResizeSurfaceGrab},
|
||||
};
|
||||
|
||||
impl XdgShellHandler for Smallvil {
|
||||
fn xdg_shell_state(&mut self) -> &mut XdgShellState {
|
||||
&mut self.xdg_shell_state
|
||||
}
|
||||
|
||||
fn new_toplevel(&mut self, surface: ToplevelSurface) {
|
||||
let window = Window::new_wayland_window(surface);
|
||||
self.space.map_element(window, (0, 0), false);
|
||||
}
|
||||
|
||||
fn new_popup(&mut self, surface: PopupSurface, _positioner: PositionerState) {
|
||||
self.unconstrain_popup(&surface);
|
||||
let _ = self.popups.track_popup(PopupKind::Xdg(surface));
|
||||
}
|
||||
|
||||
fn reposition_request(&mut self, surface: PopupSurface, positioner: PositionerState, token: u32) {
|
||||
surface.with_pending_state(|state| {
|
||||
let geometry = positioner.get_geometry();
|
||||
state.geometry = geometry;
|
||||
state.positioner = positioner;
|
||||
});
|
||||
self.unconstrain_popup(&surface);
|
||||
surface.send_repositioned(token);
|
||||
}
|
||||
|
||||
fn move_request(&mut self, surface: ToplevelSurface, seat: wl_seat::WlSeat, serial: Serial) {
|
||||
let seat = Seat::from_resource(&seat).unwrap();
|
||||
|
||||
let wl_surface = surface.wl_surface();
|
||||
|
||||
if let Some(start_data) = check_grab(&seat, wl_surface, serial) {
|
||||
let pointer = seat.get_pointer().unwrap();
|
||||
|
||||
let window = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|w| w.toplevel().unwrap().wl_surface() == wl_surface)
|
||||
.unwrap()
|
||||
.clone();
|
||||
let initial_window_location = self.space.element_location(&window).unwrap();
|
||||
|
||||
let grab = MoveSurfaceGrab {
|
||||
start_data,
|
||||
window,
|
||||
initial_window_location,
|
||||
};
|
||||
|
||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||
}
|
||||
}
|
||||
|
||||
fn resize_request(
|
||||
&mut self,
|
||||
surface: ToplevelSurface,
|
||||
seat: wl_seat::WlSeat,
|
||||
serial: Serial,
|
||||
edges: xdg_toplevel::ResizeEdge,
|
||||
) {
|
||||
let seat = Seat::from_resource(&seat).unwrap();
|
||||
|
||||
let wl_surface = surface.wl_surface();
|
||||
|
||||
if let Some(start_data) = check_grab(&seat, wl_surface, serial) {
|
||||
let pointer = seat.get_pointer().unwrap();
|
||||
|
||||
let window = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|w| w.toplevel().unwrap().wl_surface() == wl_surface)
|
||||
.unwrap()
|
||||
.clone();
|
||||
let initial_window_location = self.space.element_location(&window).unwrap();
|
||||
let initial_window_size = window.geometry().size;
|
||||
|
||||
surface.with_pending_state(|state| {
|
||||
state.states.set(xdg_toplevel::State::Resizing);
|
||||
});
|
||||
|
||||
surface.send_pending_configure();
|
||||
|
||||
let grab = ResizeSurfaceGrab::start(
|
||||
start_data,
|
||||
window,
|
||||
edges.into(),
|
||||
Rectangle::new(initial_window_location, initial_window_size),
|
||||
);
|
||||
|
||||
pointer.set_grab(self, grab, serial, Focus::Clear);
|
||||
}
|
||||
}
|
||||
|
||||
fn grab(&mut self, _surface: PopupSurface, _seat: wl_seat::WlSeat, _serial: Serial) {
|
||||
// TODO popup grabs
|
||||
}
|
||||
}
|
||||
|
||||
fn check_grab(
|
||||
seat: &Seat<Smallvil>,
|
||||
surface: &WlSurface,
|
||||
serial: Serial,
|
||||
) -> Option<PointerGrabStartData<Smallvil>> {
|
||||
let pointer = seat.get_pointer()?;
|
||||
|
||||
// Check that this surface has a click grab.
|
||||
if !pointer.has_grab(serial) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let start_data = pointer.grab_start_data()?;
|
||||
|
||||
let (focus, _) = start_data.focus.as_ref()?;
|
||||
// If the focus was for a different surface, ignore the request.
|
||||
if !focus.id().same_client_as(&surface.id()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(start_data)
|
||||
}
|
||||
|
||||
/// Should be called on `WlSurface::commit`
|
||||
pub fn handle_commit(popups: &mut PopupManager, space: &Space<Window>, surface: &WlSurface) {
|
||||
// Handle toplevel commits.
|
||||
if let Some(window) = space
|
||||
.elements()
|
||||
.find(|w| w.toplevel().unwrap().wl_surface() == surface)
|
||||
.cloned()
|
||||
{
|
||||
let initial_configure_sent = with_states(surface, |states| {
|
||||
states
|
||||
.data_map
|
||||
.get::<XdgToplevelSurfaceData>()
|
||||
.unwrap()
|
||||
.lock()
|
||||
.unwrap()
|
||||
.initial_configure_sent
|
||||
});
|
||||
|
||||
if !initial_configure_sent {
|
||||
window.toplevel().unwrap().send_configure();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle popup commits.
|
||||
popups.commit(surface);
|
||||
if let Some(popup) = popups.find_popup(surface) {
|
||||
match popup {
|
||||
PopupKind::Xdg(ref xdg) => {
|
||||
if !xdg.is_initial_configure_sent() {
|
||||
// NOTE: This should never fail as the initial configure is always
|
||||
// allowed.
|
||||
xdg.send_configure().expect("initial configure failed");
|
||||
}
|
||||
}
|
||||
PopupKind::InputMethod(ref _input_method) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Smallvil {
|
||||
fn unconstrain_popup(&self, popup: &PopupSurface) {
|
||||
let Ok(root) = find_popup_root_surface(&PopupKind::Xdg(popup.clone())) else {
|
||||
return;
|
||||
};
|
||||
let Some(window) = self
|
||||
.space
|
||||
.elements()
|
||||
.find(|w| w.toplevel().unwrap().wl_surface() == &root)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let output = self.space.outputs().next().unwrap();
|
||||
let output_geo = self.space.output_geometry(output).unwrap();
|
||||
let window_geo = self.space.element_geometry(window).unwrap();
|
||||
|
||||
// The target geometry for the positioner should be relative to its parent's geometry, so
|
||||
// we will compute that here.
|
||||
let mut target = output_geo;
|
||||
target.loc -= get_popup_toplevel_coords(&PopupKind::Xdg(popup.clone()));
|
||||
target.loc -= window_geo.loc;
|
||||
|
||||
popup.with_pending_state(|state| {
|
||||
state.geometry = state.positioner.get_unconstrained_geometry(target);
|
||||
});
|
||||
}
|
||||
}
|
||||
Vendored
+144
@@ -0,0 +1,144 @@
|
||||
use smithay::{
|
||||
backend::input::{
|
||||
AbsolutePositionEvent, Axis, AxisSource, ButtonState, Event, InputBackend, InputEvent,
|
||||
KeyboardKeyEvent, PointerAxisEvent, PointerButtonEvent,
|
||||
},
|
||||
input::{
|
||||
keyboard::FilterResult,
|
||||
pointer::{AxisFrame, ButtonEvent, MotionEvent},
|
||||
},
|
||||
reexports::wayland_server::protocol::wl_surface::WlSurface,
|
||||
utils::SERIAL_COUNTER,
|
||||
};
|
||||
|
||||
use crate::state::Smallvil;
|
||||
|
||||
impl Smallvil {
|
||||
pub fn process_input_event<I: InputBackend>(&mut self, event: InputEvent<I>) {
|
||||
match event {
|
||||
InputEvent::Keyboard { event, .. } => {
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
let time = Event::time(&event);
|
||||
|
||||
self.seat.get_keyboard().unwrap().input::<(), _>(
|
||||
self,
|
||||
event.key_code(),
|
||||
event.state(),
|
||||
serial,
|
||||
time,
|
||||
|_, _, _| FilterResult::Forward,
|
||||
);
|
||||
}
|
||||
InputEvent::PointerMotion { .. } => {}
|
||||
InputEvent::PointerMotionAbsolute { event, .. } => {
|
||||
let output = self.space.outputs().next().unwrap();
|
||||
|
||||
let output_geo = self.space.output_geometry(output).unwrap();
|
||||
|
||||
let pos = event.position_transformed(output_geo.size) + output_geo.loc.to_f64();
|
||||
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
|
||||
let pointer = self.seat.get_pointer().unwrap();
|
||||
|
||||
let under = self.surface_under(pos);
|
||||
|
||||
pointer.motion(
|
||||
self,
|
||||
under,
|
||||
&MotionEvent {
|
||||
location: pos,
|
||||
serial,
|
||||
time: event.time(),
|
||||
},
|
||||
);
|
||||
pointer.frame(self);
|
||||
}
|
||||
InputEvent::PointerButton { event, .. } => {
|
||||
let pointer = self.seat.get_pointer().unwrap();
|
||||
let keyboard = self.seat.get_keyboard().unwrap();
|
||||
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
|
||||
let button = event.button_code();
|
||||
|
||||
let button_state = event.state();
|
||||
|
||||
if ButtonState::Pressed == button_state && !pointer.is_grabbed() {
|
||||
if let Some((window, _loc)) = self
|
||||
.space
|
||||
.element_under(pointer.current_location())
|
||||
.map(|(w, l)| (w.clone(), l))
|
||||
{
|
||||
self.space.raise_element(&window, true);
|
||||
keyboard.set_focus(
|
||||
self,
|
||||
Some(window.toplevel().unwrap().wl_surface().clone()),
|
||||
serial,
|
||||
);
|
||||
self.space.elements().for_each(|window| {
|
||||
window.toplevel().unwrap().send_pending_configure();
|
||||
});
|
||||
} else {
|
||||
self.space.elements().for_each(|window| {
|
||||
window.set_activated(false);
|
||||
window.toplevel().unwrap().send_pending_configure();
|
||||
});
|
||||
keyboard.set_focus(self, Option::<WlSurface>::None, serial);
|
||||
}
|
||||
};
|
||||
|
||||
pointer.button(
|
||||
self,
|
||||
&ButtonEvent {
|
||||
button,
|
||||
state: button_state,
|
||||
serial,
|
||||
time: event.time(),
|
||||
},
|
||||
);
|
||||
pointer.frame(self);
|
||||
}
|
||||
InputEvent::PointerAxis { event, .. } => {
|
||||
let source = event.source();
|
||||
|
||||
let horizontal_amount = event
|
||||
.amount(Axis::Horizontal)
|
||||
.unwrap_or_else(|| event.amount_v120(Axis::Horizontal).unwrap_or(0.0) * 15.0 / 120.);
|
||||
let vertical_amount = event
|
||||
.amount(Axis::Vertical)
|
||||
.unwrap_or_else(|| event.amount_v120(Axis::Vertical).unwrap_or(0.0) * 15.0 / 120.);
|
||||
let horizontal_amount_discrete = event.amount_v120(Axis::Horizontal);
|
||||
let vertical_amount_discrete = event.amount_v120(Axis::Vertical);
|
||||
|
||||
let mut frame = AxisFrame::new(event.time()).source(source);
|
||||
if horizontal_amount != 0.0 {
|
||||
frame = frame.value(Axis::Horizontal, horizontal_amount);
|
||||
if let Some(discrete) = horizontal_amount_discrete {
|
||||
frame = frame.v120(Axis::Horizontal, discrete as i32);
|
||||
}
|
||||
}
|
||||
if vertical_amount != 0.0 {
|
||||
frame = frame.value(Axis::Vertical, vertical_amount);
|
||||
if let Some(discrete) = vertical_amount_discrete {
|
||||
frame = frame.v120(Axis::Vertical, discrete as i32);
|
||||
}
|
||||
}
|
||||
|
||||
if source == AxisSource::Finger {
|
||||
if event.amount(Axis::Horizontal) == Some(0.0) {
|
||||
frame = frame.stop(Axis::Horizontal);
|
||||
}
|
||||
if event.amount(Axis::Vertical) == Some(0.0) {
|
||||
frame = frame.stop(Axis::Vertical);
|
||||
}
|
||||
}
|
||||
|
||||
let pointer = self.seat.get_pointer().unwrap();
|
||||
pointer.axis(self, frame);
|
||||
pointer.frame(self);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
#![allow(irrefutable_let_patterns)]
|
||||
|
||||
mod handlers;
|
||||
|
||||
mod grabs;
|
||||
mod input;
|
||||
mod state;
|
||||
mod winit;
|
||||
|
||||
use smithay::reexports::{calloop::EventLoop, wayland_server::Display};
|
||||
pub use state::Smallvil;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
init_logging();
|
||||
|
||||
let mut event_loop: EventLoop<Smallvil> = EventLoop::try_new()?;
|
||||
|
||||
let display: Display<Smallvil> = Display::new()?;
|
||||
|
||||
let mut state = Smallvil::new(&mut event_loop, display);
|
||||
|
||||
// Open a Wayland/X11 window for our nested compositor
|
||||
crate::winit::init_winit(&mut event_loop, &mut state)?;
|
||||
|
||||
// Set WAYLAND_DISPLAY to our socket name, so child processes connect to Smallvil rather
|
||||
// than the host compositor
|
||||
unsafe { std::env::set_var("WAYLAND_DISPLAY", &state.socket_name) };
|
||||
|
||||
// Spawn a test client, that will run under Smallvil
|
||||
spawn_client();
|
||||
|
||||
event_loop.run(None, &mut state, move |_| {
|
||||
// Smallvil is running
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_logging() {
|
||||
if let Ok(env_filter) = tracing_subscriber::EnvFilter::try_from_default_env() {
|
||||
tracing_subscriber::fmt().with_env_filter(env_filter).init();
|
||||
} else {
|
||||
tracing_subscriber::fmt().init();
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn_client() {
|
||||
let mut args = std::env::args().skip(1);
|
||||
let flag = args.next();
|
||||
let arg = args.next();
|
||||
|
||||
match (flag.as_deref(), arg) {
|
||||
(Some("-c") | Some("--command"), Some(command)) => {
|
||||
std::process::Command::new(command).spawn().ok();
|
||||
}
|
||||
_ => {
|
||||
std::process::Command::new("weston-terminal").spawn().ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+168
@@ -0,0 +1,168 @@
|
||||
use std::{ffi::OsString, sync::Arc};
|
||||
|
||||
use smithay::{
|
||||
desktop::{PopupManager, Space, Window, WindowSurfaceType},
|
||||
input::{Seat, SeatState},
|
||||
reexports::{
|
||||
calloop::{EventLoop, Interest, LoopSignal, Mode, PostAction, generic::Generic},
|
||||
wayland_server::{
|
||||
Display, DisplayHandle,
|
||||
backend::{ClientData, ClientId, DisconnectReason},
|
||||
protocol::wl_surface::WlSurface,
|
||||
},
|
||||
},
|
||||
utils::{Logical, Point},
|
||||
wayland::{
|
||||
compositor::{CompositorClientState, CompositorState},
|
||||
output::OutputManagerState,
|
||||
selection::data_device::DataDeviceState,
|
||||
shell::xdg::XdgShellState,
|
||||
shm::ShmState,
|
||||
socket::ListeningSocketSource,
|
||||
},
|
||||
};
|
||||
|
||||
pub struct Smallvil {
|
||||
pub start_time: std::time::Instant,
|
||||
pub socket_name: OsString,
|
||||
pub display_handle: DisplayHandle,
|
||||
|
||||
pub space: Space<Window>,
|
||||
pub loop_signal: LoopSignal,
|
||||
|
||||
// Smithay State
|
||||
pub compositor_state: CompositorState,
|
||||
pub xdg_shell_state: XdgShellState,
|
||||
pub shm_state: ShmState,
|
||||
pub output_manager_state: OutputManagerState,
|
||||
pub seat_state: SeatState<Smallvil>,
|
||||
pub data_device_state: DataDeviceState,
|
||||
pub popups: PopupManager,
|
||||
|
||||
pub seat: Seat<Self>,
|
||||
}
|
||||
|
||||
impl Smallvil {
|
||||
pub fn new(event_loop: &mut EventLoop<Self>, display: Display<Self>) -> Self {
|
||||
let start_time = std::time::Instant::now();
|
||||
|
||||
let dh = display.handle();
|
||||
|
||||
// Here we initialize implementations of some wayland protocols
|
||||
// Some of them require us to implement traits on the Smallvil state,
|
||||
// you can find those implementations in the `crate::handlers` module
|
||||
|
||||
// Initialize protocols needed for displaying windows
|
||||
let compositor_state = CompositorState::new::<Self>(&dh);
|
||||
let xdg_shell_state = XdgShellState::new::<Self>(&dh);
|
||||
let shm_state = ShmState::new::<Self>(&dh, vec![]);
|
||||
let popups = PopupManager::default();
|
||||
|
||||
let output_manager_state = OutputManagerState::new_with_xdg_output::<Self>(&dh);
|
||||
|
||||
// Data device is responsible for clipboard and drag-and-drop
|
||||
let data_device_state = DataDeviceState::new::<Self>(&dh);
|
||||
|
||||
// A seat is a group of keyboards, pointer and touch devices.
|
||||
// A seat typically has a pointer and maintains a keyboard focus and a pointer focus.
|
||||
let mut seat_state = SeatState::new();
|
||||
let mut seat: Seat<Self> = seat_state.new_wl_seat(&dh, "winit");
|
||||
|
||||
// Notify clients that we have a keyboard, for the sake of the example we assume that keyboard is always present.
|
||||
// You may want to track keyboard hot-plug in real compositor.
|
||||
seat.add_keyboard(Default::default(), 200, 25).unwrap();
|
||||
|
||||
// Notify clients that we have a pointer (mouse)
|
||||
// Here we assume that there is always pointer plugged in
|
||||
seat.add_pointer();
|
||||
|
||||
// A space represents a two-dimensional plane. Windows and Outputs can be mapped onto it.
|
||||
//
|
||||
// Windows get a position and stacking order through mapping.
|
||||
// Outputs become views of a part of the Space and can be rendered via Space::render_output.
|
||||
let space = Space::default();
|
||||
|
||||
// Setup a wayland socket that will be used to accept clients
|
||||
let socket_name = Self::init_wayland_listener(display, event_loop);
|
||||
|
||||
// Get the loop signal, used to stop the event loop
|
||||
let loop_signal = event_loop.get_signal();
|
||||
|
||||
Self {
|
||||
start_time,
|
||||
display_handle: dh,
|
||||
|
||||
space,
|
||||
loop_signal,
|
||||
socket_name,
|
||||
|
||||
compositor_state,
|
||||
xdg_shell_state,
|
||||
shm_state,
|
||||
output_manager_state,
|
||||
seat_state,
|
||||
data_device_state,
|
||||
popups,
|
||||
seat,
|
||||
}
|
||||
}
|
||||
|
||||
fn init_wayland_listener(display: Display<Smallvil>, event_loop: &mut EventLoop<Self>) -> OsString {
|
||||
// Creates a new listening socket, automatically choosing the next available `wayland` socket name.
|
||||
let listening_socket = ListeningSocketSource::new_auto().unwrap();
|
||||
|
||||
// Get the name of the listening socket.
|
||||
// Clients will connect to this socket.
|
||||
let socket_name = listening_socket.socket_name().to_os_string();
|
||||
|
||||
let loop_handle = event_loop.handle();
|
||||
|
||||
loop_handle
|
||||
.insert_source(listening_socket, move |client_stream, _, state| {
|
||||
// Inside the callback, you should insert the client into the display.
|
||||
//
|
||||
// You may also associate some data with the client when inserting the client.
|
||||
state
|
||||
.display_handle
|
||||
.insert_client(client_stream, Arc::new(ClientState::default()))
|
||||
.unwrap();
|
||||
})
|
||||
.expect("Failed to init the wayland event source.");
|
||||
|
||||
// You also need to add the display itself to the event loop, so that client events will be processed by wayland-server.
|
||||
loop_handle
|
||||
.insert_source(
|
||||
Generic::new(display, Interest::READ, Mode::Level),
|
||||
|_, display, state| {
|
||||
// Safety: we don't drop the display
|
||||
unsafe {
|
||||
display.get_mut().dispatch_clients(state).unwrap();
|
||||
}
|
||||
Ok(PostAction::Continue)
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
socket_name
|
||||
}
|
||||
|
||||
pub fn surface_under(&self, pos: Point<f64, Logical>) -> Option<(WlSurface, Point<f64, Logical>)> {
|
||||
self.space.element_under(pos).and_then(|(window, location)| {
|
||||
window
|
||||
.surface_under(pos - location.to_f64(), WindowSurfaceType::ALL)
|
||||
.map(|(s, p)| (s, (p + location).to_f64()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Data associated with a wayland client that connects to Smallvil.
|
||||
/// One instance of this type per client.
|
||||
#[derive(Default)]
|
||||
pub struct ClientState {
|
||||
pub compositor_state: CompositorClientState,
|
||||
}
|
||||
|
||||
impl ClientData for ClientState {
|
||||
fn initialized(&self, _client_id: ClientId) {}
|
||||
fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {}
|
||||
}
|
||||
Vendored
+110
@@ -0,0 +1,110 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use smithay::{
|
||||
backend::{
|
||||
renderer::{
|
||||
damage::OutputDamageTracker, element::surface::WaylandSurfaceRenderElement, gles::GlesRenderer,
|
||||
},
|
||||
winit::{self, WinitEvent},
|
||||
},
|
||||
output::{Mode, Output, PhysicalProperties, Subpixel},
|
||||
reexports::calloop::EventLoop,
|
||||
utils::{Rectangle, Transform},
|
||||
};
|
||||
|
||||
use crate::Smallvil;
|
||||
|
||||
pub fn init_winit(
|
||||
event_loop: &mut EventLoop<Smallvil>,
|
||||
state: &mut Smallvil,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let (mut backend, winit) = winit::init()?;
|
||||
|
||||
let mode = Mode {
|
||||
size: backend.window_size(),
|
||||
refresh: 60_000,
|
||||
};
|
||||
|
||||
let output = Output::new(
|
||||
"winit".to_string(),
|
||||
PhysicalProperties {
|
||||
size: (0, 0).into(),
|
||||
subpixel: Subpixel::Unknown,
|
||||
make: "Smithay".into(),
|
||||
model: "Winit".into(),
|
||||
serial_number: "Unknown".into(),
|
||||
},
|
||||
);
|
||||
let _global = output.create_global::<Smallvil>(&state.display_handle);
|
||||
output.change_current_state(Some(mode), Some(Transform::Flipped180), None, Some((0, 0).into()));
|
||||
output.set_preferred(mode);
|
||||
|
||||
state.space.map_output(&output, (0, 0));
|
||||
|
||||
let mut damage_tracker = OutputDamageTracker::from_output(&output);
|
||||
|
||||
event_loop.handle().insert_source(winit, move |event, _, state| {
|
||||
match event {
|
||||
WinitEvent::Resized { size, .. } => {
|
||||
output.change_current_state(
|
||||
Some(Mode {
|
||||
size,
|
||||
refresh: 60_000,
|
||||
}),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
WinitEvent::Input(event) => state.process_input_event(event),
|
||||
WinitEvent::Redraw => {
|
||||
let size = backend.window_size();
|
||||
let damage = Rectangle::from_size(size);
|
||||
|
||||
{
|
||||
let (renderer, mut framebuffer) = backend.bind().unwrap();
|
||||
smithay::desktop::space::render_output::<
|
||||
_,
|
||||
WaylandSurfaceRenderElement<GlesRenderer>,
|
||||
_,
|
||||
_,
|
||||
>(
|
||||
&output,
|
||||
renderer,
|
||||
&mut framebuffer,
|
||||
1.0,
|
||||
0,
|
||||
[&state.space],
|
||||
&[],
|
||||
&mut damage_tracker,
|
||||
[0.1, 0.1, 0.1, 1.0],
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
backend.submit(Some(&[damage])).unwrap();
|
||||
|
||||
state.space.elements().for_each(|window| {
|
||||
window.send_frame(
|
||||
&output,
|
||||
state.start_time.elapsed(),
|
||||
Some(Duration::ZERO),
|
||||
|_, _| Some(output.clone()),
|
||||
)
|
||||
});
|
||||
|
||||
state.space.refresh();
|
||||
state.popups.cleanup();
|
||||
let _ = state.display_handle.flush_clients();
|
||||
|
||||
// Ask for redraw to schedule new frame.
|
||||
backend.window().request_redraw();
|
||||
}
|
||||
WinitEvent::CloseRequested => {
|
||||
state.loop_signal.stop();
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "smithay-drm-extras"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
authors = ["Bartłomiej Maryńczak <[email protected]>"]
|
||||
|
||||
[dependencies]
|
||||
libdisplay-info = { version = "0.3.0", optional = true }
|
||||
drm = { version = "0.14.0" }
|
||||
|
||||
[features]
|
||||
default = ["display-info"]
|
||||
display-info = ["libdisplay-info"]
|
||||
|
||||
[dev-dependencies.smithay]
|
||||
path = "../"
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Bartłomiej Maryńczak
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
# Smithay DRM Extras
|
||||
|
||||
This crate contains some extra abstractions and helpers over DRM
|
||||
|
||||
- `display_info` module is responsible for extraction of information from DRM connectors (`model` and `manufacturer`)
|
||||
- `drm_scanner` module contains helpers for detecting connector connected and disconnected events as well as mapping crtc to them.
|
||||
- `ConnectorScanner` is responsible for tracking connected/disconnected events.
|
||||
- `CrtcMapper` trait and `SimpleCrtcMapper` are meant for mapping crtc to connector.
|
||||
- `DrmScanner<CrtcMapper>` combines two above into single abstraction. If it does not fit your needs you can always drop down to using `ConnectScanner` alone.
|
||||
@@ -0,0 +1,220 @@
|
||||
use std::{collections::HashMap, path::PathBuf, time::Duration};
|
||||
|
||||
use smithay::reexports::drm::control::{connector, crtc};
|
||||
use smithay_drm_extras::{
|
||||
display_info,
|
||||
drm_scanner::{self, DrmScanEvent},
|
||||
};
|
||||
|
||||
use smithay::{
|
||||
backend::{
|
||||
drm::{self, DrmDeviceFd, DrmNode},
|
||||
session::{Session, libseat::LibSeatSession},
|
||||
udev::{UdevBackend, UdevEvent},
|
||||
},
|
||||
reexports::{
|
||||
calloop::{EventLoop, LoopHandle, timer::Timer},
|
||||
rustix::fs::OFlags,
|
||||
},
|
||||
utils::DeviceFd,
|
||||
};
|
||||
|
||||
struct State {
|
||||
handle: LoopHandle<'static, Self>,
|
||||
session: LibSeatSession,
|
||||
devices: HashMap<DrmNode, Device>,
|
||||
}
|
||||
|
||||
struct Device {
|
||||
drm: drm::DrmDevice,
|
||||
drm_scanner: drm_scanner::DrmScanner,
|
||||
surfaces: HashMap<crtc::Handle, Surface>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Surface {
|
||||
// Your gbm surface stuff goes here
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut event_loop = EventLoop::<State>::try_new()?;
|
||||
|
||||
let (session, notify) = LibSeatSession::new().unwrap();
|
||||
|
||||
event_loop.handle().insert_source(notify, |_, _, _| {}).unwrap();
|
||||
|
||||
let mut state = State {
|
||||
handle: event_loop.handle(),
|
||||
session,
|
||||
devices: Default::default(),
|
||||
};
|
||||
|
||||
init_udev(&mut state);
|
||||
|
||||
event_loop
|
||||
.handle()
|
||||
.insert_source(Timer::from_duration(Duration::from_secs(5)), |_, _, _| {
|
||||
panic!("Aborted");
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
event_loop.run(None, &mut state, |_data| {})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn init_udev(state: &mut State) {
|
||||
let backend = UdevBackend::new(state.session.seat()).unwrap();
|
||||
for (device_id, path) in backend.device_list() {
|
||||
state.on_udev_event(UdevEvent::Added {
|
||||
device_id,
|
||||
path: path.to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
state
|
||||
.handle
|
||||
.insert_source(backend, |event, _, state| state.on_udev_event(event))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn on_udev_event(&mut self, event: UdevEvent) {
|
||||
match event {
|
||||
UdevEvent::Added { device_id, path } => {
|
||||
if let Ok(node) = DrmNode::from_dev_id(device_id) {
|
||||
self.device_added(node, path);
|
||||
}
|
||||
}
|
||||
UdevEvent::Changed { device_id } => {
|
||||
if let Ok(node) = DrmNode::from_dev_id(device_id) {
|
||||
self.device_changed(node);
|
||||
}
|
||||
}
|
||||
UdevEvent::Removed { device_id } => {
|
||||
if let Ok(node) = DrmNode::from_dev_id(device_id) {
|
||||
self.device_removed(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn device_added(&mut self, node: DrmNode, path: PathBuf) {
|
||||
let fd = self
|
||||
.session
|
||||
.open(
|
||||
&path,
|
||||
OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY | OFlags::NONBLOCK,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let fd = DrmDeviceFd::new(DeviceFd::from(fd));
|
||||
|
||||
let (drm, drm_notifier) = drm::DrmDevice::new(fd, false).unwrap();
|
||||
|
||||
self.handle
|
||||
.insert_source(drm_notifier, move |event, _, _| match event {
|
||||
drm::DrmEvent::VBlank(_) => {}
|
||||
drm::DrmEvent::Error(_) => {}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
self.devices.insert(
|
||||
node,
|
||||
Device {
|
||||
drm,
|
||||
drm_scanner: Default::default(),
|
||||
surfaces: Default::default(),
|
||||
},
|
||||
);
|
||||
|
||||
self.device_changed(node);
|
||||
}
|
||||
|
||||
fn connector_connected(&mut self, node: DrmNode, connector: connector::Info, crtc: crtc::Handle) {
|
||||
if let Some(device) = self.devices.get_mut(&node) {
|
||||
let name = format!("{}-{}", connector.interface().as_str(), connector.interface_id());
|
||||
|
||||
let display_info = display_info::for_connector(&device.drm, connector.handle());
|
||||
|
||||
let manufacturer = display_info
|
||||
.as_ref()
|
||||
.and_then(|info| info.make())
|
||||
.unwrap_or_else(|| "Unknown".into());
|
||||
|
||||
let model = display_info
|
||||
.as_ref()
|
||||
.and_then(|info| info.model())
|
||||
.unwrap_or_else(|| "Unknown".into());
|
||||
|
||||
println!("Connected:");
|
||||
dbg!(name);
|
||||
dbg!(manufacturer);
|
||||
dbg!(model);
|
||||
|
||||
device.surfaces.insert(crtc, Surface {});
|
||||
}
|
||||
}
|
||||
|
||||
fn connector_disconnected(&mut self, node: DrmNode, connector: connector::Info, crtc: crtc::Handle) {
|
||||
let name = format!("{}-{}", connector.interface().as_str(), connector.interface_id());
|
||||
|
||||
println!("Disconnected:");
|
||||
dbg!(name);
|
||||
|
||||
if let Some(device) = self.devices.get_mut(&node) {
|
||||
device.surfaces.remove(&crtc);
|
||||
}
|
||||
}
|
||||
|
||||
fn device_changed(&mut self, node: DrmNode) {
|
||||
let device = if let Some(device) = self.devices.get_mut(&node) {
|
||||
device
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
for event in device
|
||||
.drm_scanner
|
||||
.scan_connectors(&device.drm)
|
||||
.expect("failed to scan connectors")
|
||||
{
|
||||
match event {
|
||||
DrmScanEvent::Connected {
|
||||
connector,
|
||||
crtc: Some(crtc),
|
||||
} => {
|
||||
self.connector_connected(node, connector, crtc);
|
||||
}
|
||||
DrmScanEvent::Disconnected {
|
||||
connector,
|
||||
crtc: Some(crtc),
|
||||
} => {
|
||||
self.connector_disconnected(node, connector, crtc);
|
||||
}
|
||||
DrmScanEvent::Changed { .. } => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn device_removed(&mut self, node: DrmNode) {
|
||||
let device = if let Some(device) = self.devices.get_mut(&node) {
|
||||
device
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let crtcs: Vec<_> = device
|
||||
.drm_scanner
|
||||
.crtcs()
|
||||
.map(|(info, crtc)| (info.clone(), crtc))
|
||||
.collect();
|
||||
|
||||
for (connector, crtc) in crtcs {
|
||||
self.connector_disconnected(node, connector, crtc);
|
||||
}
|
||||
|
||||
self.devices.remove(&node);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
//! # EDID - Extended Display Identification Data
|
||||
//!
|
||||
//! This module is meant to help with extraction of EDID data from connectors
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # mod helpers { include!("./docs/doctest_helpers.rs"); };
|
||||
//! # let drm_device: helpers::FakeDevice = todo!();
|
||||
//! # let connector = todo!();
|
||||
//! use smithay_drm_extras::display_info;
|
||||
//!
|
||||
//! let info = display_info::for_connector(&drm_device, connector).unwrap();
|
||||
//!
|
||||
//! println!("Monitor name: {:?}", info.model());
|
||||
//! println!("Manufacturer name: {:?}", info.make());
|
||||
//! ```
|
||||
|
||||
use drm::control::{Device as ControlDevice, connector};
|
||||
use libdisplay_info::info::Info;
|
||||
|
||||
/// Try to read the [`Info`] from the connector EDID property
|
||||
pub fn for_connector(device: &impl ControlDevice, connector: connector::Handle) -> Option<Info> {
|
||||
let props = device.get_properties(connector).ok()?;
|
||||
|
||||
let (info, value) = props
|
||||
.into_iter()
|
||||
.filter_map(|(handle, value)| {
|
||||
let info = device.get_property(handle).ok()?;
|
||||
|
||||
Some((info, value))
|
||||
})
|
||||
.find(|(info, _)| info.name().to_str() == Ok("EDID"))?;
|
||||
|
||||
let blob = info.value_type().convert_value(value).as_blob()?;
|
||||
let data = device.get_property_blob(blob).ok()?;
|
||||
|
||||
Info::parse_edid(&data).ok()
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
pub struct FakeDevice;
|
||||
impl std::os::unix::prelude::AsFd for FakeDevice {
|
||||
fn as_fd(&self) -> std::os::unix::prelude::BorrowedFd<'_> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
impl drm::Device for FakeDevice {}
|
||||
impl drm::control::Device for FakeDevice {}
|
||||
@@ -0,0 +1,244 @@
|
||||
//! # Drm Scanner
|
||||
//!
|
||||
//! - [`ConnectorScanner`] is responsible for tracking connected/disconnected events.
|
||||
//! - [`CrtcMapper`] trait and [`SimpleCrtcMapper`] are meant for mapping crtc to connector.
|
||||
//! - [`DrmScanner`] combines two above into single abstraction.
|
||||
//! If it does not fit your needs you can always drop down to using [`ConnectScanner`] alone.
|
||||
//!
|
||||
//! ### Example
|
||||
//! ```no_run
|
||||
//! # mod helpers { include!("./docs/doctest_helpers.rs"); };
|
||||
//! # let drm_device: helpers::FakeDevice = todo!();
|
||||
//! use smithay_drm_extras::drm_scanner::{DrmScanner, DrmScanEvent};
|
||||
//!
|
||||
//! let mut scanner: DrmScanner = DrmScanner::new();
|
||||
//!
|
||||
//! for event in scanner.scan_connectors(&drm_device).expect("failed to scan connectors") {
|
||||
//! match event {
|
||||
//! DrmScanEvent::Connected { .. } => {},
|
||||
//! DrmScanEvent::Disconnected { .. } => {},
|
||||
//! DrmScanEvent::Changed { .. } => {},
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
use std::collections::HashMap;
|
||||
|
||||
use drm::control::{Device as ControlDevice, connector, crtc};
|
||||
|
||||
mod connector_scanner;
|
||||
pub use connector_scanner::{ConnectorScanEvent, ConnectorScanResult, ConnectorScanner};
|
||||
|
||||
mod crtc_mapper;
|
||||
pub use crtc_mapper::{CrtcMapper, SimpleCrtcMapper};
|
||||
|
||||
/// Drm Scanner
|
||||
///
|
||||
/// Wrapper over [`ConnectorScanner`] and [`CrtcMapper`]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DrmScanner<Mapper = SimpleCrtcMapper>
|
||||
where
|
||||
Mapper: CrtcMapper,
|
||||
{
|
||||
connectors: ConnectorScanner,
|
||||
crtc_mapper: Mapper,
|
||||
}
|
||||
|
||||
impl<M> DrmScanner<M>
|
||||
where
|
||||
M: CrtcMapper + Default,
|
||||
{
|
||||
/// Create new DrmScanner with default CRTC mapper.
|
||||
pub fn new() -> Self {
|
||||
Self::new_with_mapper(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<M> DrmScanner<M>
|
||||
where
|
||||
M: CrtcMapper,
|
||||
{
|
||||
/// Create new DrmScanner with custom CRTC mapper
|
||||
pub fn new_with_mapper(mapper: M) -> Self {
|
||||
Self {
|
||||
connectors: Default::default(),
|
||||
crtc_mapper: mapper,
|
||||
}
|
||||
}
|
||||
|
||||
/// [`CrtcMapper`] getter
|
||||
pub fn crtc_mapper(&self) -> &M {
|
||||
&self.crtc_mapper
|
||||
}
|
||||
|
||||
/// Muttable [`CrtcMapper`] getter
|
||||
pub fn crtc_mapper_mut(&mut self) -> &mut M {
|
||||
&mut self.crtc_mapper
|
||||
}
|
||||
|
||||
/// Scan connectors to find out what has changed since last call to this method.
|
||||
///
|
||||
/// Returns [`DrmScanResult`] that contains added and removed connectors,
|
||||
/// and CRTCs that got assigned to them.
|
||||
///
|
||||
/// Should be called on every device changed event
|
||||
///
|
||||
/// ```no_run
|
||||
/// # mod helpers { include!("./docs/doctest_helpers.rs"); };
|
||||
/// # let drm_device: helpers::FakeDevice = todo!();
|
||||
/// use smithay_drm_extras::drm_scanner::{DrmScanner, DrmScanEvent};
|
||||
///
|
||||
/// let mut scanner: DrmScanner = DrmScanner::new();
|
||||
/// let res = scanner.scan_connectors(&drm_device).expect("failed to scan connectors");
|
||||
///
|
||||
/// // You can extract scan info manually
|
||||
/// println!("Plugged {} connectors", res.connected.len());
|
||||
/// println!("Unplugged {} connectors", res.disconnected.len());
|
||||
///
|
||||
/// // Or simply iterate over it
|
||||
/// for event in res {
|
||||
/// match event {
|
||||
/// DrmScanEvent::Connected { .. } => {},
|
||||
/// DrmScanEvent::Disconnected { .. } => {},
|
||||
/// DrmScanEvent::Changed { .. } => {},
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn scan_connectors(&mut self, drm: &impl ControlDevice) -> std::io::Result<DrmScanResult> {
|
||||
let scan = self.connectors.scan(drm)?;
|
||||
|
||||
let removed = scan
|
||||
.disconnected
|
||||
.into_iter()
|
||||
.map(|info| {
|
||||
let crtc = self.crtc_mapper.crtc_for_connector(&info.handle());
|
||||
(info, crtc)
|
||||
})
|
||||
.collect();
|
||||
|
||||
self.crtc_mapper.map(drm, self.connectors.connectors().values());
|
||||
|
||||
let added = scan
|
||||
.connected
|
||||
.into_iter()
|
||||
.map(|info| {
|
||||
let crtc = self.crtc_mapper.crtc_for_connector(&info.handle());
|
||||
(info, crtc)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let changed = scan
|
||||
.changed
|
||||
.into_iter()
|
||||
.map(|info| {
|
||||
let crtc = self.crtc_mapper.crtc_for_connector(&info.handle());
|
||||
(info, crtc)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(DrmScanResult {
|
||||
disconnected: removed,
|
||||
connected: added,
|
||||
changed,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get map of all connectors, connected and disconnected ones.
|
||||
pub fn connectors(&self) -> &HashMap<connector::Handle, connector::Info> {
|
||||
self.connectors.connectors()
|
||||
}
|
||||
|
||||
/// Get CRTC that is mapped to supplied connector
|
||||
///
|
||||
/// This will query underlying [`CrtcMapper`]
|
||||
pub fn crtc_for_connector(&self, connector: &connector::Handle) -> Option<crtc::Handle> {
|
||||
self.crtc_mapper.crtc_for_connector(connector)
|
||||
}
|
||||
|
||||
/// Get iterator over all `connector -> CRTC` mappings
|
||||
pub fn crtcs(&self) -> impl Iterator<Item = (&connector::Info, crtc::Handle)> {
|
||||
self.connectors()
|
||||
.iter()
|
||||
.filter_map(|(handle, info)| Some((info, self.crtc_for_connector(handle)?)))
|
||||
}
|
||||
}
|
||||
|
||||
type DrmScanItem = (connector::Info, Option<crtc::Handle>);
|
||||
|
||||
/// Result of [`DrmScanner::scan_connectors`]
|
||||
///
|
||||
/// You can use `added` and `removed` fields of this result manually,
|
||||
/// or you can just iterate (using [`IntoIterator`] or [`DrmScanResult::iter`])
|
||||
/// over this result to get [`DrmScanEvent`].
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct DrmScanResult {
|
||||
/// Connectors that got plugged in since last scan
|
||||
pub connected: Vec<DrmScanItem>,
|
||||
/// Connectors that got unplugged since last scan
|
||||
pub disconnected: Vec<DrmScanItem>,
|
||||
/// Connectors whose mode list changed while staying connected
|
||||
pub changed: Vec<DrmScanItem>,
|
||||
}
|
||||
|
||||
impl DrmScanResult {
|
||||
/// Creates event iterator for this result
|
||||
///
|
||||
/// Internally this clones the data so it is equivalent to [`IntoIterator`]
|
||||
pub fn iter(&self) -> impl Iterator<Item = DrmScanEvent> {
|
||||
self.clone().into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
/// Created from [`DrmScanResult`], informs about connector events.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DrmScanEvent {
|
||||
/// A new connector got plugged in since last scan
|
||||
Connected {
|
||||
/// Info about connected connector
|
||||
connector: connector::Info,
|
||||
/// Crtc that got mapped to this connector
|
||||
crtc: Option<crtc::Handle>,
|
||||
},
|
||||
/// A connector got unplugged in since last scan
|
||||
Disconnected {
|
||||
/// Info about disconnected connector
|
||||
connector: connector::Info,
|
||||
/// Crtc that is no longer mapped to this connector
|
||||
crtc: Option<crtc::Handle>,
|
||||
},
|
||||
/// The connector's mode list changed while staying connected
|
||||
Changed {
|
||||
/// Info about the connector whose modes changed
|
||||
connector: connector::Info,
|
||||
/// Crtc that is mapped to this connector
|
||||
crtc: Option<crtc::Handle>,
|
||||
},
|
||||
}
|
||||
|
||||
impl DrmScanEvent {
|
||||
fn connected((connector, crtc): (connector::Info, Option<crtc::Handle>)) -> Self {
|
||||
DrmScanEvent::Connected { connector, crtc }
|
||||
}
|
||||
|
||||
fn disconnected((connector, crtc): (connector::Info, Option<crtc::Handle>)) -> Self {
|
||||
DrmScanEvent::Disconnected { connector, crtc }
|
||||
}
|
||||
|
||||
fn changed((connector, crtc): (connector::Info, Option<crtc::Handle>)) -> Self {
|
||||
DrmScanEvent::Changed { connector, crtc }
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for DrmScanResult {
|
||||
type Item = DrmScanEvent;
|
||||
type IntoIter = std::vec::IntoIter<DrmScanEvent>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.disconnected
|
||||
.into_iter()
|
||||
.map(DrmScanEvent::disconnected)
|
||||
.chain(self.connected.into_iter().map(DrmScanEvent::connected))
|
||||
.chain(self.changed.into_iter().map(DrmScanEvent::changed))
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use drm::control::{Device as ControlDevice, connector};
|
||||
|
||||
/// Responsible for tracking connected/disconnected events.
|
||||
///
|
||||
/// ### Example
|
||||
/// ```no_run
|
||||
/// # mod helpers { include!("../docs/doctest_helpers.rs"); };
|
||||
/// # let drm_device: helpers::FakeDevice = todo!();
|
||||
/// use smithay_drm_extras::drm_scanner::{ConnectorScanner, ConnectorScanEvent};
|
||||
///
|
||||
/// let mut scanner = ConnectorScanner::new();
|
||||
///
|
||||
/// for event in scanner.scan(&drm_device).expect("failed to scan connectors") {
|
||||
/// match event {
|
||||
/// ConnectorScanEvent::Connected(conn) => {},
|
||||
/// ConnectorScanEvent::Disconnected(conn) => {},
|
||||
/// ConnectorScanEvent::Changed(conn) => {},
|
||||
/// }
|
||||
/// }
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ConnectorScanner {
|
||||
connectors: HashMap<connector::Handle, connector::Info>,
|
||||
}
|
||||
|
||||
impl ConnectorScanner {
|
||||
/// Create new [`ConnectorScanner`]
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
/// Should be called on every device changed event
|
||||
pub fn scan(&mut self, drm: &impl ControlDevice) -> std::io::Result<ConnectorScanResult> {
|
||||
let res_handles = drm.resource_handles()?;
|
||||
let connector_handles = res_handles.connectors();
|
||||
|
||||
let mut added = Vec::new();
|
||||
let mut removed = Vec::new();
|
||||
let mut changed = Vec::new();
|
||||
|
||||
for conn in connector_handles
|
||||
.iter()
|
||||
.filter_map(|conn| drm.get_connector(*conn, true).ok())
|
||||
{
|
||||
let curr_state = conn.state();
|
||||
|
||||
use connector::State;
|
||||
if let Some(old) = self.connectors.insert(conn.handle(), conn.clone()) {
|
||||
match (old.state(), curr_state) {
|
||||
(State::Connected, State::Disconnected) => removed.push(conn),
|
||||
(State::Disconnected | State::Unknown, State::Connected) => added.push(conn),
|
||||
//
|
||||
// Emit Changed when the mode list changes while staying
|
||||
// connected. This covers the EDID race where the kernel
|
||||
// initially reports a connector as Connected with an
|
||||
// empty or fallback mode list (EDID not yet read) and a
|
||||
// later rescan finds the real modes populated.
|
||||
(State::Connected, State::Connected) => {
|
||||
if old.modes() != conn.modes() {
|
||||
changed.push(conn);
|
||||
}
|
||||
}
|
||||
(State::Disconnected, State::Disconnected) => {}
|
||||
//
|
||||
(State::Unknown, _) => {}
|
||||
(_, State::Unknown) => {}
|
||||
}
|
||||
} else if curr_state == State::Connected {
|
||||
added.push(conn)
|
||||
}
|
||||
}
|
||||
|
||||
// Check for connectors that disappeared entirely (e.g., USB-C dock disconnected while
|
||||
// suspended). These won't be in resource_handles() anymore, so handle them specially.
|
||||
let connector_handles_set: HashSet<_> = connector_handles.iter().copied().collect();
|
||||
let to_remove: Vec<_> = self
|
||||
.connectors
|
||||
.keys()
|
||||
.filter(|handle| !connector_handles_set.contains(handle))
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
for handle in to_remove {
|
||||
if let Some(info) = self.connectors.remove(&handle) {
|
||||
if info.state() == connector::State::Connected {
|
||||
removed.push(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ConnectorScanResult {
|
||||
connected: added,
|
||||
disconnected: removed,
|
||||
changed,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get map of all connectors, connected and disconnected ones.
|
||||
pub fn connectors(&self) -> &HashMap<connector::Handle, connector::Info> {
|
||||
&self.connectors
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of [`ConnectorScanner::scan`]
|
||||
///
|
||||
/// You can use `added` and `removed` fields of this result manually,
|
||||
/// or you can just iterate (using [`IntoIterator`] or [`ConnectorScanResult::iter`])
|
||||
/// over this result to get [`ConnectorScanEvent`].
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct ConnectorScanResult {
|
||||
/// Connectors that got plugged in since last scan
|
||||
pub connected: Vec<connector::Info>,
|
||||
/// Connectors that got unplugged since last scan
|
||||
pub disconnected: Vec<connector::Info>,
|
||||
/// Connectors whose mode list changed while staying connected
|
||||
pub changed: Vec<connector::Info>,
|
||||
}
|
||||
|
||||
/// Created from [`ConnectorScanResult`], informs about connector events.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ConnectorScanEvent {
|
||||
/// A new connector got plugged in since last scan
|
||||
Connected(connector::Info),
|
||||
/// A connector got unplugged in since last scan
|
||||
Disconnected(connector::Info),
|
||||
/// The connector's mode list changed while staying connected
|
||||
Changed(connector::Info),
|
||||
}
|
||||
|
||||
impl ConnectorScanResult {
|
||||
/// Creates event iterator for this result
|
||||
///
|
||||
/// Internally this clones the data so it is equivalent to [`IntoIterator`]
|
||||
pub fn iter(&self) -> impl Iterator<Item = ConnectorScanEvent> {
|
||||
self.clone().into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for ConnectorScanResult {
|
||||
type Item = ConnectorScanEvent;
|
||||
type IntoIter = std::vec::IntoIter<ConnectorScanEvent>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.disconnected
|
||||
.into_iter()
|
||||
.map(ConnectorScanEvent::Disconnected)
|
||||
.chain(self.connected.into_iter().map(ConnectorScanEvent::Connected))
|
||||
.chain(self.changed.into_iter().map(ConnectorScanEvent::Changed))
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use drm::control::{Device as ControlDevice, connector, crtc};
|
||||
|
||||
/// CRTC Mapper trait
|
||||
///
|
||||
/// It exists to allow custom mappers in [`super::DrmScanner`].
|
||||
///
|
||||
/// It is responsible for mapping CRTCs to connectors.
|
||||
/// For each connector it has to pick suitable CRTC.
|
||||
pub trait CrtcMapper {
|
||||
/// Request mapping of CRTCs to supplied connectors
|
||||
///
|
||||
/// Usually called in response to udev device changed event,
|
||||
/// or on device init.
|
||||
fn map<'a>(
|
||||
&mut self,
|
||||
drm: &impl ControlDevice,
|
||||
connectors: impl Iterator<Item = &'a connector::Info> + Clone,
|
||||
);
|
||||
|
||||
/// Query CRTC mapped to supplied connector
|
||||
fn crtc_for_connector(&self, connector: &connector::Handle) -> Option<crtc::Handle>;
|
||||
}
|
||||
|
||||
/// Simple CRTC Mapper
|
||||
///
|
||||
/// This is basic mapper that simply chooses one CRTC for every connector.
|
||||
///
|
||||
/// It is also capable of recovering mappings that were used by display manger or tty
|
||||
/// before the compositor was started up.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct SimpleCrtcMapper {
|
||||
crtcs: HashMap<connector::Handle, crtc::Handle>,
|
||||
}
|
||||
|
||||
impl SimpleCrtcMapper {
|
||||
/// Create new [`SimpleCrtcMapper`]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn is_taken(&self, crtc: &crtc::Handle) -> bool {
|
||||
self.crtcs.values().any(|v| v == crtc)
|
||||
}
|
||||
|
||||
fn is_available(&self, crtc: &crtc::Handle) -> bool {
|
||||
!self.is_taken(crtc)
|
||||
}
|
||||
|
||||
fn restored_for_connector(
|
||||
&self,
|
||||
drm: &impl ControlDevice,
|
||||
connector: &connector::Info,
|
||||
) -> Option<crtc::Handle> {
|
||||
let encoder = connector.current_encoder()?;
|
||||
let encoder = drm.get_encoder(encoder).ok()?;
|
||||
let crtc = encoder.crtc()?;
|
||||
|
||||
self.is_available(&crtc).then_some(crtc)
|
||||
}
|
||||
|
||||
fn pick_next_available_for_connector(
|
||||
&self,
|
||||
drm: &impl ControlDevice,
|
||||
connector: &connector::Info,
|
||||
) -> Option<crtc::Handle> {
|
||||
let res_handles = drm.resource_handles().ok()?;
|
||||
|
||||
connector
|
||||
.encoders()
|
||||
.iter()
|
||||
.flat_map(|encoder_handle| drm.get_encoder(*encoder_handle))
|
||||
.find_map(|encoder_info| {
|
||||
res_handles
|
||||
.filter_crtcs(encoder_info.possible_crtcs())
|
||||
.into_iter()
|
||||
.find(|crtc| self.is_available(crtc))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl super::CrtcMapper for SimpleCrtcMapper {
|
||||
fn map<'a>(
|
||||
&mut self,
|
||||
drm: &impl ControlDevice,
|
||||
connectors: impl Iterator<Item = &'a connector::Info> + Clone,
|
||||
) {
|
||||
// Release CRTC reservations for every connector that is not currently connected. This
|
||||
// covers connectors that are present but disconnected, as well as connectors that have
|
||||
// vanished from the resource list entirely (e.g. DP-MST sink connectors when a dock is
|
||||
// unplugged or the system is suspended).
|
||||
let connected: HashSet<connector::Handle> = connectors
|
||||
.clone()
|
||||
.filter(|conn| conn.state() == connector::State::Connected)
|
||||
.map(|conn| conn.handle())
|
||||
.collect();
|
||||
self.crtcs.retain(|handle, _| connected.contains(handle));
|
||||
|
||||
let mut needs_crtc: Vec<&connector::Info> = connectors
|
||||
.filter(|conn| conn.state() == connector::State::Connected)
|
||||
.filter(|conn| !self.crtcs.contains_key(&conn.handle()))
|
||||
.collect();
|
||||
|
||||
needs_crtc.retain(|connector| {
|
||||
if let Some(crtc) = self.restored_for_connector(drm, connector) {
|
||||
self.crtcs.insert(connector.handle(), crtc);
|
||||
|
||||
// This connector no longer needs crtc so let's remove it
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
|
||||
for connector in needs_crtc {
|
||||
if let Some(crtc) = self.pick_next_available_for_connector(drm, connector) {
|
||||
self.crtcs.insert(connector.handle(), crtc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn crtc_for_connector(&self, connector: &connector::Handle) -> Option<crtc::Handle> {
|
||||
self.crtcs.get(connector).copied()
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
//! # Smithay DRM Extras
|
||||
//!
|
||||
//! This crate contains some extra abstractions and helpers over DRM
|
||||
//!
|
||||
//! - [`display_info`] is responsible for extraction of information from DRM connectors
|
||||
//! - [`drm_scanner`] is responsible for detecting connector connected and
|
||||
//! disconnected events, as well as mapping CRTC to them.
|
||||
//!
|
||||
//! ### Features
|
||||
//! - `display_info` - If enabled `display_info` functionality is enabled through `libdisplay-info` integration
|
||||
|
||||
#![warn(missing_docs, missing_debug_implementations)]
|
||||
|
||||
#[cfg(feature = "display-info")]
|
||||
pub mod display_info;
|
||||
pub mod drm_scanner;
|
||||
+715
@@ -0,0 +1,715 @@
|
||||
//! Module for [dmabuf](https://docs.kernel.org/driver-api/dma-buf.html) buffers.
|
||||
//!
|
||||
//! `Dmabuf`s act alike to smart pointers and can be freely cloned and passed around.
|
||||
//! Once the last `Dmabuf` reference is dropped, its file descriptor is closed and
|
||||
//! underlying resources are freed.
|
||||
//!
|
||||
//! If you want to hold on to a potentially alive dmabuf without blocking the free up
|
||||
//! of the underlying resources, you may `downgrade` a `Dmabuf` reference to a `WeakDmabuf`.
|
||||
//!
|
||||
//! This can be especially useful in resources where other parts of the stack should decide upon
|
||||
//! the lifetime of the buffer. E.g. when you are only caching associated resources for a dmabuf.
|
||||
|
||||
use calloop::generic::Generic;
|
||||
use calloop::{EventSource, Interest, Mode, PostAction};
|
||||
use rustix::ioctl::Setter;
|
||||
|
||||
use super::{Allocator, Buffer, Format, Fourcc, Modifier};
|
||||
#[cfg(feature = "backend_drm")]
|
||||
use crate::backend::drm::DrmNode;
|
||||
use crate::utils::{Buffer as BufferCoords, Size};
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
use crate::wayland::compositor::{Blocker, BlockerState};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::os::unix::io::{AsFd, BorrowedFd, OwnedFd};
|
||||
#[cfg(feature = "backend_drm")]
|
||||
use std::sync::Mutex;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::{error, fmt};
|
||||
|
||||
/// Maximum amount of planes this implementation supports
|
||||
pub const MAX_PLANES: usize = 4;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct DmabufInternal {
|
||||
/// The submitted planes
|
||||
pub planes: Vec<Plane>,
|
||||
/// The size of this buffer
|
||||
pub size: Size<i32, BufferCoords>,
|
||||
/// The format in use
|
||||
pub format: Fourcc,
|
||||
/// Format modifier
|
||||
pub modifier: Modifier,
|
||||
/// The flags applied to it
|
||||
///
|
||||
/// This is a bitflag, to be compared with the `Flags` enum re-exported by this module.
|
||||
pub flags: DmabufFlags,
|
||||
/// Presumably compatible device for buffer import
|
||||
///
|
||||
/// This is inferred from client apis, however there is no kernel api or guarantee this is correct
|
||||
#[cfg(feature = "backend_drm")]
|
||||
node: Mutex<Option<DrmNode>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Plane {
|
||||
pub fd: Arc<OwnedFd>,
|
||||
/// The plane index
|
||||
#[allow(dead_code)]
|
||||
pub plane_idx: u32,
|
||||
/// Offset from the start of the Fd
|
||||
pub offset: u32,
|
||||
/// Stride for this plane
|
||||
pub stride: u32,
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Possible flags for a DMA buffer
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct DmabufFlags: u32 {
|
||||
/// The buffer content is Y-inverted
|
||||
const Y_INVERT = 1;
|
||||
/// The buffer content is interlaced
|
||||
const INTERLACED = 2;
|
||||
/// The buffer content if interlaced is bottom-field first
|
||||
const BOTTOM_FIRST = 4;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// Strong reference to a dmabuf handle
|
||||
pub struct Dmabuf(pub(crate) Arc<DmabufInternal>);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// Weak reference to a dmabuf handle
|
||||
pub struct WeakDmabuf(pub(crate) Weak<DmabufInternal>);
|
||||
|
||||
impl PartialEq for Dmabuf {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Arc::ptr_eq(&self.0, &other.0)
|
||||
}
|
||||
}
|
||||
impl Eq for Dmabuf {}
|
||||
|
||||
impl PartialEq for WeakDmabuf {
|
||||
#[inline]
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Weak::ptr_eq(&self.0, &other.0)
|
||||
}
|
||||
}
|
||||
impl Eq for WeakDmabuf {}
|
||||
|
||||
impl Hash for Dmabuf {
|
||||
#[inline]
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
Arc::as_ptr(&self.0).hash(state)
|
||||
}
|
||||
}
|
||||
impl Hash for WeakDmabuf {
|
||||
#[inline]
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.as_ptr().hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl Buffer for Dmabuf {
|
||||
#[inline]
|
||||
fn size(&self) -> Size<i32, BufferCoords> {
|
||||
self.0.size
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn format(&self) -> Format {
|
||||
Format {
|
||||
code: self.0.format,
|
||||
modifier: self.0.modifier,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for Dmabufs
|
||||
#[derive(Debug)]
|
||||
pub struct DmabufBuilder {
|
||||
internal: DmabufInternal,
|
||||
}
|
||||
|
||||
impl DmabufBuilder {
|
||||
/// Add a plane to the constructed Dmabuf
|
||||
///
|
||||
/// *Note*: Each Dmabuf needs at least one plane.
|
||||
/// MAX_PLANES notes the maximum amount of planes any format may use with this implementation.
|
||||
pub fn add_plane(&mut self, fd: impl Into<Arc<OwnedFd>>, offset: u32, stride: u32) -> bool {
|
||||
if self.internal.planes.len() == MAX_PLANES {
|
||||
return false;
|
||||
}
|
||||
self.internal.planes.push(Plane {
|
||||
fd: fd.into(),
|
||||
plane_idx: self.internal.planes.len() as u32,
|
||||
offset,
|
||||
stride,
|
||||
});
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Sets a known good node to import the dmabuf with.
|
||||
///
|
||||
/// While this is only a strong hint and no guarantee, implementations
|
||||
/// should avoid setting this at all, if they can't be reasonably certain.
|
||||
#[cfg(feature = "backend_drm")]
|
||||
pub fn set_node(&mut self, node: DrmNode) {
|
||||
self.internal.node = Mutex::new(Some(node));
|
||||
}
|
||||
|
||||
/// Build a `Dmabuf` out of the provided parameters and planes
|
||||
///
|
||||
/// Returns `None` if the builder has no planes attached.
|
||||
pub fn build(self) -> Option<Dmabuf> {
|
||||
if self.internal.planes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Dmabuf(Arc::new(self.internal)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Dmabuf {
|
||||
/// Create a new Dmabuf by initializing with values from an existing buffer
|
||||
///
|
||||
// Note: the `src` Buffer is only used a reference for size and format.
|
||||
// The contents are determined by the provided file descriptors, which
|
||||
// do not need to refer to the same buffer `src` does.
|
||||
pub fn builder_from_buffer(src: &impl Buffer, flags: DmabufFlags) -> DmabufBuilder {
|
||||
Self::builder(src.size(), src.format().code, src.format().modifier, flags)
|
||||
}
|
||||
|
||||
/// Create a new Dmabuf builder
|
||||
pub fn builder(
|
||||
size: impl Into<Size<i32, BufferCoords>>,
|
||||
format: Fourcc,
|
||||
modifier: Modifier,
|
||||
flags: DmabufFlags,
|
||||
) -> DmabufBuilder {
|
||||
DmabufBuilder {
|
||||
internal: DmabufInternal {
|
||||
planes: Vec::with_capacity(MAX_PLANES),
|
||||
size: size.into(),
|
||||
format,
|
||||
modifier,
|
||||
flags,
|
||||
#[cfg(feature = "backend_drm")]
|
||||
node: Mutex::new(None),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// The amount of planes this Dmabuf has
|
||||
pub fn num_planes(&self) -> usize {
|
||||
self.0.planes.len()
|
||||
}
|
||||
|
||||
/// Returns raw handles of the planes of this buffer
|
||||
pub fn handles(&self) -> impl Iterator<Item = BorrowedFd<'_>> + '_ {
|
||||
self.0.planes.iter().map(|p| p.fd.as_fd())
|
||||
}
|
||||
|
||||
/// Returns offsets for the planes of this buffer
|
||||
pub fn offsets(&self) -> impl Iterator<Item = u32> + '_ {
|
||||
self.0.planes.iter().map(|p| p.offset)
|
||||
}
|
||||
|
||||
/// Returns strides for the planes of this buffer
|
||||
pub fn strides(&self) -> impl Iterator<Item = u32> + '_ {
|
||||
self.0.planes.iter().map(|p| p.stride)
|
||||
}
|
||||
|
||||
/// Returns if this buffer format has any vendor-specific modifiers set or is implicit/linear
|
||||
pub fn has_modifier(&self) -> bool {
|
||||
self.0.modifier != Modifier::Invalid && self.0.modifier != Modifier::Linear
|
||||
}
|
||||
|
||||
/// Returns the flags stored on this buffer without interpreting them.
|
||||
///
|
||||
/// Bits unknown to this Smithay version are retained.
|
||||
pub fn flags(&self) -> DmabufFlags {
|
||||
self.0.flags
|
||||
}
|
||||
|
||||
/// Returns if the buffer is stored inverted on the y-axis
|
||||
pub fn y_inverted(&self) -> bool {
|
||||
self.0.flags.contains(DmabufFlags::Y_INVERT)
|
||||
}
|
||||
|
||||
/// Create a weak reference to this dmabuf
|
||||
pub fn weak(&self) -> WeakDmabuf {
|
||||
WeakDmabuf(Arc::downgrade(&self.0))
|
||||
}
|
||||
|
||||
/// Presumably compatible device for buffer import
|
||||
///
|
||||
/// This is inferred from client apis, as there is no kernel api or other guarantee this is correct,
|
||||
/// so it should only be treated as a hint.
|
||||
#[cfg(feature = "backend_drm")]
|
||||
pub fn node(&self) -> Option<DrmNode> {
|
||||
*self.0.node.lock().unwrap()
|
||||
}
|
||||
|
||||
/// Sets or unsets any node set for this dmabuf (see [`Dmabuf::node`]).
|
||||
///
|
||||
/// May alter behavior of other parts of smithay using this as a hint.
|
||||
#[cfg(feature = "backend_drm")]
|
||||
pub fn set_node(&self, node: impl Into<Option<DrmNode>>) {
|
||||
*self.0.node.lock().unwrap() = node.into();
|
||||
}
|
||||
|
||||
/// Create an [`calloop::EventSource`] and [`Blocker`] for this [`Dmabuf`].
|
||||
///
|
||||
/// Usually used to block applying surface state on the readiness of an attached dmabuf.
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
#[profiling::function]
|
||||
pub fn generate_blocker(
|
||||
&self,
|
||||
interest: Interest,
|
||||
) -> Result<(DmabufBlocker, DmabufSource), AlreadyReady> {
|
||||
let source = DmabufSource::new(self.clone(), interest)?;
|
||||
let blocker = DmabufBlocker(source.signal.clone());
|
||||
Ok((blocker, source))
|
||||
}
|
||||
|
||||
/// Map the plane at specified index with the specified mode
|
||||
///
|
||||
/// Returns `Err` if the plane with the specified index does not exist or
|
||||
/// mmap failed
|
||||
pub fn map_plane(
|
||||
&self,
|
||||
idx: usize,
|
||||
mode: DmabufMappingMode,
|
||||
) -> Result<DmabufMapping, DmabufMappingFailed> {
|
||||
let plane = self
|
||||
.0
|
||||
.planes
|
||||
.get(idx)
|
||||
.ok_or(DmabufMappingFailed::PlaneIndexOutOfBound)?;
|
||||
|
||||
let size = rustix::fs::seek(&plane.fd, rustix::fs::SeekFrom::End(0)).map_err(std::io::Error::from)?;
|
||||
rustix::fs::seek(&plane.fd, rustix::fs::SeekFrom::Start(0)).map_err(std::io::Error::from)?;
|
||||
|
||||
let len = (size - plane.offset as u64) as usize;
|
||||
let ptr = unsafe {
|
||||
rustix::mm::mmap(
|
||||
std::ptr::null_mut(),
|
||||
len,
|
||||
mode.into(),
|
||||
rustix::mm::MapFlags::SHARED,
|
||||
&plane.fd,
|
||||
plane.offset as u64,
|
||||
)
|
||||
}
|
||||
.map_err(std::io::Error::from)?;
|
||||
Ok(DmabufMapping { len, ptr })
|
||||
}
|
||||
|
||||
/// Synchronize access for the plane at the specified index
|
||||
///
|
||||
/// Returns `Err` if the plane with the specified index does not exist or
|
||||
/// the dmabuf_sync ioctl failed
|
||||
pub fn sync_plane(&self, idx: usize, flags: DmabufSyncFlags) -> Result<(), DmabufSyncFailed> {
|
||||
let plane = self
|
||||
.0
|
||||
.planes
|
||||
.get(idx)
|
||||
.ok_or(DmabufSyncFailed::PlaneIndexOutOfBound)?;
|
||||
unsafe { rustix::ioctl::ioctl(&plane.fd, Setter::<DMA_BUF_SYNC, _>::new(dma_buf_sync { flags })) }
|
||||
.map_err(std::io::Error::from)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Modes of mapping a dmabuf plane
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct DmabufMappingMode: u32 {
|
||||
/// Map the dmabuf readable
|
||||
const READ = 0b00000001;
|
||||
/// Map the dmabuf writable
|
||||
const WRITE = 0b00000010;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DmabufMappingMode> for rustix::mm::ProtFlags {
|
||||
#[inline]
|
||||
fn from(mode: DmabufMappingMode) -> Self {
|
||||
let mut flags = rustix::mm::ProtFlags::empty();
|
||||
|
||||
if mode.contains(DmabufMappingMode::READ) {
|
||||
flags |= rustix::mm::ProtFlags::READ;
|
||||
}
|
||||
|
||||
if mode.contains(DmabufMappingMode::WRITE) {
|
||||
flags |= rustix::mm::ProtFlags::WRITE;
|
||||
}
|
||||
|
||||
flags
|
||||
}
|
||||
}
|
||||
|
||||
/// Dmabuf mapping errors
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("Mapping the dmabuf failed")]
|
||||
pub enum DmabufMappingFailed {
|
||||
/// The supplied index for the plane is out of bounds
|
||||
#[error("The supplied index for the plane is out of bounds")]
|
||||
PlaneIndexOutOfBound,
|
||||
/// Io error during map operation
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
/// Dmabuf sync errors
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("Sync of the dmabuf failed")]
|
||||
pub enum DmabufSyncFailed {
|
||||
/// The supplied index for the plane is out of bounds
|
||||
#[error("The supplied index for the plane is out of bounds")]
|
||||
PlaneIndexOutOfBound,
|
||||
/// Io error during sync operation
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
bitflags::bitflags! {
|
||||
/// Flags for the [`Dmabuf::sync_plane`](Dmabuf::sync_plane) operation
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct DmabufSyncFlags: std::ffi::c_ulonglong {
|
||||
/// Read from the dmabuf
|
||||
const READ = 1 << 0;
|
||||
/// Write to the dmabuf
|
||||
#[allow(clippy::identity_op)]
|
||||
const WRITE = 2 << 0;
|
||||
/// Start of read/write
|
||||
const START = 0 << 2;
|
||||
/// End of read/write
|
||||
const END = 1 << 2;
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[allow(non_camel_case_types)]
|
||||
struct dma_buf_sync {
|
||||
flags: DmabufSyncFlags,
|
||||
}
|
||||
|
||||
const DMA_BUF_SYNC: rustix::ioctl::Opcode = rustix::ioctl::opcode::write::<dma_buf_sync>(b'b', 0);
|
||||
|
||||
/// A mapping into a [`Dmabuf`]
|
||||
#[derive(Debug)]
|
||||
pub struct DmabufMapping {
|
||||
ptr: *mut std::ffi::c_void,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl DmabufMapping {
|
||||
/// Access the raw pointer of the mapping
|
||||
pub fn ptr(&self) -> *mut std::ffi::c_void {
|
||||
self.ptr
|
||||
}
|
||||
|
||||
/// Access the length of the mapping
|
||||
pub fn length(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: The caller is responsible for accessing the data without assuming
|
||||
// another process isn't mutating it, regardless of how many threads this is
|
||||
// referenced in.
|
||||
unsafe impl Send for DmabufMapping {}
|
||||
unsafe impl Sync for DmabufMapping {}
|
||||
|
||||
impl Drop for DmabufMapping {
|
||||
fn drop(&mut self) {
|
||||
let _ = unsafe { rustix::mm::munmap(self.ptr, self.len) };
|
||||
}
|
||||
}
|
||||
|
||||
impl WeakDmabuf {
|
||||
/// Try to upgrade to a strong reference of this buffer.
|
||||
///
|
||||
/// Fails if no strong references exist anymore and the handle was already closed.
|
||||
#[inline]
|
||||
pub fn upgrade(&self) -> Option<Dmabuf> {
|
||||
self.0.upgrade().map(Dmabuf)
|
||||
}
|
||||
|
||||
/// Returns true if there are not any strong references remaining
|
||||
#[inline]
|
||||
pub fn is_gone(&self) -> bool {
|
||||
self.0.strong_count() == 0
|
||||
}
|
||||
}
|
||||
|
||||
/// Buffer that can be exported as Dmabufs
|
||||
pub trait AsDmabuf {
|
||||
/// Error type returned, if exporting fails
|
||||
type Error: std::error::Error;
|
||||
|
||||
/// Export this buffer as a new Dmabuf
|
||||
fn export(&self) -> Result<Dmabuf, Self::Error>;
|
||||
}
|
||||
|
||||
impl AsDmabuf for Dmabuf {
|
||||
type Error = std::convert::Infallible;
|
||||
|
||||
#[inline]
|
||||
fn export(&self) -> Result<Dmabuf, std::convert::Infallible> {
|
||||
Ok(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Type erased error
|
||||
#[derive(Debug)]
|
||||
pub struct AnyError(Box<dyn error::Error + Send + Sync>);
|
||||
|
||||
impl fmt::Display for AnyError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for AnyError {
|
||||
#[inline]
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
Some(&*self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for Allocators, whose buffer types implement [`AsDmabuf`].
|
||||
///
|
||||
/// Implements `Allocator<Buffer=Dmabuf, Error=AnyError>`
|
||||
#[derive(Debug)]
|
||||
pub struct DmabufAllocator<A>(pub A)
|
||||
where
|
||||
A: Allocator,
|
||||
<A as Allocator>::Buffer: AsDmabuf + 'static,
|
||||
<A as Allocator>::Error: 'static;
|
||||
|
||||
impl<A> Allocator for DmabufAllocator<A>
|
||||
where
|
||||
A: Allocator,
|
||||
<A as Allocator>::Buffer: AsDmabuf + 'static,
|
||||
<A as Allocator>::Error: Send + Sync + 'static,
|
||||
<<A as Allocator>::Buffer as AsDmabuf>::Error: Send + Sync + 'static,
|
||||
{
|
||||
type Buffer = Dmabuf;
|
||||
type Error = AnyError;
|
||||
|
||||
#[profiling::function]
|
||||
fn create_buffer(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
fourcc: Fourcc,
|
||||
modifiers: &[Modifier],
|
||||
) -> Result<Self::Buffer, Self::Error> {
|
||||
self.0
|
||||
.create_buffer(width, height, fourcc, modifiers)
|
||||
.map_err(|err| AnyError(err.into()))
|
||||
.and_then(|b| AsDmabuf::export(&b).map_err(|err| AnyError(err.into())))
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Blocker`] implementation for an accompaning [`DmabufSource`]
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
#[derive(Debug)]
|
||||
pub struct DmabufBlocker(Arc<AtomicBool>);
|
||||
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
impl Blocker for DmabufBlocker {
|
||||
fn state(&self) -> BlockerState {
|
||||
if self.0.load(Ordering::SeqCst) {
|
||||
BlockerState::Released
|
||||
} else {
|
||||
BlockerState::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Subsource {
|
||||
Active(Generic<Arc<OwnedFd>, std::io::Error>),
|
||||
Done(Generic<Arc<OwnedFd>, std::io::Error>),
|
||||
Empty,
|
||||
}
|
||||
|
||||
impl Subsource {
|
||||
fn done(&mut self) {
|
||||
let mut this = Subsource::Empty;
|
||||
std::mem::swap(self, &mut this);
|
||||
match this {
|
||||
Subsource::Done(source) | Subsource::Active(source) => {
|
||||
*self = Subsource::Done(source);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// [`Dmabuf`]-based event source. Can be used to monitor implicit fences of a dmabuf.
|
||||
#[derive(Debug)]
|
||||
pub struct DmabufSource {
|
||||
dmabuf: Dmabuf,
|
||||
signal: Arc<AtomicBool>,
|
||||
sources: [Subsource; 4],
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
#[error("Dmabuf is already ready for the given interest")]
|
||||
/// Dmabuf is already ready for the given interest
|
||||
pub struct AlreadyReady;
|
||||
|
||||
impl DmabufSource {
|
||||
/// Creates a new [`DmabufSource`] from a [`Dmabuf`] and interest.
|
||||
///
|
||||
/// This event source will monitor the implicit fences of the given dmabuf.
|
||||
/// Monitoring for READ-access will monitor the state of the most recent write or exclusive fence.
|
||||
/// Monitoring for WRITE-access, will monitor state of all attached fences, shared and exclusive ones.
|
||||
///
|
||||
/// The event source is a one shot event source and will remove itself from the event loop after being triggered once.
|
||||
/// To monitor for new fences added at a later time a new DmabufSource needs to be created.
|
||||
///
|
||||
/// Returns `AlreadyReady` if all corresponding fences are already signalled or if `interest` is empty.
|
||||
#[profiling::function]
|
||||
pub fn new(dmabuf: Dmabuf, interest: Interest) -> Result<Self, AlreadyReady> {
|
||||
if !interest.readable && !interest.writable {
|
||||
return Err(AlreadyReady);
|
||||
}
|
||||
|
||||
let mut sources = [
|
||||
Subsource::Empty,
|
||||
Subsource::Empty,
|
||||
Subsource::Empty,
|
||||
Subsource::Empty,
|
||||
];
|
||||
for (idx, handle) in dmabuf.handles().enumerate() {
|
||||
if matches!(
|
||||
rustix::event::poll(
|
||||
&mut [rustix::event::PollFd::new(
|
||||
&handle,
|
||||
if interest.writable {
|
||||
rustix::event::PollFlags::OUT
|
||||
} else {
|
||||
rustix::event::PollFlags::IN
|
||||
},
|
||||
)],
|
||||
Some(&rustix::time::Timespec {
|
||||
tv_sec: 0,
|
||||
tv_nsec: 0
|
||||
})
|
||||
),
|
||||
Ok(1)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
let fd = dmabuf.0.planes[idx].fd.clone();
|
||||
sources[idx] = Subsource::Active(Generic::new(fd, interest, Mode::OneShot));
|
||||
}
|
||||
if sources
|
||||
.iter()
|
||||
.all(|x| matches!(x, Subsource::Done(_) | Subsource::Empty))
|
||||
{
|
||||
Err(AlreadyReady)
|
||||
} else {
|
||||
Ok(DmabufSource {
|
||||
dmabuf,
|
||||
sources,
|
||||
signal: Arc::new(AtomicBool::new(false)),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventSource for DmabufSource {
|
||||
type Event = ();
|
||||
type Metadata = Dmabuf;
|
||||
type Ret = Result<(), std::io::Error>;
|
||||
|
||||
type Error = std::io::Error;
|
||||
|
||||
#[profiling::function]
|
||||
fn process_events<F>(
|
||||
&mut self,
|
||||
readiness: calloop::Readiness,
|
||||
token: calloop::Token,
|
||||
mut callback: F,
|
||||
) -> Result<PostAction, Self::Error>
|
||||
where
|
||||
F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
|
||||
{
|
||||
for i in 0..4 {
|
||||
if let Ok(PostAction::Remove) = if let Subsource::Active(source) = &mut self.sources[i] {
|
||||
// luckily Generic skips events for other tokens
|
||||
source.process_events(readiness, token, |_, _| Ok(PostAction::Remove))
|
||||
} else {
|
||||
Ok(PostAction::Continue)
|
||||
} {
|
||||
self.sources[i].done();
|
||||
}
|
||||
}
|
||||
|
||||
if self
|
||||
.sources
|
||||
.iter()
|
||||
.all(|x| matches!(x, Subsource::Done(_) | Subsource::Empty))
|
||||
{
|
||||
self.signal.store(true, Ordering::SeqCst);
|
||||
callback((), &mut self.dmabuf)?;
|
||||
Ok(PostAction::Remove)
|
||||
} else {
|
||||
Ok(PostAction::Reregister)
|
||||
}
|
||||
}
|
||||
|
||||
fn register(
|
||||
&mut self,
|
||||
poll: &mut calloop::Poll,
|
||||
token_factory: &mut calloop::TokenFactory,
|
||||
) -> calloop::Result<()> {
|
||||
for source in self.sources.iter_mut().filter_map(|source| match source {
|
||||
Subsource::Active(source) => Some(source),
|
||||
_ => None,
|
||||
}) {
|
||||
source.register(poll, token_factory)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reregister(
|
||||
&mut self,
|
||||
poll: &mut calloop::Poll,
|
||||
token_factory: &mut calloop::TokenFactory,
|
||||
) -> calloop::Result<()> {
|
||||
for source in self.sources.iter_mut() {
|
||||
match source {
|
||||
Subsource::Active(source) => source.reregister(poll, token_factory)?,
|
||||
Subsource::Done(source) => {
|
||||
let _ = source.unregister(poll);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unregister(&mut self, poll: &mut calloop::Poll) -> calloop::Result<()> {
|
||||
for source in self.sources.iter_mut() {
|
||||
match source {
|
||||
Subsource::Active(source) => source.unregister(poll)?,
|
||||
Subsource::Done(source) => {
|
||||
let _ = source.unregister(poll);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
//! Module for [DumbBuffer](https://docs.kernel.org/gpu/drm-kms.html#dumb-buffer-objects) buffers
|
||||
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
|
||||
use drm::buffer::Buffer as DrmBuffer;
|
||||
use drm::control::{Device as ControlDevice, dumbbuffer::DumbBuffer as Handle};
|
||||
use tracing::instrument;
|
||||
|
||||
use super::dmabuf::{AsDmabuf, Dmabuf, DmabufFlags};
|
||||
use super::{Allocator, Buffer, Format, Fourcc, Modifier, format::get_bpp};
|
||||
use crate::backend::drm::DrmDeviceFd;
|
||||
use crate::backend::drm::DrmNode;
|
||||
use crate::utils::{Buffer as BufferCoords, Size};
|
||||
|
||||
/// Wrapper around raw DumbBuffer handles.
|
||||
pub struct DumbBuffer {
|
||||
fd: DrmDeviceFd,
|
||||
handle: Handle,
|
||||
format: Format,
|
||||
}
|
||||
|
||||
impl fmt::Debug for DumbBuffer {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("DumbBuffer")
|
||||
.field("handle", &self.handle)
|
||||
.field("format", &self.format)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// Light wrapper around an [`DrmDeviceFd`] to implement the [`Allocator`]-trait
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DumbAllocator {
|
||||
fd: DrmDeviceFd,
|
||||
}
|
||||
|
||||
impl DumbAllocator {
|
||||
/// Create a new [`DumbAllocator`] from a [`DrmDeviceFd`].
|
||||
pub fn new(fd: DrmDeviceFd) -> Self {
|
||||
DumbAllocator { fd }
|
||||
}
|
||||
}
|
||||
|
||||
impl Allocator for DumbAllocator {
|
||||
type Buffer = DumbBuffer;
|
||||
type Error = io::Error;
|
||||
|
||||
#[instrument(level = "trace", err)]
|
||||
#[profiling::function]
|
||||
fn create_buffer(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
fourcc: Fourcc,
|
||||
modifiers: &[Modifier],
|
||||
) -> Result<DumbBuffer, Self::Error> {
|
||||
// dumb buffers are always linear
|
||||
if modifiers
|
||||
.iter()
|
||||
.all(|&x| x != Modifier::Invalid && x != Modifier::Linear)
|
||||
{
|
||||
return Err(rustix::io::Errno::INVAL.into());
|
||||
}
|
||||
|
||||
let handle = self.fd.create_dumb_buffer(
|
||||
(width, height),
|
||||
fourcc,
|
||||
get_bpp(fourcc).ok_or(rustix::io::Errno::INVAL)? as u32,
|
||||
)?;
|
||||
|
||||
Ok(DumbBuffer {
|
||||
fd: self.fd.clone(),
|
||||
handle,
|
||||
format: Format {
|
||||
code: fourcc,
|
||||
modifier: Modifier::Linear,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Buffer for DumbBuffer {
|
||||
fn size(&self) -> Size<i32, BufferCoords> {
|
||||
let (w, h) = self.handle.size();
|
||||
(w as i32, h as i32).into()
|
||||
}
|
||||
|
||||
fn format(&self) -> Format {
|
||||
self.format
|
||||
}
|
||||
}
|
||||
|
||||
impl DumbBuffer {
|
||||
/// Raw handle to the underlying buffer.
|
||||
///
|
||||
/// Note: This handle will become invalid, once the `DumbBuffer` wrapper is dropped
|
||||
/// or the device used to create is closed. Do not copy this handle and assume it keeps being valid.
|
||||
pub fn handle(&self) -> &Handle {
|
||||
&self.handle
|
||||
}
|
||||
}
|
||||
|
||||
impl AsDmabuf for DumbBuffer {
|
||||
type Error = io::Error;
|
||||
|
||||
#[profiling::function]
|
||||
fn export(&self) -> Result<Dmabuf, Self::Error> {
|
||||
let fd = self
|
||||
.fd
|
||||
.buffer_to_prime_fd(self.handle.handle(), drm::CLOEXEC | drm::RDWR)?;
|
||||
let mut builder = Dmabuf::builder(
|
||||
self.size(),
|
||||
self.format.code,
|
||||
self.format.modifier,
|
||||
DmabufFlags::empty(),
|
||||
);
|
||||
builder.add_plane(fd, 0, self.handle.pitch());
|
||||
if let Ok(node) = DrmNode::from_file(&self.fd) {
|
||||
builder.set_node(node);
|
||||
}
|
||||
builder.build().ok_or(rustix::io::Errno::INVAL.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DumbBuffer {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.fd.destroy_dumb_buffer(self.handle);
|
||||
}
|
||||
}
|
||||
+611
@@ -0,0 +1,611 @@
|
||||
//! Format info tables for DRM formats.
|
||||
//!
|
||||
//! This module provides three functions, [`get_opaque`], [`has_alpha`] and [`get_bpp`].
|
||||
//!
|
||||
//! [`get_opaque`] returns the opaque alternative of a DRM format with an alpha channel.
|
||||
//!
|
||||
//! ```
|
||||
//! # use smithay::backend::allocator::Fourcc;
|
||||
//! # use smithay::backend::allocator::format::get_opaque;
|
||||
//! assert_eq!(Some(Fourcc::Xrgb8888), get_opaque(Fourcc::Argb8888));
|
||||
//! ```
|
||||
//!
|
||||
//! [`has_alpha`] returns true if the format has an alpha channel.
|
||||
//!
|
||||
//! ```
|
||||
//! # use smithay::backend::allocator::Fourcc;
|
||||
//! # use smithay::backend::allocator::format::has_alpha;
|
||||
//! assert!(has_alpha(Fourcc::Argb8888));
|
||||
//! assert!(!has_alpha(Fourcc::Xrgb8888));
|
||||
//! ```
|
||||
//!
|
||||
//! [`get_bpp`] returns the number of bits per pixel of a format.
|
||||
//!
|
||||
//! ```
|
||||
//! # use smithay::backend::allocator::Fourcc;
|
||||
//! # use smithay::backend::allocator::format::get_bpp;
|
||||
//! assert_eq!(get_bpp(Fourcc::Argb8888), Some(32));
|
||||
//! ```
|
||||
//!
|
||||
//! [`get_depth`] returns the number of used bits per pixel of a format
|
||||
//! (excluding padding or non-alpha "X" parts of the format).
|
||||
//!
|
||||
//! ```
|
||||
//! # use smithay::backend::allocator::Fourcc;
|
||||
//! # use smithay::backend::allocator::format::get_depth;
|
||||
//! assert_eq!(get_depth(Fourcc::Argb8888), Some(32));
|
||||
//! assert_eq!(get_depth(Fourcc::Xrgb8888), Some(24));
|
||||
//! ```
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::Format;
|
||||
use indexmap::IndexSet;
|
||||
|
||||
/// Macro to generate table lookup functions for formats.
|
||||
///
|
||||
/// See the module documentation for usage details.
|
||||
macro_rules! format_tables {
|
||||
(
|
||||
$($fourcc: ident {
|
||||
$(opaque: $opaque: ident,)?
|
||||
alpha: $alpha: expr,
|
||||
bpp: $bpp: expr,
|
||||
depth: $depth: expr $(,)?
|
||||
}),*
|
||||
) => {
|
||||
/// Returns the opaque alternative of the specified format.
|
||||
///
|
||||
/// If the format has an alpha channel, this may return the corresponding opaque format.
|
||||
///
|
||||
/// Unknown formats will always return [`None`].
|
||||
pub const fn get_opaque(
|
||||
fourcc: $crate::backend::allocator::Fourcc,
|
||||
) -> Option<$crate::backend::allocator::Fourcc> {
|
||||
match fourcc {
|
||||
$($(
|
||||
$crate::backend::allocator::Fourcc::$fourcc
|
||||
=> Some($crate::backend::allocator::Fourcc::$opaque),
|
||||
)?)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the transparent alternative of the specified format.
|
||||
///
|
||||
/// If the format has an unused alpha channel, this may return the corresponding non-opaque format.
|
||||
///
|
||||
/// Unknown formats will always return [`None`].
|
||||
pub const fn get_transparent(
|
||||
fourcc: $crate::backend::allocator::Fourcc,
|
||||
) -> Option<$crate::backend::allocator::Fourcc> {
|
||||
match fourcc {
|
||||
$($(
|
||||
$crate::backend::allocator::Fourcc::$opaque
|
||||
=> Some($crate::backend::allocator::Fourcc::$fourcc),
|
||||
)?)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the format has an alpha channel.
|
||||
///
|
||||
/// This function may be useful to know if the alpha channel may need to be swizzled when rendering
|
||||
/// with some graphics apis.
|
||||
///
|
||||
/// Unknown formats will always return `false`.
|
||||
pub const fn has_alpha(fourcc: $crate::backend::allocator::Fourcc) -> bool {
|
||||
match fourcc {
|
||||
$(
|
||||
$crate::backend::allocator::Fourcc::$fourcc => $alpha,
|
||||
)*
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the bits per pixel of the specified format.
|
||||
///
|
||||
/// Unknown formats will always return [`None`].
|
||||
pub const fn get_bpp(
|
||||
fourcc: $crate::backend::allocator::Fourcc,
|
||||
) -> Option<usize> {
|
||||
match fourcc {
|
||||
$($crate::backend::allocator::Fourcc::$fourcc => Some($bpp),)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the depth of the specified format.
|
||||
///
|
||||
/// Unknown formats will always return [`None`].
|
||||
pub const fn get_depth(
|
||||
fourcc: $crate::backend::allocator::Fourcc,
|
||||
) -> Option<usize> {
|
||||
match fourcc {
|
||||
$($crate::backend::allocator::Fourcc::$fourcc => Some($depth),)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn _impl_formats() -> &'static [$crate::backend::allocator::Fourcc] {
|
||||
&[
|
||||
$(
|
||||
$crate::backend::allocator::Fourcc::$fourcc,
|
||||
)*
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
format_tables! {
|
||||
// 8-bit bpp Red
|
||||
R8 { alpha: false, bpp: 8, depth: 8 },
|
||||
|
||||
// TODO: Update drm-fourcc
|
||||
// 16-bit bpp Red with padding (x:R)
|
||||
// R10 { bpp: 16, depth: 10 },
|
||||
// R12 { bpp: 16, depth: 12 },
|
||||
|
||||
// 16-bit bpp Red
|
||||
R16 { alpha: false, bpp: 16, depth: 16 },
|
||||
|
||||
// 16-bit bpp RG
|
||||
Rg88 { alpha: false, bpp: 16, depth: 16 },
|
||||
|
||||
Gr88 { alpha: false, bpp: 16, depth: 16 },
|
||||
|
||||
// 32-bit bpp RG
|
||||
Rg1616 { alpha: false, bpp: 32, depth: 32 },
|
||||
|
||||
Gr1616 { alpha: false, bpp: 32, depth: 32 },
|
||||
|
||||
// 8-bit bpp RGB
|
||||
Rgb332 { alpha: false, bpp: 8, depth: 8 },
|
||||
|
||||
Bgr233 { alpha: false, bpp: 8, depth: 8 },
|
||||
|
||||
// 16-bit bpp RGB, 4 bits per channel
|
||||
Argb4444 {
|
||||
opaque: Xrgb4444,
|
||||
alpha: true,
|
||||
bpp: 16,
|
||||
depth: 16,
|
||||
},
|
||||
|
||||
Xrgb4444 { alpha: false, bpp: 16, depth: 12 },
|
||||
|
||||
Abgr4444 {
|
||||
opaque: Xbgr4444,
|
||||
alpha: true,
|
||||
bpp: 16,
|
||||
depth: 16,
|
||||
},
|
||||
|
||||
Xbgr4444 { alpha: false, bpp: 16, depth: 12 },
|
||||
|
||||
Rgba4444 {
|
||||
opaque: Rgbx4444,
|
||||
alpha: true,
|
||||
bpp: 16,
|
||||
depth: 16,
|
||||
},
|
||||
|
||||
Rgbx4444 { alpha: false, bpp: 16, depth: 12 },
|
||||
|
||||
Bgra4444 {
|
||||
opaque: Bgrx4444,
|
||||
alpha: true,
|
||||
bpp: 16,
|
||||
depth: 16,
|
||||
},
|
||||
|
||||
Bgrx4444 { alpha: false, bpp: 16, depth: 12 },
|
||||
|
||||
// 16-bit bpp RGB, 5 bits per color channel, 1 bit for alpha channel
|
||||
Argb1555 {
|
||||
opaque: Xrgb1555,
|
||||
alpha: true,
|
||||
bpp: 16,
|
||||
depth: 16
|
||||
},
|
||||
|
||||
Xrgb1555 { alpha: false, bpp: 16, depth: 15 },
|
||||
|
||||
Abgr1555 {
|
||||
opaque: Xbgr1555,
|
||||
alpha: true,
|
||||
bpp: 16,
|
||||
depth: 16,
|
||||
},
|
||||
|
||||
Xbgr1555 { alpha: false, bpp: 16, depth: 15 },
|
||||
|
||||
Rgba5551 {
|
||||
opaque: Rgbx5551,
|
||||
alpha: true,
|
||||
bpp: 16,
|
||||
depth: 16,
|
||||
},
|
||||
|
||||
Rgbx5551 { alpha: false, bpp: 16, depth: 15 },
|
||||
|
||||
Bgra5551 {
|
||||
opaque: Bgrx5551,
|
||||
alpha: true,
|
||||
bpp: 16,
|
||||
depth: 16,
|
||||
},
|
||||
|
||||
Bgrx5551 { alpha: false, bpp: 16, depth: 15 },
|
||||
|
||||
// 16-bit bpp RGB, no alpha, 6 bits for green channel and 5 bits for blue and red
|
||||
Rgb565 { alpha: false, bpp: 16, depth: 16 },
|
||||
|
||||
Bgr565 { alpha: false, bpp: 16, depth: 16 },
|
||||
|
||||
// 24-bit bpp RGB
|
||||
Rgb888 { alpha: false, bpp: 24, depth: 24 },
|
||||
|
||||
Bgr888 { alpha: false, bpp: 24, depth: 24 },
|
||||
|
||||
// 32-bit bpp RGB, 8 bits per channel
|
||||
Argb8888 {
|
||||
opaque: Xrgb8888,
|
||||
alpha: true,
|
||||
bpp: 32,
|
||||
depth: 32,
|
||||
},
|
||||
|
||||
Xrgb8888 { alpha: false, bpp: 32, depth: 24 },
|
||||
|
||||
Abgr8888 {
|
||||
opaque: Xbgr8888,
|
||||
alpha: true,
|
||||
bpp: 32,
|
||||
depth: 32,
|
||||
},
|
||||
|
||||
Xbgr8888 { alpha: false, bpp: 32, depth: 24 },
|
||||
|
||||
Rgba8888 {
|
||||
opaque: Rgbx8888,
|
||||
alpha: true,
|
||||
bpp: 32,
|
||||
depth: 32,
|
||||
},
|
||||
|
||||
Rgbx8888 { alpha: false, bpp: 32, depth: 24 },
|
||||
|
||||
Bgra8888 {
|
||||
opaque: Bgrx8888,
|
||||
alpha: true,
|
||||
bpp: 32,
|
||||
depth: 32,
|
||||
},
|
||||
|
||||
Bgrx8888 { alpha: false, bpp: 32, depth: 24 },
|
||||
|
||||
// 32-bit bpp RGB with 10-bits per color channel
|
||||
|
||||
Argb2101010 {
|
||||
opaque: Xrgb2101010,
|
||||
alpha: true,
|
||||
bpp: 32,
|
||||
depth: 32,
|
||||
},
|
||||
|
||||
Xrgb2101010 { alpha: false, bpp: 32, depth: 30 },
|
||||
|
||||
Abgr2101010 {
|
||||
opaque: Xbgr2101010,
|
||||
alpha: true,
|
||||
bpp: 32,
|
||||
depth: 32,
|
||||
},
|
||||
|
||||
Xbgr2101010 { alpha: false, bpp: 32, depth: 30 },
|
||||
|
||||
Rgba1010102 {
|
||||
opaque: Rgbx1010102,
|
||||
alpha: true,
|
||||
bpp: 32,
|
||||
depth: 32,
|
||||
},
|
||||
|
||||
Rgbx1010102 { alpha: false, bpp: 32, depth: 30 },
|
||||
|
||||
Bgra1010102 {
|
||||
opaque: Bgrx1010102,
|
||||
alpha: true,
|
||||
bpp: 32,
|
||||
depth: 32,
|
||||
},
|
||||
|
||||
Bgrx1010102 { alpha: false, bpp: 32, depth: 30 },
|
||||
|
||||
// 64-bit RGB, 16-bits per channel
|
||||
// TODO: Update drm-fourcc
|
||||
// Argb16161616 {
|
||||
// opaque: Xrgb16161616,
|
||||
// alpha: true,
|
||||
// bpp: 64,
|
||||
// depth: 64,
|
||||
// },
|
||||
// Xrgb16161616 { alpha: false, bpp: 64, depth: 48 },
|
||||
// Abgr16161616 {
|
||||
// opaque: Xbgr16161616,
|
||||
// alpha: true,
|
||||
// bpp: 64,
|
||||
// depth: 64,
|
||||
// },
|
||||
// Xbgr16161616 { alpha: false, bpp: 64, depth: 48 },
|
||||
|
||||
// Floating point 64bpp RGB
|
||||
// IEEE 754-2008 binary16 half-precision float
|
||||
Argb16161616f {
|
||||
opaque: Xrgb16161616f,
|
||||
alpha: true,
|
||||
bpp: 64,
|
||||
depth: 64
|
||||
},
|
||||
|
||||
Xrgb16161616f { alpha: false, bpp: 64, depth: 48 },
|
||||
|
||||
Abgr16161616f {
|
||||
opaque: Xbgr16161616f,
|
||||
alpha: true,
|
||||
bpp: 64,
|
||||
depth: 64,
|
||||
},
|
||||
|
||||
Xbgr16161616f { alpha: false, bpp: 64, depth: 48 },
|
||||
|
||||
// RGBA with 10-bit components packed in 64-bits per pixel, with 6-bits of unused padding per component
|
||||
|
||||
// Axbxgxrx106106106106 has no direct non-alpha alternative.
|
||||
Axbxgxrx106106106106 { alpha: true, bpp: 64, depth: 40 },
|
||||
|
||||
Yuv420 {alpha: false, bpp: 12, depth: 12 },
|
||||
Nv12 {alpha: false, bpp: 12, depth: 12 }
|
||||
// TODO: YUV and other formats
|
||||
}
|
||||
|
||||
/// A set of [`Format`]s
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct FormatSet {
|
||||
formats: Arc<IndexSet<Format>>,
|
||||
}
|
||||
|
||||
impl FormatSet {
|
||||
#[cfg(any(feature = "backend_egl", feature = "backend_drm"))]
|
||||
pub(crate) fn from_formats(formats: IndexSet<Format>) -> Self {
|
||||
FormatSet {
|
||||
formats: Arc::new(formats),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FormatSet {
|
||||
/// Return an iterator over the values of the set, in their order
|
||||
pub fn iter(&self) -> FormatSetIter<'_> {
|
||||
FormatSetIter {
|
||||
inner: self.formats.iter(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return `true` if an equivalent to `value` exists in the set.
|
||||
pub fn contains(&self, format: &Format) -> bool {
|
||||
self.formats.contains(format)
|
||||
}
|
||||
|
||||
/// Return an iterator over the values that are in both `self` and `other`.
|
||||
pub fn intersection<'a>(&'a self, other: &'a FormatSet) -> FormatSetIntersection<'a> {
|
||||
FormatSetIntersection {
|
||||
inner: self.formats.intersection(&other.formats),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get access to the underlying storage.
|
||||
pub fn indexset(&self) -> &IndexSet<Format> {
|
||||
&self.formats
|
||||
}
|
||||
}
|
||||
|
||||
/// A lazy iterator producing elements in the intersection of [`FormatSet`]s.
|
||||
#[derive(Debug)]
|
||||
pub struct FormatSetIntersection<'a> {
|
||||
inner: indexmap::set::Intersection<'a, Format, std::collections::hash_map::RandomState>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for FormatSetIntersection<'a> {
|
||||
type Item = &'a Format;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.inner.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for FormatSet {
|
||||
type Item = Format;
|
||||
|
||||
type IntoIter = FormatSetIntoIter;
|
||||
|
||||
#[inline]
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
FormatSetIntoIter {
|
||||
inner: (*self.formats).clone().into_iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<Format> for FormatSet {
|
||||
#[inline]
|
||||
fn from_iter<T: IntoIterator<Item = Format>>(iter: T) -> Self {
|
||||
Self {
|
||||
formats: Arc::new(IndexSet::from_iter(iter)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the items of an [`FormatSet`].
|
||||
#[derive(Debug)]
|
||||
pub struct FormatSetIter<'a> {
|
||||
inner: indexmap::set::Iter<'a, Format>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for FormatSetIter<'a> {
|
||||
type Item = &'a Format;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.inner.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
/// An owning iterator over the items of an [`FormatSet`].
|
||||
#[derive(Debug)]
|
||||
pub struct FormatSetIntoIter {
|
||||
inner: indexmap::set::IntoIter<Format>,
|
||||
}
|
||||
|
||||
impl Iterator for FormatSetIntoIter {
|
||||
type Item = Format;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
self.inner.size_hint()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{_impl_formats, get_bpp, get_depth, get_opaque, get_transparent, has_alpha};
|
||||
|
||||
/// Tests that opaque alternatives are not the same as the variant with alpha.
|
||||
#[test]
|
||||
fn opaque_neq() {
|
||||
for &format in _impl_formats() {
|
||||
if let Some(opaque) = get_opaque(format) {
|
||||
assert_ne!(
|
||||
format, opaque,
|
||||
"{}'s opaque alternative is the same format",
|
||||
format
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that opaque alternatives are cleanly converting back with get_transparent.
|
||||
#[test]
|
||||
fn opaque_inverse() {
|
||||
for &format in _impl_formats() {
|
||||
if let Some(opaque) = get_opaque(format) {
|
||||
let transparent = get_transparent(opaque);
|
||||
assert_eq!(
|
||||
Some(format),
|
||||
transparent,
|
||||
"{}'s opaque alternative {} doesn't cleanly convert back, got: {:?}",
|
||||
format,
|
||||
opaque,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that opaque alternatives for formats do not have opaque alternatives themselves.
|
||||
///
|
||||
/// For example, `Argb8888` should have an opaque alternative of `Xrgb8888` and `Xrgb8888` should have no
|
||||
/// opaque alternative.
|
||||
#[test]
|
||||
fn opaque_alternatives() {
|
||||
for &format in _impl_formats() {
|
||||
if let Some(opaque) = get_opaque(format) {
|
||||
// If a format is considered to be an opaque alternative, the format should not have an opaque
|
||||
// alternative.
|
||||
let result = get_opaque(opaque);
|
||||
|
||||
assert!(
|
||||
result.is_none(),
|
||||
"Format {format} has an opaque alternative, {opaque}. However {opaque} reports an opaque alternative {opaque_opaque:?} which is incorrect",
|
||||
format = format,
|
||||
opaque = opaque,
|
||||
opaque_opaque = result,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests that a format and it's opaque alternative have the same number of bits per pixel.
|
||||
#[test]
|
||||
fn opaque_has_same_bpp() {
|
||||
for &format in _impl_formats() {
|
||||
if let Some(opaque) = get_opaque(format) {
|
||||
let format_bpp = get_bpp(format);
|
||||
let opaque_bpp = get_bpp(opaque);
|
||||
|
||||
assert_eq!(
|
||||
format_bpp,
|
||||
opaque_bpp,
|
||||
"Format {format} has a bpp of {format_bpp:?}. However the opaque alternative {opaque} has a different bpp of {opaque_bpp:?}",
|
||||
format_bpp = get_bpp(format),
|
||||
opaque = opaque,
|
||||
opaque_bpp = get_bpp(opaque),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A format with an opaque alternative should have alpha.
|
||||
///
|
||||
/// The opaque alternative should not have alpha.
|
||||
#[test]
|
||||
fn format_with_opaque_has_alpha() {
|
||||
for &format in _impl_formats() {
|
||||
if let Some(opaque) = get_opaque(format) {
|
||||
// Since the format has an opaque alternative, verify the opaque alternative does not have alpha.
|
||||
assert!(
|
||||
has_alpha(format),
|
||||
"{} has an opaque alternative but does not state it has an alpha component",
|
||||
format
|
||||
);
|
||||
|
||||
// The opaque alternative should not have alpha.
|
||||
assert!(
|
||||
!has_alpha(opaque),
|
||||
"opaque alternative to {} ({}) has an alpha channel",
|
||||
format,
|
||||
opaque
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A format's depth should always be equal or small to it's bits-per-pixel
|
||||
#[test]
|
||||
fn format_bpp_greater_or_equal_than_depth() {
|
||||
for &format in _impl_formats() {
|
||||
assert!(
|
||||
get_depth(format) <= get_bpp(format),
|
||||
"{} has a depth higher than its bpp",
|
||||
format
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+384
@@ -0,0 +1,384 @@
|
||||
//! Module for Buffers created using [libgbm](gbm).
|
||||
//!
|
||||
//! The re-exported [`GbmDevice`](gbm::Device) implements the [`Allocator`] trait
|
||||
//! and [`GbmBuffer`](gbm::BufferObject) satisfies the [`Buffer`] trait while also allowing
|
||||
//! conversions to and from [dmabufs](super::dmabuf).
|
||||
|
||||
use super::{
|
||||
Allocator, Buffer, Format, Fourcc, Modifier,
|
||||
dmabuf::{AsDmabuf, Dmabuf, DmabufFlags, MAX_PLANES},
|
||||
};
|
||||
use crate::backend::drm::DrmNode;
|
||||
use crate::utils::{Buffer as BufferCoords, Size};
|
||||
use drm::buffer::PlanarBuffer;
|
||||
use gbm::BufferObject;
|
||||
pub use gbm::{BufferObjectFlags as GbmBufferFlags, Device as GbmDevice};
|
||||
use std::os::unix::io::{AsFd, BorrowedFd};
|
||||
use tracing::instrument;
|
||||
|
||||
/// A GBM buffer object
|
||||
#[derive(Debug)]
|
||||
pub struct GbmBuffer {
|
||||
drm_node: Option<DrmNode>,
|
||||
bo: BufferObject<()>,
|
||||
size: Size<i32, BufferCoords>,
|
||||
format: Format,
|
||||
}
|
||||
|
||||
impl PlanarBuffer for GbmBuffer {
|
||||
#[inline]
|
||||
fn size(&self) -> (u32, u32) {
|
||||
(self.size.w as u32, self.size.h as u32)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn format(&self) -> Fourcc {
|
||||
self.format.code
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn modifier(&self) -> Option<Modifier> {
|
||||
match self.format.modifier {
|
||||
Modifier::Invalid => None,
|
||||
x => Some(x),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pitches(&self) -> [u32; 4] {
|
||||
self.bo.pitches()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn handles(&self) -> [Option<drm::buffer::Handle>; 4] {
|
||||
self.bo.handles()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn offsets(&self) -> [u32; 4] {
|
||||
self.bo.offsets()
|
||||
}
|
||||
}
|
||||
|
||||
impl drm::buffer::Buffer for GbmBuffer {
|
||||
#[inline]
|
||||
fn size(&self) -> (u32, u32) {
|
||||
(self.size.w as u32, self.size.h as u32)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn format(&self) -> Fourcc {
|
||||
self.format.code
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pitch(&self) -> u32 {
|
||||
self.bo.pitch()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn handle(&self) -> drm::buffer::Handle {
|
||||
drm::buffer::Buffer::handle(&self.bo)
|
||||
}
|
||||
}
|
||||
|
||||
impl GbmBuffer {
|
||||
/// Create a [`GbmBuffer`] from an existing [`BufferObject`]
|
||||
///
|
||||
/// `implicit` forces the object to assume the modifier is `Invalid` for cases,
|
||||
/// where the buffer was allocated with an older api, that doesn't support modifiers.
|
||||
///
|
||||
/// Gbm might otherwise give us the underlying or a non-sensical modifier,
|
||||
/// which can fail in various other apis.
|
||||
pub fn from_bo(bo: BufferObject<()>, implicit: bool) -> Self {
|
||||
let drm_node = DrmNode::from_file(bo.device_fd()).ok();
|
||||
Self::from_bo_with_node(bo, implicit, drm_node)
|
||||
}
|
||||
|
||||
/// Create a [`GbmBuffer`] from an existing [`BufferObject`] explicitly defining the device node
|
||||
///
|
||||
/// `implicit` forces the object to assume the modifier is `Invalid` for cases,
|
||||
/// where the buffer was allocated with an older api, that doesn't support modifiers.
|
||||
///
|
||||
/// Gbm might otherwise give us the underlying or a non-sensical modifier,
|
||||
/// which can fail in various other apis.
|
||||
pub fn from_bo_with_node(bo: BufferObject<()>, implicit: bool, drm_node: Option<DrmNode>) -> Self {
|
||||
let size = (bo.width() as i32, bo.height() as i32).into();
|
||||
let format = Format {
|
||||
code: bo.format(),
|
||||
modifier: if implicit {
|
||||
Modifier::Invalid
|
||||
} else {
|
||||
bo.modifier()
|
||||
},
|
||||
};
|
||||
Self {
|
||||
drm_node,
|
||||
bo,
|
||||
size,
|
||||
format,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the [`DrmNode`] of the device the buffer was allocated when available
|
||||
pub fn device_node(&self) -> Option<DrmNode> {
|
||||
self.drm_node
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for GbmBuffer {
|
||||
type Target = BufferObject<()>;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.bo
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for GbmBuffer {
|
||||
#[inline]
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.bo
|
||||
}
|
||||
}
|
||||
|
||||
/// Light wrapper around an [`GbmDevice`] to implement the [`Allocator`]-trait
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GbmAllocator<A: AsFd + 'static> {
|
||||
drm_node: Option<DrmNode>,
|
||||
device: GbmDevice<A>,
|
||||
default_flags: GbmBufferFlags,
|
||||
}
|
||||
|
||||
impl<A: AsFd + 'static> AsRef<GbmDevice<A>> for GbmAllocator<A> {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &GbmDevice<A> {
|
||||
&self.device
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: AsFd + 'static> AsMut<GbmDevice<A>> for GbmAllocator<A> {
|
||||
#[inline]
|
||||
fn as_mut(&mut self) -> &mut GbmDevice<A> {
|
||||
&mut self.device
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: AsFd + 'static> AsFd for GbmAllocator<A> {
|
||||
#[inline]
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
self.device.as_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: AsFd + 'static> GbmAllocator<A> {
|
||||
/// Create a new [`GbmAllocator`] from a [`GbmDevice`] with some default usage flags,
|
||||
/// to be used when [`Allocator::create_buffer`] is invoked.
|
||||
pub fn new(device: GbmDevice<A>, default_flags: GbmBufferFlags) -> GbmAllocator<A> {
|
||||
let drm_node = DrmNode::from_file(device.as_fd()).ok();
|
||||
GbmAllocator {
|
||||
drm_node,
|
||||
device,
|
||||
default_flags,
|
||||
}
|
||||
}
|
||||
|
||||
/// Alternative to [`Allocator::create_buffer`], if you need a one-off buffer with
|
||||
/// a different set of usage flags.
|
||||
#[instrument(level = "trace", skip(self), fields(self.device = ?self.device, err))]
|
||||
#[profiling::function]
|
||||
pub fn create_buffer_with_flags(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
fourcc: Fourcc,
|
||||
modifiers: &[Modifier],
|
||||
flags: GbmBufferFlags,
|
||||
) -> Result<GbmBuffer, std::io::Error> {
|
||||
#[cfg(feature = "backend_gbm_has_create_with_modifiers2")]
|
||||
let result = self
|
||||
.device
|
||||
.create_buffer_object_with_modifiers2(width, height, fourcc, modifiers.iter().copied(), flags)
|
||||
.map(|bo| GbmBuffer::from_bo_with_node(bo, false, self.drm_node));
|
||||
|
||||
#[cfg(not(feature = "backend_gbm_has_create_with_modifiers2"))]
|
||||
let result = if (flags & !(GbmBufferFlags::SCANOUT | GbmBufferFlags::RENDERING)).is_empty() {
|
||||
self.device
|
||||
.create_buffer_object_with_modifiers(width, height, fourcc, modifiers.iter().copied())
|
||||
.map(|bo| GbmBuffer::from_bo_with_node(bo, false, self.drm_node))
|
||||
} else if modifiers.contains(&Modifier::Invalid) || modifiers.contains(&Modifier::Linear) {
|
||||
return self
|
||||
.device
|
||||
.create_buffer_object(width, height, fourcc, flags)
|
||||
.map(|bo| GbmBuffer::from_bo_with_node(bo, true, self.drm_node));
|
||||
} else {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"unsupported combination of flags and modifiers",
|
||||
));
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(bo) => Ok(bo),
|
||||
Err(err) => {
|
||||
if modifiers.contains(&Modifier::Invalid) || modifiers.contains(&Modifier::Linear) {
|
||||
self.device
|
||||
.create_buffer_object(width, height, fourcc, flags)
|
||||
.map(|bo| GbmBuffer::from_bo_with_node(bo, true, self.drm_node))
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: AsFd + 'static> Allocator for GbmAllocator<A> {
|
||||
type Buffer = GbmBuffer;
|
||||
type Error = std::io::Error;
|
||||
|
||||
#[profiling::function]
|
||||
fn create_buffer(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
fourcc: Fourcc,
|
||||
modifiers: &[Modifier],
|
||||
) -> Result<GbmBuffer, Self::Error> {
|
||||
self.create_buffer_with_flags(width, height, fourcc, modifiers, self.default_flags)
|
||||
}
|
||||
}
|
||||
|
||||
impl Buffer for GbmBuffer {
|
||||
#[inline]
|
||||
fn size(&self) -> Size<i32, BufferCoords> {
|
||||
self.size
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn format(&self) -> Format {
|
||||
self.format
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors during conversion to a dmabuf handle from a gbm buffer object
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum GbmConvertError {
|
||||
/// The buffer consists out of multiple file descriptions, which is currently unsupported
|
||||
#[error("Buffer consists out of multiple file descriptors, which is currently unsupported")]
|
||||
UnsupportedBuffer,
|
||||
/// The conversion returned an invalid file descriptor
|
||||
#[error("Buffer returned invalid file descriptor")]
|
||||
InvalidFD(#[from] gbm::InvalidFdError),
|
||||
}
|
||||
|
||||
impl AsDmabuf for GbmBuffer {
|
||||
type Error = GbmConvertError;
|
||||
|
||||
#[cfg(feature = "backend_gbm_has_fd_for_plane")]
|
||||
#[profiling::function]
|
||||
fn export(&self) -> Result<Dmabuf, GbmConvertError> {
|
||||
let planes = self.plane_count() as i32;
|
||||
|
||||
let mut builder = Dmabuf::builder_from_buffer(self, DmabufFlags::empty());
|
||||
for idx in 0..planes {
|
||||
let fd = self.fd_for_plane(idx)?;
|
||||
|
||||
builder.add_plane(fd, self.offset(idx), self.stride_for_plane(idx));
|
||||
}
|
||||
|
||||
if let Some(node) = self.device_node() {
|
||||
builder.set_node(node);
|
||||
}
|
||||
|
||||
Ok(builder.build().unwrap())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "backend_gbm_has_fd_for_plane"))]
|
||||
#[profiling::function]
|
||||
fn export(&self) -> Result<Dmabuf, GbmConvertError> {
|
||||
let planes = self.plane_count() as i32;
|
||||
|
||||
let mut iter = (0i32..planes).map(|i| self.handle_for_plane(i));
|
||||
let first = iter.next().expect("Encountered a buffer with zero planes");
|
||||
// check that all handles are the same
|
||||
let handle = iter.try_fold(first, |first, next| {
|
||||
if unsafe { next.u64_ == first.u64_ } {
|
||||
return Some(first);
|
||||
}
|
||||
None
|
||||
});
|
||||
if handle.is_none() {
|
||||
// GBM is lacking a function to get a FD for a given plane. Instead,
|
||||
// check all planes have the same handle. We can't use
|
||||
// drmPrimeHandleToFD because that messes up handle ref'counting in
|
||||
// the user-space driver.
|
||||
return Err(GbmConvertError::UnsupportedBuffer);
|
||||
}
|
||||
|
||||
let mut builder = Dmabuf::builder_from_buffer(self, DmabufFlags::empty());
|
||||
for idx in 0..planes {
|
||||
let fd = self.fd()?;
|
||||
|
||||
builder.add_plane(fd, self.offset(idx), self.stride_for_plane(idx));
|
||||
}
|
||||
|
||||
if let Some(node) = self.device_node() {
|
||||
builder.set_node(node);
|
||||
}
|
||||
|
||||
Ok(builder.build().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl Dmabuf {
|
||||
/// Import a Dmabuf using libgbm, creating a gbm Buffer Object to the same underlying data.
|
||||
#[profiling::function]
|
||||
pub fn import_to<A: AsFd + 'static>(
|
||||
&self,
|
||||
gbm: &GbmDevice<A>,
|
||||
usage: GbmBufferFlags,
|
||||
) -> std::io::Result<GbmBuffer> {
|
||||
let mut handles = [None; MAX_PLANES];
|
||||
for (i, handle) in self.handles().take(MAX_PLANES).enumerate() {
|
||||
handles[i] = Some(handle);
|
||||
}
|
||||
let mut strides = [0i32; MAX_PLANES];
|
||||
for (i, stride) in self.strides().take(MAX_PLANES).enumerate() {
|
||||
strides[i] = stride as i32;
|
||||
}
|
||||
let mut offsets = [0i32; MAX_PLANES];
|
||||
for (i, offset) in self.offsets().take(MAX_PLANES).enumerate() {
|
||||
offsets[i] = offset as i32;
|
||||
}
|
||||
|
||||
if self.has_modifier() || self.num_planes() > 1 || self.offsets().next().unwrap() != 0 {
|
||||
gbm.import_buffer_object_from_dma_buf_with_modifiers(
|
||||
self.num_planes() as u32,
|
||||
handles,
|
||||
self.width(),
|
||||
self.height(),
|
||||
self.format().code,
|
||||
usage,
|
||||
strides,
|
||||
offsets,
|
||||
self.format().modifier,
|
||||
)
|
||||
.map(|bo| GbmBuffer::from_bo(bo, false))
|
||||
} else {
|
||||
gbm.import_buffer_object_from_dma_buf(
|
||||
handles[0].unwrap(),
|
||||
self.width(),
|
||||
self.height(),
|
||||
strides[0] as u32,
|
||||
self.format().code,
|
||||
if self.format().modifier == Modifier::Linear {
|
||||
usage | GbmBufferFlags::LINEAR
|
||||
} else {
|
||||
usage
|
||||
},
|
||||
)
|
||||
.map(|bo| GbmBuffer::from_bo(bo, true))
|
||||
}
|
||||
}
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
//! Buffer allocation and management.
|
||||
//!
|
||||
//! Collection of common traits and implementations around
|
||||
//! buffer creation and handling from various sources.
|
||||
//!
|
||||
//! Allocators provided:
|
||||
//! - Dumb Buffers through [`crate::backend::drm::DrmDevice`]
|
||||
//! - Gbm Buffers through [`::gbm::Device`]
|
||||
//!
|
||||
//! Buffer types supported:
|
||||
//! - [DumbBuffers](dumb::DumbBuffer)
|
||||
//! - [GbmBuffers](::gbm::BufferObject)
|
||||
//! - [DmaBufs](dmabuf::Dmabuf)
|
||||
//!
|
||||
//! Helpers:
|
||||
//! - [`Swapchain`] to help with buffer management for framebuffers
|
||||
|
||||
pub mod dmabuf;
|
||||
#[cfg(feature = "backend_drm")]
|
||||
pub mod dumb;
|
||||
pub mod format;
|
||||
#[cfg(feature = "backend_gbm")]
|
||||
pub mod gbm;
|
||||
pub mod udmabuf;
|
||||
#[cfg(feature = "backend_vulkan")]
|
||||
pub mod vulkan;
|
||||
|
||||
mod swapchain;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use crate::utils::{Buffer as BufferCoords, Size};
|
||||
pub use swapchain::{Slot, Swapchain};
|
||||
|
||||
pub use drm_fourcc::{
|
||||
DrmFormat as Format, DrmFourcc as Fourcc, DrmModifier as Modifier, DrmVendor as Vendor,
|
||||
UnrecognizedFourcc, UnrecognizedVendor,
|
||||
};
|
||||
|
||||
/// Common trait describing common properties of most types of buffers.
|
||||
pub trait Buffer {
|
||||
/// Width of the two-dimensional buffer
|
||||
fn width(&self) -> u32 {
|
||||
self.size().w as u32
|
||||
}
|
||||
/// Height of the two-dimensional buffer
|
||||
fn height(&self) -> u32 {
|
||||
self.size().h as u32
|
||||
}
|
||||
/// Size of the two-dimensional buffer
|
||||
fn size(&self) -> Size<i32, BufferCoords>;
|
||||
/// Pixel format of the buffer
|
||||
fn format(&self) -> Format;
|
||||
}
|
||||
|
||||
/// Interface to create Buffers
|
||||
pub trait Allocator {
|
||||
/// Buffer type produced by this allocator
|
||||
type Buffer: Buffer;
|
||||
/// Error type thrown if allocations fail
|
||||
type Error: std::error::Error;
|
||||
|
||||
/// Try to create a buffer with the given dimensions and pixel format
|
||||
fn create_buffer(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
fourcc: Fourcc,
|
||||
modifiers: &[Modifier],
|
||||
) -> Result<Self::Buffer, Self::Error>;
|
||||
}
|
||||
|
||||
// General implementations for interior mutability.
|
||||
|
||||
impl<A: Allocator> Allocator for Arc<Mutex<A>> {
|
||||
type Buffer = A::Buffer;
|
||||
type Error = A::Error;
|
||||
|
||||
fn create_buffer(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
fourcc: Fourcc,
|
||||
modifiers: &[Modifier],
|
||||
) -> Result<Self::Buffer, Self::Error> {
|
||||
let mut guard = self.lock().unwrap();
|
||||
guard.create_buffer(width, height, fourcc, modifiers)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Allocator> Allocator for Rc<RefCell<A>> {
|
||||
type Buffer = A::Buffer;
|
||||
type Error = A::Error;
|
||||
|
||||
fn create_buffer(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
fourcc: Fourcc,
|
||||
modifiers: &[Modifier],
|
||||
) -> Result<Self::Buffer, Self::Error> {
|
||||
self.borrow_mut().create_buffer(width, height, fourcc, modifiers)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Buffer, E: std::error::Error> Allocator for Box<dyn Allocator<Buffer = B, Error = E> + 'static> {
|
||||
type Buffer = B;
|
||||
type Error = E;
|
||||
|
||||
fn create_buffer(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
fourcc: Fourcc,
|
||||
modifiers: &[Modifier],
|
||||
) -> Result<B, E> {
|
||||
(**self).create_buffer(width, height, fourcc, modifiers)
|
||||
}
|
||||
}
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
use std::{
|
||||
fmt,
|
||||
ops::Deref,
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, AtomicU8, Ordering},
|
||||
},
|
||||
};
|
||||
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::backend::allocator::{Allocator, Buffer, Fourcc, Modifier};
|
||||
use crate::utils::user_data::UserDataMap;
|
||||
|
||||
use super::dmabuf::{AsDmabuf, Dmabuf};
|
||||
|
||||
pub const SLOT_CAP: usize = 4;
|
||||
|
||||
/// Swapchain handling a fixed set of re-usable buffers e.g. for scan-out.
|
||||
///
|
||||
/// ## How am I supposed to use this?
|
||||
///
|
||||
/// To do proper buffer management, most compositors do so called double-buffering.
|
||||
/// Which means you use two buffers, one that is currently presented (the front buffer)
|
||||
/// and one that is currently rendered to (the back buffer). After each rendering operation
|
||||
/// you swap the buffers around, the old front buffer becomes the new back buffer, while
|
||||
/// the new front buffer is displayed to the user. This avoids showing the user rendering
|
||||
/// artifacts doing rendering.
|
||||
///
|
||||
/// There are also reasons to do triple-buffering, e.g. if you swap operation takes a
|
||||
/// unspecified amount of time. In that case you have one buffer, that is currently
|
||||
/// displayed, one that is done drawing and about to be swapped in and another one,
|
||||
/// which you can use to render currently.
|
||||
///
|
||||
/// Re-using and managing these buffers becomes increasingly complex the more buffers you
|
||||
/// introduce, which is where `Swapchain` comes into play.
|
||||
///
|
||||
/// `Swapchain` allocates buffers for you and transparently re-created them, e.g. when resizing.
|
||||
/// All you tell the swapchain is: *"Give me the next free buffer"* (by calling [`acquire`](Swapchain::acquire)).
|
||||
/// You then hold on to the returned buffer during rendering and swapping and free it once it is displayed.
|
||||
/// Efficient re-use of the buffers is done by the swapchain.
|
||||
///
|
||||
/// If you have associated resources for each buffer that can be reused (e.g. framebuffer `Handle`s for a `DrmDevice`),
|
||||
/// you can store then in the `Slot`s userdata field. If a buffer is re-used, its userdata is preserved for the next time
|
||||
/// it is returned by `acquire()`.
|
||||
pub struct Swapchain<A: Allocator> {
|
||||
/// Allocator used by the swapchain
|
||||
pub allocator: A,
|
||||
|
||||
width: u32,
|
||||
height: u32,
|
||||
fourcc: Fourcc,
|
||||
modifiers: Vec<Modifier>,
|
||||
|
||||
slots: [Arc<InternalSlot<A::Buffer>>; SLOT_CAP],
|
||||
}
|
||||
|
||||
impl<A: Allocator> fmt::Debug for Swapchain<A> {
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt.debug_struct("Swapchain")
|
||||
.field("width", &self.width)
|
||||
.field("height", &self.height)
|
||||
.field("fourcc", &self.fourcc)
|
||||
.field("modifiers", &self.modifiers)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
/// Slot of a swapchain containing an allocated buffer and its userdata.
|
||||
///
|
||||
/// The buffer is marked for re-use once all copies are dropped.
|
||||
/// Holding on to this struct will block the buffer in the swapchain.
|
||||
#[derive(Debug)]
|
||||
pub struct Slot<B: Buffer>(Arc<InternalSlot<B>>);
|
||||
|
||||
#[derive(Debug)]
|
||||
struct InternalSlot<B: Buffer> {
|
||||
buffer: Option<B>,
|
||||
acquired: AtomicBool,
|
||||
age: AtomicU8,
|
||||
userdata: UserDataMap,
|
||||
}
|
||||
|
||||
impl<B: Buffer> Slot<B> {
|
||||
/// Retrieve userdata for this slot.
|
||||
pub fn userdata(&self) -> &UserDataMap {
|
||||
&self.0.userdata
|
||||
}
|
||||
|
||||
/// Retrieve the age of the buffer
|
||||
pub fn age(&self) -> u8 {
|
||||
self.0.age.load(Ordering::SeqCst)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Buffer> Default for InternalSlot<B> {
|
||||
fn default() -> Self {
|
||||
InternalSlot {
|
||||
buffer: None,
|
||||
acquired: AtomicBool::new(false),
|
||||
age: AtomicU8::new(0),
|
||||
userdata: UserDataMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Buffer> Deref for Slot<B> {
|
||||
type Target = B;
|
||||
fn deref(&self) -> &B {
|
||||
Option::as_ref(&self.0.buffer).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Buffer + AsDmabuf> AsDmabuf for Slot<B> {
|
||||
type Error = <B as AsDmabuf>::Error;
|
||||
|
||||
fn export(&self) -> Result<super::dmabuf::Dmabuf, Self::Error> {
|
||||
let maybe_dmabuf = self.userdata().get::<Dmabuf>();
|
||||
if maybe_dmabuf.is_none() {
|
||||
let dmabuf = (**self).export()?;
|
||||
self.userdata().insert_if_missing_threadsafe(|| dmabuf);
|
||||
}
|
||||
|
||||
Ok(self.userdata().get::<Dmabuf>().cloned().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Buffer> Drop for Slot<B> {
|
||||
fn drop(&mut self) {
|
||||
self.0.acquired.store(false, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> Swapchain<A>
|
||||
where
|
||||
A: Allocator,
|
||||
{
|
||||
/// Create a new swapchain with the desired allocator, dimensions and pixel format for the created buffers.
|
||||
pub fn new(
|
||||
allocator: A,
|
||||
width: u32,
|
||||
height: u32,
|
||||
fourcc: Fourcc,
|
||||
modifiers: Vec<Modifier>,
|
||||
) -> Swapchain<A> {
|
||||
Swapchain {
|
||||
allocator,
|
||||
width,
|
||||
height,
|
||||
fourcc,
|
||||
modifiers,
|
||||
slots: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Acquire a new slot from the swapchain, if one is still free.
|
||||
///
|
||||
/// The swapchain has an internal maximum of four re-usable buffers.
|
||||
/// This function returns the first free one.
|
||||
#[instrument(level = "trace", skip_all, err)]
|
||||
#[profiling::function]
|
||||
pub fn acquire(&mut self) -> Result<Option<Slot<A::Buffer>>, A::Error> {
|
||||
if let Some(free_slot) = self
|
||||
.slots
|
||||
.iter_mut()
|
||||
.find(|s| !s.acquired.swap(true, Ordering::SeqCst))
|
||||
{
|
||||
if free_slot.buffer.is_none() {
|
||||
let free_slot = Arc::get_mut(free_slot).expect("Acquired was false, but Arc is not unique?");
|
||||
match self
|
||||
.allocator
|
||||
.create_buffer(self.width, self.height, self.fourcc, &self.modifiers)
|
||||
{
|
||||
Ok(buffer) => free_slot.buffer = Some(buffer),
|
||||
Err(err) => {
|
||||
free_slot.acquired.store(false, Ordering::SeqCst);
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(free_slot.buffer.is_some());
|
||||
return Ok(Some(Slot(free_slot.clone())));
|
||||
}
|
||||
|
||||
// no free slots
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Mark a given buffer as submitted.
|
||||
///
|
||||
/// This might effect internal data (e.g. buffer age) and may only be called,
|
||||
/// the buffer may not be used for rendering anymore.
|
||||
/// You may hold on to it, if you require keeping it alive.
|
||||
///
|
||||
/// Buffers can always just be safely discarded by dropping them, but not
|
||||
/// calling this function before may affect performance characteristics
|
||||
/// (e.g. by not tracking the buffer age).
|
||||
pub fn submitted(&mut self, slot: &Slot<A::Buffer>) {
|
||||
// don't mess up the state, if the user submitted and old buffer, after e.g. a resize
|
||||
if !self.slots.iter().any(|other| Arc::ptr_eq(&slot.0, other)) {
|
||||
return;
|
||||
}
|
||||
|
||||
slot.0.age.store(1, Ordering::SeqCst);
|
||||
for other_slot in &mut self.slots {
|
||||
if !Arc::ptr_eq(other_slot, &slot.0) && other_slot.buffer.is_some() {
|
||||
#[allow(deprecated, reason = "our MSRV is 1.87 but try_update() was added in 1.95")]
|
||||
let res = other_slot
|
||||
.age
|
||||
.fetch_update(Ordering::SeqCst, Ordering::SeqCst, |age| {
|
||||
if age > 0 { age.checked_add(1) } else { Some(0) }
|
||||
});
|
||||
// If the age overflows the slot was not used for a long time. Lets clear it
|
||||
if res.is_err() {
|
||||
*other_slot = Default::default();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Change the dimensions of newly returned buffers.
|
||||
///
|
||||
/// Already obtained buffers are unaffected and will be cleaned up on drop.
|
||||
pub fn resize(&mut self, width: u32, height: u32) {
|
||||
if self.width == width && self.height == height {
|
||||
return;
|
||||
}
|
||||
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
self.slots = Default::default();
|
||||
}
|
||||
|
||||
/// Remove all internally cached buffers.
|
||||
pub fn reset_buffers(&mut self) {
|
||||
for slot in &mut self.slots {
|
||||
*slot = Default::default();
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset the age for each buffer.
|
||||
///
|
||||
/// Resetting the buffer age will discard all damage information and force a
|
||||
/// full redraw for the next frame.
|
||||
pub fn reset_buffer_ages(&mut self) {
|
||||
for slot in &mut self.slots {
|
||||
match Arc::get_mut(slot) {
|
||||
Some(slot) => slot.age = AtomicU8::new(0),
|
||||
None => *slot = Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get set format
|
||||
pub fn format(&self) -> Fourcc {
|
||||
self.fourcc
|
||||
}
|
||||
|
||||
/// Get allowed modifiers
|
||||
pub fn modifiers(&self) -> &[Modifier] {
|
||||
&self.modifiers
|
||||
}
|
||||
}
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
//! Module containing an implementation of the [`Allocator`]-trait using linux `udmabuf` api.
|
||||
|
||||
#![cfg(target_os = "linux")]
|
||||
|
||||
use std::{
|
||||
io,
|
||||
os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, OwnedFd, RawFd},
|
||||
sync::LazyLock,
|
||||
};
|
||||
|
||||
use super::{
|
||||
Allocator, Fourcc, Modifier,
|
||||
dmabuf::{Dmabuf, DmabufFlags},
|
||||
format::get_bpp,
|
||||
};
|
||||
use crate::utils::Size;
|
||||
|
||||
use rustix::{
|
||||
fs::{MemfdFlags, Mode, OFlags, SealFlags, fcntl_add_seals, ftruncate, memfd_create},
|
||||
ioctl::{Ioctl, Opcode, ioctl, opcode},
|
||||
param::page_size,
|
||||
};
|
||||
|
||||
static PAGE_SIZE: LazyLock<usize> = LazyLock::new(page_size);
|
||||
|
||||
/// [`Allocator`] creating [`Dmabuf`]s for system memory via the `udmabuf` uapi.
|
||||
///
|
||||
/// Note: [`Allocator::create_buffer`] will always return linear buffers for a
|
||||
/// [`UdmabufAllocator`] and fail if [`Modifier::Linear`] is not in the list
|
||||
/// of passed modifiers.
|
||||
#[derive(Debug)]
|
||||
pub struct UdmabufAllocator {
|
||||
dev: OwnedFd,
|
||||
}
|
||||
|
||||
/// Minimum alignment requirement for the stride for `radv` (which seems to be the highest among drivers).
|
||||
pub const STRIDE_ALIGN: usize = 256;
|
||||
|
||||
impl UdmabufAllocator {
|
||||
/// Tries to open `/dev/udmabuf` and create a [`UdmabufAllocator`].
|
||||
pub fn new() -> io::Result<UdmabufAllocator> {
|
||||
let fd = rustix::fs::open("/dev/udmabuf", OFlags::RDONLY | OFlags::CLOEXEC, Mode::empty())?;
|
||||
Ok(UdmabufAllocator { dev: fd })
|
||||
}
|
||||
|
||||
/// Creates a [`Dmabuf`] from an existing allocation represented by a memfd.
|
||||
///
|
||||
/// For importing to succeed a few additional requirements need to be satisfied:
|
||||
/// - the width and height cannot exceed 65535.
|
||||
/// - the stride needs to be aligned to [`STRIDE_ALIGN`].
|
||||
/// - size and offset need to be aligned to the platforms page size.
|
||||
///
|
||||
/// Note: dmabufs created through this interface will always be interpreted as linear.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn create_buffer_from_memfd(
|
||||
&self,
|
||||
mem_fd: impl AsFd,
|
||||
offset: usize,
|
||||
size: usize,
|
||||
format: Fourcc,
|
||||
width: u32,
|
||||
height: u32,
|
||||
stride: u32,
|
||||
) -> Result<Dmabuf, io::Error> {
|
||||
if width >= u16::MAX as u32 || height >= u16::MAX as u32 {
|
||||
return Err(io::ErrorKind::InvalidData.into());
|
||||
}
|
||||
if !(stride as usize).is_multiple_of(STRIDE_ALIGN) {
|
||||
return Err(io::ErrorKind::InvalidData.into());
|
||||
}
|
||||
|
||||
if !size.is_multiple_of(*PAGE_SIZE) || size < (height * stride) as usize {
|
||||
return Err(io::ErrorKind::InvalidData.into());
|
||||
}
|
||||
|
||||
let dma_fd = udmabuf_from_memfd(self.dev.as_fd(), mem_fd.as_fd(), offset as u64, size as u64)?;
|
||||
let mut dmabuf = Dmabuf::builder(
|
||||
Size::new(width as i32, height as i32),
|
||||
format,
|
||||
Modifier::Linear,
|
||||
DmabufFlags::empty(),
|
||||
);
|
||||
dmabuf.add_plane(dma_fd, 0, stride);
|
||||
dmabuf.build().ok_or_else(|| io::ErrorKind::Unsupported.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Allocator for UdmabufAllocator {
|
||||
type Buffer = Dmabuf;
|
||||
type Error = io::Error;
|
||||
|
||||
fn create_buffer(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
fourcc: Fourcc,
|
||||
modifiers: &[Modifier],
|
||||
) -> Result<Self::Buffer, Self::Error> {
|
||||
if width >= u16::MAX as u32 || height >= u16::MAX as u32 {
|
||||
return Err(io::ErrorKind::InvalidData.into());
|
||||
}
|
||||
if !modifiers.contains(&Modifier::Linear) {
|
||||
return Err(io::ErrorKind::InvalidData.into());
|
||||
}
|
||||
let stride = (width as usize * get_bpp(fourcc).ok_or(io::ErrorKind::InvalidData)?)
|
||||
.next_multiple_of(STRIDE_ALIGN);
|
||||
let size = (height as usize * stride).next_multiple_of(*PAGE_SIZE);
|
||||
|
||||
let mem_fd = memfd_create("udmabuf", MemfdFlags::ALLOW_SEALING | MemfdFlags::CLOEXEC)?;
|
||||
ftruncate(&mem_fd, size as u64)?;
|
||||
fcntl_add_seals(&mem_fd, SealFlags::SHRINK)?;
|
||||
self.create_buffer_from_memfd(mem_fd, 0, size, fourcc, width, height, stride as u32)
|
||||
}
|
||||
}
|
||||
|
||||
fn udmabuf_from_memfd(
|
||||
fd: BorrowedFd<'_>,
|
||||
mem_fd: BorrowedFd<'_>,
|
||||
offset: u64,
|
||||
size: u64,
|
||||
) -> io::Result<OwnedFd> {
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
struct udmabuf_create {
|
||||
memfd: u32,
|
||||
flags: u32,
|
||||
offset: u64,
|
||||
size: u64,
|
||||
}
|
||||
const UDMABUF_FLAGS_CLOEXEC: u32 = 0x01;
|
||||
|
||||
unsafe impl Ioctl for udmabuf_create {
|
||||
type Output = RawFd;
|
||||
|
||||
const IS_MUTATING: bool = false;
|
||||
|
||||
fn opcode(&self) -> Opcode {
|
||||
opcode::write::<udmabuf_create>(b'u', 0x42)
|
||||
}
|
||||
|
||||
fn as_ptr(&mut self) -> *mut rustix::ffi::c_void {
|
||||
self as *mut Self as *mut _
|
||||
}
|
||||
|
||||
unsafe fn output_from_ptr(
|
||||
out: rustix::ioctl::IoctlOutput,
|
||||
_extract_output: *mut rustix::ffi::c_void,
|
||||
) -> rustix::io::Result<Self::Output> {
|
||||
if out < 0 {
|
||||
Err(rustix::io::Errno::from_raw_os_error(!out))
|
||||
} else {
|
||||
Ok(out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let args = udmabuf_create {
|
||||
memfd: mem_fd.as_raw_fd() as u32,
|
||||
flags: UDMABUF_FLAGS_CLOEXEC,
|
||||
offset,
|
||||
size,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
ioctl(fd, args)
|
||||
.map(|fd| OwnedFd::from_raw_fd(fd))
|
||||
.map_err(|err| io::Error::from_raw_os_error(err.raw_os_error()))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
//! Format conversions between Vulkan and DRM formats.
|
||||
|
||||
/// Macro to generate format conversions between Vulkan and FourCC format codes.
|
||||
///
|
||||
/// Any entry in this table may have attributes associated with a conversion. This is needed for `PACK` Vulkan
|
||||
/// formats which may only have an alternative given a specific host endian.
|
||||
///
|
||||
/// See the module documentation for usage details.
|
||||
macro_rules! vk_format_table {
|
||||
(
|
||||
$(
|
||||
// This meta specifier is used for format conversions for PACK formats.
|
||||
$(#[$conv_meta:meta])*
|
||||
$fourcc: ident => $vk: ident
|
||||
),* $(,)?
|
||||
) => {
|
||||
/// Converts a FourCC format code to a Vulkan format code.
|
||||
///
|
||||
/// This will return [`None`] if the format is not known.
|
||||
///
|
||||
/// These format conversions will return all known FourCC and Vulkan format conversions. However a
|
||||
/// Vulkan implementation may not support some Vulkan format. One notable example of this are the
|
||||
/// formats introduced in `VK_EXT_4444_formats`. The corresponding FourCC codes will return the
|
||||
/// formats from `VK_EXT_4444_formats`, but the caller is responsible for testing that a Vulkan device
|
||||
/// supports these formats.
|
||||
pub const fn get_vk_format(fourcc: $crate::backend::allocator::Fourcc) -> Option<ash::vk::Format> {
|
||||
// FIXME: Use reexport for ash::vk::Format
|
||||
match fourcc {
|
||||
$(
|
||||
$(#[$conv_meta])*
|
||||
$crate::backend::allocator::Fourcc::$fourcc => Some(ash::vk::Format::$vk),
|
||||
)*
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all the known format conversions.
|
||||
///
|
||||
/// The list contains FourCC format codes that may be converted using [`get_vk_format`].
|
||||
pub const fn known_formats() -> &'static [$crate::backend::allocator::Fourcc] {
|
||||
&[
|
||||
$(
|
||||
$crate::backend::allocator::Fourcc::$fourcc
|
||||
),*
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// FIXME: SRGB format is not always correct.
|
||||
//
|
||||
// Vulkan classifies formats by both channel sizes and colorspace. FourCC format codes do not classify formats
|
||||
// based on colorspace.
|
||||
//
|
||||
// To implement this correctly, it is likely that parsing vulkan.xml and classifying families of colorspaces
|
||||
// would be needed since there are a lot of formats.
|
||||
//
|
||||
// Many of these conversions come from wsi_common_wayland.c in Mesa
|
||||
vk_format_table! {
|
||||
Argb8888 => B8G8R8A8_SRGB,
|
||||
Xrgb8888 => B8G8R8A8_SRGB,
|
||||
|
||||
Abgr8888 => R8G8B8A8_SRGB,
|
||||
Xbgr8888 => R8G8B8A8_SRGB,
|
||||
|
||||
// PACK32 formats are equivalent to u32 instead of [u8; 4] and thus depend their layout depends the host
|
||||
// endian.
|
||||
#[cfg(target_endian = "little")]
|
||||
Rgba8888 => A8B8G8R8_SRGB_PACK32,
|
||||
#[cfg(target_endian = "little")]
|
||||
Rgbx8888 => A8B8G8R8_SRGB_PACK32,
|
||||
|
||||
#[cfg(target_endian = "little")]
|
||||
Argb2101010 => A2R10G10B10_UNORM_PACK32,
|
||||
#[cfg(target_endian = "little")]
|
||||
Xrgb2101010 => A2R10G10B10_UNORM_PACK32,
|
||||
|
||||
#[cfg(target_endian = "little")]
|
||||
Abgr2101010 => A2B10G10R10_UNORM_PACK32,
|
||||
#[cfg(target_endian = "little")]
|
||||
Xbgr2101010 => A2B10G10R10_UNORM_PACK32,
|
||||
}
|
||||
+807
@@ -0,0 +1,807 @@
|
||||
//! Module for Buffers created using Vulkan.
|
||||
//!
|
||||
//! The [`VulkanAllocator`] type implements the [`Allocator`] trait and [`VulkanImage`] implements [`Buffer`].
|
||||
//! A [`VulkanImage`] may be exported as a [dmabuf](super::dmabuf).
|
||||
//!
|
||||
//! The Vulkan allocator supports up to Vulkan 1.3.
|
||||
//!
|
||||
//! The Vulkan allocator requires the following device extensions (and their dependencies):
|
||||
//! - `VK_EXT_image_drm_format_modifier`
|
||||
//! - `VK_EXT_external_memory_dmabuf`
|
||||
//! - `VK_KHR_external_memory_fd`
|
||||
//!
|
||||
//! Additionally the Vulkan allocator may enable the following extensions if available:
|
||||
//! - `VK_EXT_4444_formats`
|
||||
//!
|
||||
//! To get the required extensions a device must support to use the Vulkan allocator, use
|
||||
//! [`VulkanAllocator::required_extensions`].
|
||||
|
||||
pub mod format;
|
||||
|
||||
use std::{
|
||||
ffi::CStr,
|
||||
fmt,
|
||||
os::unix::io::{FromRawFd, OwnedFd},
|
||||
sync::{Arc, Weak, mpsc},
|
||||
};
|
||||
|
||||
use ash::{ext, khr, vk};
|
||||
use bitflags::bitflags;
|
||||
use drm_fourcc::{DrmFormat, DrmFourcc, DrmModifier};
|
||||
use tracing::instrument;
|
||||
|
||||
#[cfg(feature = "backend_drm")]
|
||||
use crate::backend::drm::DrmNode;
|
||||
use crate::{
|
||||
backend::{
|
||||
allocator::dmabuf::DmabufFlags,
|
||||
vulkan::{PhysicalDevice, version::Version},
|
||||
},
|
||||
utils::{Buffer as BufferCoord, Size},
|
||||
};
|
||||
|
||||
use super::{
|
||||
Allocator, Buffer,
|
||||
dmabuf::{AsDmabuf, Dmabuf, MAX_PLANES},
|
||||
};
|
||||
|
||||
bitflags! {
|
||||
/// Flags to indicate the intended usage for a buffer.
|
||||
///
|
||||
/// The usage flags may influence whether a buffer with a specific format and modifier combination may
|
||||
/// successfully be imported by a graphics api when it is exported as a [`Dmabuf`].
|
||||
///
|
||||
/// For example, if a [`Buffer`] is used for scan-out, it is only necessary to specify the color
|
||||
/// attachment usage. However, if the exported buffer is used by a client and intended to be rendered to,
|
||||
/// then it is possible that the import will fail because the buffer was not allocated with right usage
|
||||
/// for the format and modifier combination.
|
||||
///
|
||||
/// The default usage set when creating a [`VulkanAllocator`] guarantees that an exported buffer may be
|
||||
/// imported successfully with the same usage.
|
||||
///
|
||||
/// If you need to allocate buffers with different usages dynamically, then you may use
|
||||
/// [`VulkanAllocator::create_buffer_with_usage`].
|
||||
///
|
||||
/// [`VulkanAllocator::is_format_supported`] can check if the combination of format, modifier and usage
|
||||
/// flags are supported.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ImageUsageFlags: vk::Flags {
|
||||
/// The image may be the source of a transfer command.
|
||||
///
|
||||
/// This allows the content of the exported buffer to be downloaded from the GPU.
|
||||
const TRANSFER_SRC = vk::ImageUsageFlags::TRANSFER_SRC.as_raw();
|
||||
|
||||
/// The image may be the destination of a transfer command.
|
||||
///
|
||||
/// This allows the content of the exported buffer to be modified by a memory upload to the GPU.
|
||||
const TRANSFER_DST = vk::ImageUsageFlags::TRANSFER_DST.as_raw();
|
||||
|
||||
/// Image may be sampled in a shader.
|
||||
///
|
||||
/// This should be used if the exported buffer will be used as a texture.
|
||||
const SAMPLED = vk::ImageUsageFlags::SAMPLED.as_raw();
|
||||
|
||||
/// The image may be used in a color attachment.
|
||||
///
|
||||
/// This should be used if the exported buffer will be rendered to.
|
||||
const COLOR_ATTACHMENT = vk::ImageUsageFlags::COLOR_ATTACHMENT.as_raw();
|
||||
}
|
||||
}
|
||||
|
||||
/// Error type for [`VulkanAllocator`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// The allocator could not be created.
|
||||
///
|
||||
/// This can occur for a few reasons:
|
||||
/// - No suitable queue family was found.
|
||||
#[error("could not create allocator")]
|
||||
Setup,
|
||||
|
||||
/// The size specified to create the buffer was too small.
|
||||
///
|
||||
/// The size must be greater than `0 x 0` pixels.
|
||||
#[error("invalid buffer size")]
|
||||
InvalidSize,
|
||||
|
||||
/// The specified format is not supported.
|
||||
///
|
||||
/// This error may occur for a few reasons:
|
||||
/// 1. There is no equivalent Vulkan format for the specified format code.
|
||||
/// 2. The driver does not support any of the specified modifiers for the format code.
|
||||
/// 3. The driver does support some of the format, but the size of the buffer requested is too large.
|
||||
/// 4. The usage is empty
|
||||
#[error("format is not supported")]
|
||||
UnsupportedFormat,
|
||||
|
||||
/// Some error from the Vulkan driver.
|
||||
#[error(transparent)]
|
||||
Vk(#[from] vk::Result),
|
||||
}
|
||||
|
||||
/// An allocator which uses Vulkan to create buffers.
|
||||
pub struct VulkanAllocator {
|
||||
formats: Vec<FormatEntry>,
|
||||
images: Vec<ImageInner>,
|
||||
default_usage: ImageUsageFlags,
|
||||
remaining_allocations: u32,
|
||||
extension_fns: ExtensionFns,
|
||||
dropped_recv: mpsc::Receiver<ImageInner>,
|
||||
dropped_sender: mpsc::Sender<ImageInner>,
|
||||
phd: PhysicalDevice,
|
||||
#[cfg(feature = "backend_drm")]
|
||||
node: Option<DrmNode>,
|
||||
device: Arc<ash::Device>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for VulkanAllocator {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("VulkanAllocator")
|
||||
.field("formats", &self.formats)
|
||||
.field("images", &self.images)
|
||||
.field("default_usage", &self.default_usage)
|
||||
.field("remaining_allocations", &self.remaining_allocations)
|
||||
.field("dropped_recv", &self.dropped_recv)
|
||||
.field("dropped_sender", &self.dropped_sender)
|
||||
.field("phd", &self.phd)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl VulkanAllocator {
|
||||
/// Maximum supported version instance version that may be used with the allocator.
|
||||
pub const MAX_INSTANCE_VERSION: Version = Version::VERSION_1_3;
|
||||
|
||||
/// Returns the list of device extensions required by the Vulkan allocator.
|
||||
///
|
||||
/// This function may return a different list for each [`PhysicalDevice`], meaning each device should be
|
||||
/// filtered using it's own call to this function.
|
||||
pub fn required_extensions(phd: &PhysicalDevice) -> Vec<&'static CStr> {
|
||||
// Always required extensions
|
||||
let mut extensions = vec![
|
||||
ext::image_drm_format_modifier::NAME,
|
||||
ext::external_memory_dma_buf::NAME,
|
||||
khr::external_memory_fd::NAME,
|
||||
];
|
||||
|
||||
if phd.api_version() < Version::VERSION_1_2 {
|
||||
// VK_EXT_image_drm_format_modifier requires VK_KHR_image_format_list.
|
||||
// VK_KHR_image_format_list is part of the core API in Vulkan 1.2
|
||||
extensions.push(khr::image_format_list::NAME);
|
||||
}
|
||||
|
||||
// Optional extensions:
|
||||
|
||||
// VK_EXT_4444_formats is part of the core API in Vulkan 1.3. Although not always supported
|
||||
// (see the 1.3 features to enable)
|
||||
if phd.api_version() < Version::VERSION_1_3 {
|
||||
// In 1.2 and below, the device must support the extension to use it.
|
||||
if phd.has_device_extension(ext::_4444_formats::NAME) {
|
||||
extensions.push(ext::_4444_formats::NAME);
|
||||
}
|
||||
}
|
||||
|
||||
extensions
|
||||
}
|
||||
|
||||
/// Creates a [`VulkanAllocator`].
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - If the version of instance which created the [`PhysicalDevice`] is higher than [`VulkanAllocator::MAX_INSTANCE_VERSION`].
|
||||
/// - If the default [`ImageUsageFlags`] are empty.
|
||||
#[instrument(err, skip(phd), fields(physical_device = phd.name()))]
|
||||
pub fn new(phd: &PhysicalDevice, default_usage: ImageUsageFlags) -> Result<VulkanAllocator, Error> {
|
||||
// Panic if the instance version is too high
|
||||
if phd.instance().api_version() > Self::MAX_INSTANCE_VERSION {
|
||||
panic!("Exceeded maximum instance api version for VulkanAllocator (1.3 max)")
|
||||
}
|
||||
|
||||
// VUID-VkPhysicalDeviceImageFormatInfo2-usage-requiredbitmask
|
||||
// At least one image usage flag must be specified.
|
||||
if default_usage.is_empty() {
|
||||
panic!("Default usage flags for allocator are empty")
|
||||
}
|
||||
|
||||
// Get required extensions
|
||||
let extensions = Self::required_extensions(phd);
|
||||
let extension_pointers = extensions.iter().copied().map(CStr::as_ptr).collect::<Vec<_>>();
|
||||
|
||||
// We don't actually submit any commands to the queue, but Vulkan requires that we create devices with
|
||||
// at least one queue (VUID-VkDeviceCreateInfo-queueCreateInfoCount-arraylength).
|
||||
let queue_families = unsafe {
|
||||
phd.instance()
|
||||
.handle()
|
||||
.get_physical_device_queue_family_properties(phd.handle())
|
||||
};
|
||||
|
||||
let queue_family_index = queue_families
|
||||
.iter()
|
||||
// Find a queue with transfer
|
||||
.position(|properties| properties.queue_flags.contains(vk::QueueFlags::TRANSFER))
|
||||
// If there is no transfer queue family, then try graphics (which must support transfer)
|
||||
.or_else(|| {
|
||||
queue_families
|
||||
.iter()
|
||||
.position(|properties| properties.queue_flags.contains(vk::QueueFlags::GRAPHICS))
|
||||
})
|
||||
.ok_or(Error::Setup)?;
|
||||
|
||||
// TODO: Enable 4444 formats features
|
||||
let queue_create_info = [vk::DeviceQueueCreateInfo::default()
|
||||
.queue_family_index(queue_family_index as u32)
|
||||
.queue_priorities(&[0.0])];
|
||||
let create_info = vk::DeviceCreateInfo::default()
|
||||
.enabled_extension_names(&extension_pointers)
|
||||
.queue_create_infos(&queue_create_info);
|
||||
|
||||
let instance = phd.instance().handle();
|
||||
let device = unsafe { instance.create_device(phd.handle(), &create_info, None) }?;
|
||||
|
||||
// Load extension functions
|
||||
let extension_fns = ExtensionFns {
|
||||
ext_image_format_modifier: ext::image_drm_format_modifier::Device::new(instance, &device),
|
||||
khr_external_memory_fd: khr::external_memory_fd::Device::new(instance, &device),
|
||||
};
|
||||
|
||||
let (dropped_sender, dropped_recv) = mpsc::channel();
|
||||
|
||||
#[cfg(feature = "backend_drm")]
|
||||
let node = phd
|
||||
.render_node()
|
||||
.ok()
|
||||
.flatten()
|
||||
.or_else(|| phd.primary_node().ok().flatten());
|
||||
|
||||
let mut allocator = VulkanAllocator {
|
||||
formats: Vec::new(),
|
||||
images: Vec::new(),
|
||||
default_usage,
|
||||
remaining_allocations: phd.limits().max_memory_allocation_count,
|
||||
extension_fns,
|
||||
dropped_recv,
|
||||
dropped_sender,
|
||||
phd: phd.clone(),
|
||||
#[cfg(feature = "backend_drm")]
|
||||
node,
|
||||
device: Arc::new(device),
|
||||
};
|
||||
|
||||
allocator.init_formats();
|
||||
|
||||
Ok(allocator)
|
||||
}
|
||||
|
||||
/// Returns whether this allocator supports the specified format with the usage flags.
|
||||
pub fn is_format_supported(&self, format: DrmFormat, usage: ImageUsageFlags) -> bool {
|
||||
// VUID-VkPhysicalDeviceImageFormatInfo2-usage-requiredbitmask
|
||||
// At least one image usage flag must be specified.
|
||||
if usage.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Check if the extents are also valid?
|
||||
// Vulkan states a maximum extent size for images.
|
||||
// This may also be useful as a function on Allocator.
|
||||
unsafe { self.get_format_info(format, vk::ImageUsageFlags::from_raw(usage.bits())) }
|
||||
.ok()
|
||||
.is_some()
|
||||
}
|
||||
|
||||
/// Try to create a buffer with the given dimensions, pixel format and usage flags.
|
||||
///
|
||||
/// This may return [`Err`] for one of the following reasons:
|
||||
/// - The `usage` is empty.
|
||||
/// - The `fourcc` format is not supported.
|
||||
/// - All of the allowed `modifiers` are not supported.
|
||||
/// - The size of the buffer is too large for the `usage`, `fourcc` format or `modifiers`.
|
||||
/// - The `fourcc` format and `modifiers` do not support the specified usage.
|
||||
#[instrument(level = "trace", err)]
|
||||
#[profiling::function]
|
||||
pub fn create_buffer_with_usage(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
fourcc: DrmFourcc,
|
||||
modifiers: &[DrmModifier],
|
||||
usage: ImageUsageFlags,
|
||||
) -> Result<VulkanImage, Error> {
|
||||
self.cleanup();
|
||||
|
||||
let vk_format = format::get_vk_format(fourcc).ok_or(Error::UnsupportedFormat)?;
|
||||
let vk_usage = vk::ImageUsageFlags::from_raw(usage.bits());
|
||||
|
||||
// VUID-VkImageCreateInfo-extent-00944, VUID-VkImageCreateInfo-extent-00945
|
||||
if width == 0 || height == 0 {
|
||||
return Err(Error::InvalidSize);
|
||||
}
|
||||
|
||||
// VUID-VkPhysicalDeviceImageFormatInfo2-usage-requiredbitmask
|
||||
// At least one image usage flag must be specified.
|
||||
if usage.is_empty() {
|
||||
return Err(Error::UnsupportedFormat);
|
||||
}
|
||||
|
||||
// VUID-VkImageCreateInfo-usage-00964, VUID-VkImageCreateInfo-usage-00965
|
||||
if usage.contains(ImageUsageFlags::COLOR_ATTACHMENT) {
|
||||
let limits = self.phd.limits();
|
||||
|
||||
if width > limits.max_framebuffer_width || height > limits.max_framebuffer_height {
|
||||
return Err(Error::InvalidSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out any format + modifier combinations that are not supported
|
||||
let modifiers = self.filter_modifiers(width, height, vk_usage, fourcc, modifiers);
|
||||
|
||||
// VUID-VkImageDrmFormatModifierListCreateInfoEXT-drmFormatModifierCount-arraylength
|
||||
if modifiers.is_empty() {
|
||||
return Err(Error::UnsupportedFormat);
|
||||
}
|
||||
|
||||
Ok(unsafe { self.create_image(width, height, vk_format, vk_usage, fourcc, &modifiers[..]) }?)
|
||||
}
|
||||
|
||||
/// Returns the [`PhysicalDevice`] this allocator was created with.
|
||||
pub fn physical_device(&self) -> &PhysicalDevice {
|
||||
&self.phd
|
||||
}
|
||||
}
|
||||
|
||||
impl Allocator for VulkanAllocator {
|
||||
type Buffer = VulkanImage;
|
||||
type Error = Error;
|
||||
|
||||
fn create_buffer(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
fourcc: DrmFourcc,
|
||||
modifiers: &[DrmModifier],
|
||||
) -> Result<VulkanImage, Self::Error> {
|
||||
self.create_buffer_with_usage(width, height, fourcc, modifiers, self.default_usage)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VulkanAllocator {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
for image in &self.images {
|
||||
self.device.destroy_image(image.image, None);
|
||||
self.device.free_memory(image.memory, None);
|
||||
}
|
||||
|
||||
self.device.destroy_device(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Vulkan image object.
|
||||
///
|
||||
/// This type implements [`Buffer`] and the underlying image may be exported as a dmabuf.
|
||||
pub struct VulkanImage {
|
||||
inner: ImageInner,
|
||||
width: u32,
|
||||
height: u32,
|
||||
format: DrmFormat,
|
||||
#[cfg(feature = "backend_drm")]
|
||||
node: Option<DrmNode>,
|
||||
/// The number of planes the image has for dmabuf export.
|
||||
format_plane_count: u32,
|
||||
khr_external_memory_fd: khr::external_memory_fd::Device,
|
||||
dropped_sender: mpsc::Sender<ImageInner>,
|
||||
device: Weak<ash::Device>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for VulkanImage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("VulkanImage")
|
||||
.field("width", &self.width)
|
||||
.field("height", &self.height)
|
||||
.field("format", &self.format)
|
||||
.field("inner", &self.inner)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Buffer for VulkanImage {
|
||||
fn width(&self) -> u32 {
|
||||
self.width
|
||||
}
|
||||
|
||||
fn height(&self) -> u32 {
|
||||
self.height
|
||||
}
|
||||
|
||||
fn size(&self) -> Size<i32, BufferCoord> {
|
||||
(self.width as i32, self.height as i32).into()
|
||||
}
|
||||
|
||||
fn format(&self) -> DrmFormat {
|
||||
self.format
|
||||
}
|
||||
}
|
||||
|
||||
impl AsDmabuf for VulkanImage {
|
||||
type Error = ExportError;
|
||||
|
||||
#[profiling::function]
|
||||
fn export(&self) -> Result<Dmabuf, Self::Error> {
|
||||
let device = self.device.upgrade().ok_or(ExportError::AllocatorDestroyed)?;
|
||||
|
||||
// Implementation may be broken if the plane count is wrong.
|
||||
if self.format_plane_count == 0 {
|
||||
return Err(ExportError::Failed);
|
||||
}
|
||||
|
||||
assert!(
|
||||
self.format_plane_count as usize <= MAX_PLANES,
|
||||
"Vulkan implementation reported too many planes"
|
||||
);
|
||||
|
||||
let create_info = vk::MemoryGetFdInfoKHR::default()
|
||||
.handle_type(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT)
|
||||
// VUID-VkMemoryGetFdInfoKHR-handleType-00671: Memory was allocated with DMA_BUF_EXT
|
||||
.memory(self.inner.memory);
|
||||
|
||||
let fd = unsafe { self.khr_external_memory_fd.get_memory_fd(&create_info) }?;
|
||||
// SAFETY: `vkGetMemoryFdKHR` creates a new file descriptor owned by the caller.
|
||||
let fd = Arc::new(unsafe { OwnedFd::from_raw_fd(fd) });
|
||||
let mut builder = Dmabuf::builder(
|
||||
self.size(),
|
||||
self.format().code,
|
||||
self.format().modifier,
|
||||
DmabufFlags::empty(),
|
||||
);
|
||||
|
||||
for idx in 0..self.format_plane_count {
|
||||
// get_image_subresource_layout only gets the layout of one memory plane. This mask specifies
|
||||
// which plane should the layout be obtained for.
|
||||
let aspect_mask = match idx {
|
||||
0 => vk::ImageAspectFlags::MEMORY_PLANE_0_EXT,
|
||||
1 => vk::ImageAspectFlags::MEMORY_PLANE_1_EXT,
|
||||
2 => vk::ImageAspectFlags::MEMORY_PLANE_2_EXT,
|
||||
3 => vk::ImageAspectFlags::MEMORY_PLANE_3_EXT,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
// VUID-vkGetImageSubresourceLayout-image-02270: All allocate images are created with drm tiling
|
||||
let subresource = vk::ImageSubresource::default().aspect_mask(aspect_mask);
|
||||
let layout = unsafe { device.get_image_subresource_layout(self.inner.image, subresource) };
|
||||
builder.add_plane(fd.clone(), layout.offset as u32, layout.row_pitch as u32);
|
||||
}
|
||||
|
||||
#[cfg(feature = "backend_drm")]
|
||||
if let Some(node) = self.node {
|
||||
builder.set_node(node);
|
||||
}
|
||||
|
||||
Ok(builder.build().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VulkanImage {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.dropped_sender.send(self.inner);
|
||||
}
|
||||
}
|
||||
|
||||
/// The error type for exporting a [`VulkanImage`] as a [`Dmabuf`].
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ExportError {
|
||||
/// The image could not export a dmabuf since the allocator has been destroyed.
|
||||
#[error("allocator has been destroyed")]
|
||||
AllocatorDestroyed,
|
||||
|
||||
/// The allocator could not export a dmabuf for an implementation dependent reason.
|
||||
#[error("could not export a dmabuf")]
|
||||
Failed,
|
||||
|
||||
/// Vulkan API error.
|
||||
#[error(transparent)]
|
||||
Vk(#[from] vk::Result),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
struct ImageInner {
|
||||
// TODO: image usage?
|
||||
image: vk::Image,
|
||||
/// The first entry will always have a non-null device memory handle.
|
||||
///
|
||||
/// The other three entries may be a null handle or valid device memory (the latter with disjoint dmabufs).
|
||||
memory: vk::DeviceMemory,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FormatEntry {
|
||||
format: DrmFormat,
|
||||
modifier_properties: vk::DrmFormatModifierPropertiesEXT,
|
||||
}
|
||||
|
||||
struct ExtensionFns {
|
||||
/// Functions to get DRM format information.
|
||||
///
|
||||
/// These are required, and therefore you may assume this functionality is available.
|
||||
ext_image_format_modifier: ext::image_drm_format_modifier::Device,
|
||||
/// Functions used for dmabuf import and export.
|
||||
///
|
||||
/// If this is [`Some`], then the allocator will support dmabuf import and export operations.
|
||||
khr_external_memory_fd: khr::external_memory_fd::Device,
|
||||
}
|
||||
|
||||
impl VulkanAllocator {
|
||||
fn init_formats(&mut self) {
|
||||
for &fourcc in format::known_formats() {
|
||||
let vk_format = format::get_vk_format(fourcc).unwrap();
|
||||
let modifier_properties = self
|
||||
.phd
|
||||
.get_format_modifier_properties(vk_format)
|
||||
.expect("The Vulkan allocator requires VK_EXT_image_drm_format_modifier");
|
||||
|
||||
for modifier_properties in modifier_properties {
|
||||
self.formats.push(FormatEntry {
|
||||
format: DrmFormat {
|
||||
code: fourcc,
|
||||
modifier: DrmModifier::from(modifier_properties.drm_format_modifier),
|
||||
},
|
||||
modifier_properties,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the format + modifier combination and the usage flags are supported.
|
||||
unsafe fn get_format_info(
|
||||
&self,
|
||||
format: DrmFormat,
|
||||
usage: vk::ImageUsageFlags,
|
||||
) -> Result<Option<vk::ImageFormatProperties>, vk::Result> {
|
||||
let vk_format = format::get_vk_format(format.code);
|
||||
|
||||
match vk_format {
|
||||
Some(vk_format) => {
|
||||
let mut image_drm_format_info = vk::PhysicalDeviceImageDrmFormatModifierInfoEXT::default()
|
||||
.drm_format_modifier(format.modifier.into())
|
||||
.sharing_mode(vk::SharingMode::EXCLUSIVE);
|
||||
let format_info = vk::PhysicalDeviceImageFormatInfo2::default()
|
||||
.format(vk_format)
|
||||
.ty(vk::ImageType::TYPE_2D)
|
||||
.tiling(vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT)
|
||||
.usage(usage)
|
||||
.flags(vk::ImageCreateFlags::empty())
|
||||
// VUID-VkPhysicalDeviceImageFormatInfo2-tiling-02249
|
||||
.push_next(&mut image_drm_format_info);
|
||||
let mut image_format_properties = vk::ImageFormatProperties2::default();
|
||||
|
||||
// VUID-vkGetPhysicalDeviceImageFormatProperties-tiling-02248: Must use vkGetPhysicalDeviceImageFormatProperties2
|
||||
let result = unsafe {
|
||||
self.phd
|
||||
.instance()
|
||||
.handle()
|
||||
.get_physical_device_image_format_properties2(
|
||||
self.phd.handle(),
|
||||
&format_info,
|
||||
&mut image_format_properties,
|
||||
)
|
||||
};
|
||||
|
||||
result
|
||||
.map(|_| Some(image_format_properties.image_format_properties))
|
||||
.or_else(|result| {
|
||||
// Unsupported format + usage combination
|
||||
if result == vk::Result::ERROR_FORMAT_NOT_SUPPORTED {
|
||||
Ok(None)
|
||||
} else {
|
||||
Err(result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_modifiers(
|
||||
&self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
vk_usage: vk::ImageUsageFlags,
|
||||
fourcc: DrmFourcc,
|
||||
modifiers: &[DrmModifier],
|
||||
) -> Vec<u64> {
|
||||
modifiers
|
||||
.iter()
|
||||
.copied()
|
||||
.filter_map(move |modifier| {
|
||||
let info = unsafe {
|
||||
self.get_format_info(
|
||||
DrmFormat {
|
||||
code: fourcc,
|
||||
modifier,
|
||||
},
|
||||
vk_usage,
|
||||
)
|
||||
}
|
||||
.ok()
|
||||
.flatten()?;
|
||||
|
||||
Some((modifier, info))
|
||||
})
|
||||
// Filter modifiers where the required image creation limits are not met
|
||||
.filter(move |(_, properties)| {
|
||||
let max_extent = properties.max_extent;
|
||||
|
||||
// VUID-VkImageCreateInfo-extent-02252
|
||||
max_extent.width >= width
|
||||
// VUID-VkImageCreateInfo-extent-02253
|
||||
&& max_extent.height >= height
|
||||
// VUID-VkImageCreateInfo-extent-02254
|
||||
// VUID-VkImageCreateInfo-extent-00946
|
||||
// VUID-VkImageCreateInfo-imageType-00957
|
||||
&& max_extent.depth >= 1
|
||||
// VUID-VkImageCreateInfo-samples-02258
|
||||
&& properties.sample_counts.contains(vk::SampleCountFlags::TYPE_1)
|
||||
})
|
||||
.map(|(modifier, _)| modifier)
|
||||
.map(Into::<u64>::into)
|
||||
// TODO: Could use a smallvec or tinyvec to reduce number allocations
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// * The list of modifiers must be supported for the given format and image usage flags.
|
||||
/// * The extent of the image must be within the maximum extents Vulkan tells.
|
||||
unsafe fn create_image(
|
||||
&mut self,
|
||||
width: u32,
|
||||
height: u32,
|
||||
vk_format: vk::Format,
|
||||
vk_usage: vk::ImageUsageFlags,
|
||||
fourcc: DrmFourcc,
|
||||
modifiers: &[u64],
|
||||
) -> Result<VulkanImage, vk::Result> {
|
||||
assert!(width > 0);
|
||||
assert!(height > 0);
|
||||
|
||||
// Ensure maximum allocations are not exceeded.
|
||||
if self.remaining_allocations == 0 {
|
||||
todo!()
|
||||
}
|
||||
|
||||
// Now that the list of valid modifiers is known, create an image using one of the modifiers.
|
||||
let mut modifier_list =
|
||||
vk::ImageDrmFormatModifierListCreateInfoEXT::default().drm_format_modifiers(modifiers);
|
||||
let mut external_memory_image_create_info = vk::ExternalMemoryImageCreateInfo::default()
|
||||
.handle_types(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT);
|
||||
|
||||
let image_create_info = vk::ImageCreateInfo::default()
|
||||
.image_type(vk::ImageType::TYPE_2D)
|
||||
.format(vk_format)
|
||||
.extent(vk::Extent3D {
|
||||
width,
|
||||
height,
|
||||
// VUID-VkImageCreateInfo-extent-00946
|
||||
// VUID-VkImageCreateInfo-imageType-00957
|
||||
depth: 1,
|
||||
})
|
||||
// VUID-VkImageCreateInfo-samples-parameter
|
||||
.samples(vk::SampleCountFlags::TYPE_1)
|
||||
// VUID-VkImageCreateInfo-mipLevels-00947
|
||||
.mip_levels(1)
|
||||
// VUID-VkImageCreateInfo-arrayLayers-00948
|
||||
.array_layers(1)
|
||||
// VUID-VkImageCreateInfo-pNext-02262
|
||||
.tiling(vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT)
|
||||
// VUID-VkImageCreateInfo-usage-requiredbitmask
|
||||
// FIXME: We don't assert any usage flags are set
|
||||
.usage(vk_usage)
|
||||
// VUID-VkImageCreateInfo-initialLayout-00993
|
||||
.initial_layout(vk::ImageLayout::UNDEFINED)
|
||||
// VUID-VkImageCreateInfo-tiling-02261
|
||||
.push_next(&mut modifier_list)
|
||||
// TODO: VUID-VkImageCreateInfo-pNext-00990
|
||||
.push_next(&mut external_memory_image_create_info);
|
||||
|
||||
// The image is placed in a scope guard to safely handle future allocation failures.
|
||||
let mut guard = scopeguard::guard(
|
||||
ImageInner {
|
||||
// This is the only spot where ? may be used to detect and error since no previous handles have been created.
|
||||
image: unsafe { self.device.create_image(&image_create_info, None) }?,
|
||||
memory: vk::DeviceMemory::null(),
|
||||
},
|
||||
|inner| unsafe { self.device.destroy_image(inner.image, None) },
|
||||
);
|
||||
|
||||
// Get the modifier Vulkan created the image using.
|
||||
let format = {
|
||||
let mut image_modifier_properties = vk::ImageDrmFormatModifierPropertiesEXT::default();
|
||||
|
||||
unsafe {
|
||||
self.extension_fns
|
||||
.ext_image_format_modifier
|
||||
.get_image_drm_format_modifier_properties(guard.image, &mut image_modifier_properties)
|
||||
}?;
|
||||
|
||||
DrmFormat {
|
||||
code: fourcc,
|
||||
modifier: DrmModifier::from(image_modifier_properties.drm_format_modifier),
|
||||
}
|
||||
};
|
||||
|
||||
// Now that we know the plane count, get the number of planes for the format + modifier
|
||||
let format_plane_count = self
|
||||
.formats
|
||||
.iter()
|
||||
.find(|entry| entry.format == format)
|
||||
.unwrap()
|
||||
.modifier_properties
|
||||
.drm_format_modifier_plane_count;
|
||||
|
||||
// Allocate image memory
|
||||
let memory_reqs = unsafe { self.device.get_image_memory_requirements(guard.image) };
|
||||
// TODO: Memory type index
|
||||
let mut export_memory_allocate_info = vk::ExportMemoryAllocateInfo::default()
|
||||
.handle_types(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT);
|
||||
let alloc_create_info = vk::MemoryAllocateInfo::default()
|
||||
.allocation_size(memory_reqs.size)
|
||||
.push_next(&mut export_memory_allocate_info);
|
||||
|
||||
unsafe {
|
||||
// Allocate memory for the image.
|
||||
guard.memory = self.device.allocate_memory(&alloc_create_info, None)?;
|
||||
// Finally bind the memory to the image
|
||||
self.device.bind_image_memory(guard.image, guard.memory, 0)?;
|
||||
}
|
||||
|
||||
// Initialization is complete, prevent the scope guard from running it's dropfn.
|
||||
let inner = scopeguard::ScopeGuard::into_inner(guard);
|
||||
|
||||
// Track the image for destruction.
|
||||
self.images.push(inner);
|
||||
|
||||
self.remaining_allocations -= 1;
|
||||
|
||||
Ok(VulkanImage {
|
||||
inner,
|
||||
width,
|
||||
height,
|
||||
format,
|
||||
format_plane_count,
|
||||
khr_external_memory_fd: self.extension_fns.khr_external_memory_fd.clone(),
|
||||
dropped_sender: self.dropped_sender.clone(),
|
||||
device: Arc::downgrade(&self.device),
|
||||
#[cfg(feature = "backend_drm")]
|
||||
node: self.node,
|
||||
})
|
||||
}
|
||||
|
||||
fn cleanup(&mut self) {
|
||||
let dropped = self.dropped_recv.try_iter().collect::<Vec<_>>();
|
||||
|
||||
self.images.retain(|image| {
|
||||
// Only drop if the
|
||||
let drop = dropped.contains(image);
|
||||
|
||||
if drop {
|
||||
// Destroy the underlying image resource
|
||||
unsafe {
|
||||
self.device.destroy_image(image.image, None);
|
||||
self.device.free_memory(image.memory, None);
|
||||
}
|
||||
|
||||
self.remaining_allocations = self
|
||||
.remaining_allocations
|
||||
.checked_add(1)
|
||||
.expect("Remaining allocations overflowed");
|
||||
debug_assert!(
|
||||
self.phd.limits().max_memory_allocation_count >= self.remaining_allocations,
|
||||
"Too many allocations released",
|
||||
);
|
||||
}
|
||||
|
||||
// If the image was dropped, return false
|
||||
!drop
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
use crate::{
|
||||
backend::renderer::{
|
||||
Color32F, Frame, Renderer,
|
||||
element::{Element, Id, RenderElement},
|
||||
utils::{CommitCounter, DamageSet, OpaqueRegions},
|
||||
},
|
||||
render_elements,
|
||||
utils::{Buffer, Physical, Rectangle, Scale, Transform, user_data::UserDataMap},
|
||||
};
|
||||
|
||||
render_elements! {
|
||||
pub DrmRenderElements<'a, R, E>;
|
||||
Holepunch=HolepunchRenderElement,
|
||||
Overlay=OverlayPlaneElement,
|
||||
Other=&'a E,
|
||||
}
|
||||
|
||||
pub struct HolepunchRenderElement {
|
||||
id: Id,
|
||||
geometry: Rectangle<i32, Physical>,
|
||||
}
|
||||
|
||||
impl HolepunchRenderElement {
|
||||
pub fn from_render_element<R, E>(id: Id, element: &E, scale: impl Into<Scale<f64>>) -> Self
|
||||
where
|
||||
R: Renderer,
|
||||
E: RenderElement<R>,
|
||||
{
|
||||
HolepunchRenderElement {
|
||||
id,
|
||||
geometry: element.geometry(scale.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> RenderElement<R> for HolepunchRenderElement
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
fn draw(
|
||||
&self,
|
||||
frame: &mut R::Frame<'_, '_>,
|
||||
_src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
damage: &[Rectangle<i32, Physical>],
|
||||
_opaque_regions: &[Rectangle<i32, Physical>],
|
||||
_cache: Option<&UserDataMap>,
|
||||
) -> Result<(), R::Error> {
|
||||
frame.clear(
|
||||
Color32F::TRANSPARENT,
|
||||
&damage
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|mut d| {
|
||||
d.loc += dst.loc;
|
||||
d
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for HolepunchRenderElement {
|
||||
fn id(&self) -> &Id {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn current_commit(&self) -> CommitCounter {
|
||||
CommitCounter::default()
|
||||
}
|
||||
|
||||
fn src(&self) -> Rectangle<f64, Buffer> {
|
||||
Rectangle::default()
|
||||
}
|
||||
|
||||
fn geometry(&self, _scale: Scale<f64>) -> Rectangle<i32, Physical> {
|
||||
self.geometry
|
||||
}
|
||||
|
||||
fn transform(&self) -> Transform {
|
||||
Transform::Normal
|
||||
}
|
||||
|
||||
fn opaque_regions(&self, _scale: Scale<f64>) -> OpaqueRegions<i32, Physical> {
|
||||
OpaqueRegions::from_slice(&[Rectangle::from_size(self.geometry.size)])
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OverlayPlaneElement {
|
||||
id: Id,
|
||||
geometry: Rectangle<i32, Physical>,
|
||||
opaque_regions: OpaqueRegions<i32, Physical>,
|
||||
}
|
||||
|
||||
impl OverlayPlaneElement {
|
||||
pub fn from_render_element<R, E>(id: Id, element: &E, scale: impl Into<Scale<f64>>) -> Option<Self>
|
||||
where
|
||||
R: Renderer,
|
||||
E: RenderElement<R>,
|
||||
{
|
||||
let scale = scale.into();
|
||||
let opaque_regions = element.opaque_regions(scale);
|
||||
let geometry = element.geometry(scale);
|
||||
|
||||
// We use the opaque regions to create a bounding box around them to
|
||||
// create a fake geometry that only includes the opaque regions.
|
||||
// If the element is not fully opaque this can save the renderer
|
||||
// from some damage when the element is moved.
|
||||
let mut opaque_geometry = opaque_regions
|
||||
.iter()
|
||||
.fold(Rectangle::default(), |acc, item| acc.merge(*item));
|
||||
|
||||
if opaque_geometry.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Because we will move the geometry by the top-left corner of
|
||||
// the opaque geometry we calculated we have to subtract
|
||||
// that from the element local opaque regions.
|
||||
let opaque_regions = opaque_regions
|
||||
.into_iter()
|
||||
.map(|mut region| {
|
||||
region.loc -= opaque_geometry.loc;
|
||||
region
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Move the opaque geometry relative to the original
|
||||
// element location
|
||||
opaque_geometry.loc += geometry.loc;
|
||||
|
||||
Some(OverlayPlaneElement {
|
||||
id,
|
||||
geometry: opaque_geometry,
|
||||
opaque_regions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Element for OverlayPlaneElement {
|
||||
fn id(&self) -> &Id {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn current_commit(&self) -> CommitCounter {
|
||||
CommitCounter::default()
|
||||
}
|
||||
|
||||
fn src(&self) -> Rectangle<f64, Buffer> {
|
||||
Rectangle::default()
|
||||
}
|
||||
|
||||
fn geometry(&self, _scale: Scale<f64>) -> Rectangle<i32, Physical> {
|
||||
self.geometry
|
||||
}
|
||||
|
||||
fn transform(&self) -> Transform {
|
||||
Transform::Normal
|
||||
}
|
||||
|
||||
fn damage_since(&self, _scale: Scale<f64>, _commit: Option<CommitCounter>) -> DamageSet<i32, Physical> {
|
||||
DamageSet::default()
|
||||
}
|
||||
|
||||
fn opaque_regions(&self, _scale: Scale<f64>) -> OpaqueRegions<i32, Physical> {
|
||||
OpaqueRegions::from_slice(&self.opaque_regions)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> RenderElement<R> for OverlayPlaneElement
|
||||
where
|
||||
R: Renderer,
|
||||
{
|
||||
fn draw(
|
||||
&self,
|
||||
_frame: &mut R::Frame<'_, '_>,
|
||||
_src: Rectangle<f64, Buffer>,
|
||||
_dst: Rectangle<i32, Physical>,
|
||||
_damage: &[Rectangle<i32, Physical>],
|
||||
_opaque_regions: &[Rectangle<i32, Physical>],
|
||||
_cache: Option<&UserDataMap>,
|
||||
) -> Result<(), R::Error> {
|
||||
// We do not actually draw anything here
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::{
|
||||
backend::{
|
||||
allocator::{
|
||||
Buffer, Slot,
|
||||
dmabuf::{AsDmabuf, Dmabuf},
|
||||
},
|
||||
drm::Framebuffer,
|
||||
renderer::{
|
||||
Bind, Blit, Color32F, Frame, Renderer,
|
||||
damage::OutputDamageTracker,
|
||||
element::{Element, Id, RenderElement, RenderElementStates},
|
||||
sync::SyncPoint,
|
||||
utils::{CommitCounter, DamageSet, DamageSnapshot, OpaqueRegions},
|
||||
},
|
||||
},
|
||||
output::OutputNoMode,
|
||||
utils::{Buffer as BufferCoords, Physical, Point, Rectangle, Scale, Size, Transform},
|
||||
};
|
||||
|
||||
use super::{DrmScanoutBuffer, ScanoutBuffer};
|
||||
|
||||
/// Result for [`DrmCompositor::render_frame`][super::DrmCompositor::render_frame]
|
||||
///
|
||||
/// **Note**: This struct may contain a reference to the composited buffer
|
||||
/// of the primary display plane. Dropping it will remove said reference and
|
||||
/// allows the buffer to be reused.
|
||||
///
|
||||
/// Keeping the buffer longer may cause the following issues:
|
||||
/// - **Too much damage** - until the buffer is marked free it is not considered
|
||||
/// submitted by the swapchain, causing the age value of newly queried buffers
|
||||
/// to be lower than necessary, potentially resulting in more rendering than necessary.
|
||||
/// To avoid this make sure the buffer is dropped before starting the next render.
|
||||
/// - **Exhaustion of swapchain images** - Continuing rendering while holding on
|
||||
/// to too many buffers may cause the swapchain to run out of images, returning errors
|
||||
/// on rendering until buffers are freed again. The exact amount of images in a
|
||||
/// swapchain is an implementation detail, but should generally be expect to be
|
||||
/// large enough to hold onto at least one `RenderFrameResult`.
|
||||
pub struct RenderFrameResult<'a, B: Buffer, F: Framebuffer, E> {
|
||||
/// If this frame contains any changes and should be submitted
|
||||
pub is_empty: bool,
|
||||
/// The render element states of this frame
|
||||
pub states: RenderElementStates,
|
||||
/// Element for the primary plane
|
||||
pub primary_element: PrimaryPlaneElement<'a, B, F, E>,
|
||||
/// Overlay elements in front to back order
|
||||
pub overlay_elements: Vec<&'a E>,
|
||||
/// Optional cursor plane element
|
||||
///
|
||||
/// If set always above all other elements
|
||||
pub cursor_element: Option<&'a E>,
|
||||
|
||||
pub(super) primary_plane_element_id: Id,
|
||||
pub(super) supports_fencing: bool,
|
||||
}
|
||||
|
||||
impl<B: Buffer, F: Framebuffer, E> RenderFrameResult<'_, B, F, E> {
|
||||
/// Returns if synchronization with kms submission can't be guaranteed through the available apis.
|
||||
pub fn needs_sync(&self) -> bool {
|
||||
if let PrimaryPlaneElement::Swapchain(ref element) = self.primary_element {
|
||||
!self.supports_fencing || !element.sync.is_exportable()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SwapchainElement<'a, 'b, B: Buffer> {
|
||||
id: Id,
|
||||
slot: &'a Slot<B>,
|
||||
transform: Transform,
|
||||
damage: &'b DamageSnapshot<i32, BufferCoords>,
|
||||
}
|
||||
|
||||
impl<B: Buffer> Element for SwapchainElement<'_, '_, B> {
|
||||
fn id(&self) -> &Id {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn current_commit(&self) -> CommitCounter {
|
||||
self.damage.current_commit()
|
||||
}
|
||||
|
||||
fn src(&self) -> Rectangle<f64, BufferCoords> {
|
||||
Rectangle::from_size(self.slot.size()).to_f64()
|
||||
}
|
||||
|
||||
fn geometry(&self, _scale: Scale<f64>) -> Rectangle<i32, Physical> {
|
||||
Rectangle::from_size(self.slot.size().to_logical(1, self.transform).to_physical(1))
|
||||
}
|
||||
|
||||
fn transform(&self) -> Transform {
|
||||
self.transform
|
||||
}
|
||||
|
||||
fn damage_since(&self, scale: Scale<f64>, commit: Option<CommitCounter>) -> DamageSet<i32, Physical> {
|
||||
self.damage
|
||||
.damage_since(commit)
|
||||
.map(|d| {
|
||||
d.into_iter()
|
||||
.map(|d| d.to_logical(1, self.transform, &self.slot.size()).to_physical(1))
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(|| DamageSet::from_slice(&[self.geometry(scale)]))
|
||||
}
|
||||
|
||||
fn opaque_regions(&self, scale: Scale<f64>) -> OpaqueRegions<i32, Physical> {
|
||||
OpaqueRegions::from_slice(&[self.geometry(scale)])
|
||||
}
|
||||
}
|
||||
|
||||
enum FrameResultDamageElement<'a, 'b, E, B: Buffer> {
|
||||
Element(&'a E),
|
||||
Swapchain(SwapchainElement<'a, 'b, B>),
|
||||
}
|
||||
|
||||
impl<E, B> Element for FrameResultDamageElement<'_, '_, E, B>
|
||||
where
|
||||
E: Element,
|
||||
B: Buffer,
|
||||
{
|
||||
fn id(&self) -> &Id {
|
||||
match self {
|
||||
FrameResultDamageElement::Element(e) => e.id(),
|
||||
FrameResultDamageElement::Swapchain(e) => e.id(),
|
||||
}
|
||||
}
|
||||
|
||||
fn current_commit(&self) -> CommitCounter {
|
||||
match self {
|
||||
FrameResultDamageElement::Element(e) => e.current_commit(),
|
||||
FrameResultDamageElement::Swapchain(e) => e.current_commit(),
|
||||
}
|
||||
}
|
||||
|
||||
fn src(&self) -> Rectangle<f64, BufferCoords> {
|
||||
match self {
|
||||
FrameResultDamageElement::Element(e) => e.src(),
|
||||
FrameResultDamageElement::Swapchain(e) => e.src(),
|
||||
}
|
||||
}
|
||||
|
||||
fn geometry(&self, scale: Scale<f64>) -> Rectangle<i32, Physical> {
|
||||
match self {
|
||||
FrameResultDamageElement::Element(e) => e.geometry(scale),
|
||||
FrameResultDamageElement::Swapchain(e) => e.geometry(scale),
|
||||
}
|
||||
}
|
||||
|
||||
fn location(&self, scale: Scale<f64>) -> Point<i32, Physical> {
|
||||
match self {
|
||||
FrameResultDamageElement::Element(e) => e.location(scale),
|
||||
FrameResultDamageElement::Swapchain(e) => e.location(scale),
|
||||
}
|
||||
}
|
||||
|
||||
fn transform(&self) -> Transform {
|
||||
match self {
|
||||
FrameResultDamageElement::Element(e) => e.transform(),
|
||||
FrameResultDamageElement::Swapchain(e) => e.transform(),
|
||||
}
|
||||
}
|
||||
|
||||
fn damage_since(&self, scale: Scale<f64>, commit: Option<CommitCounter>) -> DamageSet<i32, Physical> {
|
||||
match self {
|
||||
FrameResultDamageElement::Element(e) => e.damage_since(scale, commit),
|
||||
FrameResultDamageElement::Swapchain(e) => e.damage_since(scale, commit),
|
||||
}
|
||||
}
|
||||
|
||||
fn opaque_regions(&self, scale: Scale<f64>) -> OpaqueRegions<i32, Physical> {
|
||||
match self {
|
||||
FrameResultDamageElement::Element(e) => e.opaque_regions(scale),
|
||||
FrameResultDamageElement::Swapchain(e) => e.opaque_regions(scale),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Defines the element for the primary plane
|
||||
pub enum PrimaryPlaneElement<'a, B: Buffer, F: Framebuffer, E> {
|
||||
/// A slot from the swapchain was used for rendering
|
||||
/// the primary plane
|
||||
Swapchain(PrimarySwapchainElement<B, F>),
|
||||
/// An element has been assigned for direct scan-out
|
||||
Element(&'a E),
|
||||
}
|
||||
|
||||
/// Error for [`RenderFrameResult::blit_frame_result`]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum BlitFrameResultError<R: std::error::Error, E: std::error::Error> {
|
||||
/// A render error occurred
|
||||
#[error(transparent)]
|
||||
Rendering(R),
|
||||
/// A error occurred during exporting the buffer
|
||||
#[error(transparent)]
|
||||
Export(E),
|
||||
}
|
||||
|
||||
impl<B, F, E> RenderFrameResult<'_, B, F, E>
|
||||
where
|
||||
B: Buffer,
|
||||
F: Framebuffer,
|
||||
{
|
||||
/// Get the damage of this frame for the specified dtr and age
|
||||
pub fn damage_from_age<'d>(
|
||||
&self,
|
||||
damage_tracker: &'d mut OutputDamageTracker,
|
||||
age: usize,
|
||||
filter: impl IntoIterator<Item = Id>,
|
||||
) -> Result<(Option<&'d Vec<Rectangle<i32, Physical>>>, RenderElementStates), OutputNoMode>
|
||||
where
|
||||
E: Element,
|
||||
{
|
||||
#[allow(clippy::mutable_key_type)]
|
||||
let filter_ids: HashSet<Id> = filter.into_iter().collect();
|
||||
|
||||
let mut elements: Vec<FrameResultDamageElement<'_, '_, E, B>> =
|
||||
Vec::with_capacity(usize::from(self.cursor_element.is_some()) + self.overlay_elements.len() + 1);
|
||||
if let Some(cursor) = self.cursor_element {
|
||||
if !filter_ids.contains(cursor.id()) {
|
||||
elements.push(FrameResultDamageElement::Element(cursor));
|
||||
}
|
||||
}
|
||||
|
||||
elements.extend(
|
||||
self.overlay_elements
|
||||
.iter()
|
||||
.filter(|e| !filter_ids.contains(e.id()))
|
||||
.map(|e| FrameResultDamageElement::Element(*e)),
|
||||
);
|
||||
|
||||
let primary_render_element = match &self.primary_element {
|
||||
PrimaryPlaneElement::Swapchain(PrimarySwapchainElement {
|
||||
slot,
|
||||
transform,
|
||||
damage,
|
||||
..
|
||||
}) => FrameResultDamageElement::Swapchain(SwapchainElement {
|
||||
id: self.primary_plane_element_id.clone(),
|
||||
transform: *transform,
|
||||
slot: match &slot.buffer {
|
||||
ScanoutBuffer::Swapchain(slot) => slot,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
damage,
|
||||
}),
|
||||
PrimaryPlaneElement::Element(e) => FrameResultDamageElement::Element(*e),
|
||||
};
|
||||
|
||||
elements.push(primary_render_element);
|
||||
|
||||
damage_tracker.damage_output(age, &elements)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, B, F, E> RenderFrameResult<'a, B, F, E>
|
||||
where
|
||||
B: Buffer + AsDmabuf,
|
||||
<B as AsDmabuf>::Error: std::fmt::Debug,
|
||||
F: Framebuffer,
|
||||
{
|
||||
/// Blit the frame result
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn blit_frame_result<R>(
|
||||
&self,
|
||||
size: impl Into<Size<i32, Physical>>,
|
||||
transform: Transform,
|
||||
scale: impl Into<Scale<f64>>,
|
||||
renderer: &mut R,
|
||||
framebuffer: &mut R::Framebuffer<'_>,
|
||||
damage: impl IntoIterator<Item = Rectangle<i32, Physical>>,
|
||||
filter: impl IntoIterator<Item = Id>,
|
||||
) -> Result<SyncPoint, BlitFrameResultError<R::Error, <B as AsDmabuf>::Error>>
|
||||
where
|
||||
R: Renderer + Bind<Dmabuf> + Blit,
|
||||
R::TextureId: 'static,
|
||||
E: Element + RenderElement<R>,
|
||||
{
|
||||
let size = size.into();
|
||||
let scale = scale.into();
|
||||
#[allow(clippy::mutable_key_type)]
|
||||
let filter_ids: HashSet<Id> = filter.into_iter().collect();
|
||||
let damage = damage.into_iter().collect::<Vec<_>>();
|
||||
|
||||
// If we have no damage we can exit early
|
||||
if damage.is_empty() {
|
||||
return Ok(SyncPoint::signaled());
|
||||
}
|
||||
|
||||
let mut opaque_regions: Vec<Rectangle<i32, Physical>> = Vec::new();
|
||||
|
||||
let mut elements_to_render: Vec<&'a E> =
|
||||
Vec::with_capacity(usize::from(self.cursor_element.is_some()) + self.overlay_elements.len() + 1);
|
||||
|
||||
if let Some(cursor_element) = self.cursor_element.as_ref() {
|
||||
if !filter_ids.contains(cursor_element.id()) {
|
||||
elements_to_render.push(*cursor_element);
|
||||
opaque_regions.extend(cursor_element.opaque_regions(scale));
|
||||
}
|
||||
}
|
||||
|
||||
for element in self
|
||||
.overlay_elements
|
||||
.iter()
|
||||
.filter(|e| !filter_ids.contains(e.id()))
|
||||
{
|
||||
elements_to_render.push(element);
|
||||
opaque_regions.extend(element.opaque_regions(scale));
|
||||
}
|
||||
|
||||
let primary_dmabuf = match &self.primary_element {
|
||||
PrimaryPlaneElement::Swapchain(PrimarySwapchainElement { slot, sync, .. }) => {
|
||||
let dmabuf = match &slot.buffer {
|
||||
ScanoutBuffer::Swapchain(slot) => slot.export().map_err(BlitFrameResultError::Export)?,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let size = dmabuf.size();
|
||||
let geometry = Rectangle::from_size(size.to_logical(1, Transform::Normal).to_physical(1));
|
||||
opaque_regions.push(geometry);
|
||||
Some((sync.clone(), dmabuf, geometry))
|
||||
}
|
||||
PrimaryPlaneElement::Element(e) => {
|
||||
elements_to_render.push(*e);
|
||||
opaque_regions.extend(e.opaque_regions(scale));
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
let clear_damage =
|
||||
Rectangle::subtract_rects_many_in_place(damage.clone(), opaque_regions.iter().copied());
|
||||
|
||||
let mut sync: Option<SyncPoint> = None;
|
||||
if !clear_damage.is_empty() {
|
||||
tracing::trace!("clearing frame damage {:#?}", clear_damage);
|
||||
|
||||
let mut frame = renderer
|
||||
.render(framebuffer, size, transform)
|
||||
.map_err(BlitFrameResultError::Rendering)?;
|
||||
|
||||
frame
|
||||
.clear(Color32F::BLACK, &clear_damage)
|
||||
.map_err(BlitFrameResultError::Rendering)?;
|
||||
|
||||
sync = Some(frame.finish().map_err(BlitFrameResultError::Rendering)?);
|
||||
}
|
||||
|
||||
// first do the potential blit
|
||||
if let Some((primary_dmabuf_sync, mut dmabuf, geometry)) = primary_dmabuf {
|
||||
let blit_damage = damage
|
||||
.iter()
|
||||
.filter_map(|d| d.intersection(geometry))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
tracing::trace!("blitting frame with damage: {:#?}", blit_damage);
|
||||
|
||||
renderer
|
||||
.wait(&primary_dmabuf_sync)
|
||||
.map_err(BlitFrameResultError::Rendering)?;
|
||||
let fb = renderer
|
||||
.bind(&mut dmabuf)
|
||||
.map_err(BlitFrameResultError::Rendering)?;
|
||||
for rect in blit_damage {
|
||||
// TODO: On Vulkan, may need to combine sync points instead of just using latest?
|
||||
sync = Some(
|
||||
renderer
|
||||
.blit(
|
||||
&fb,
|
||||
framebuffer,
|
||||
rect,
|
||||
rect,
|
||||
crate::backend::renderer::TextureFilter::Linear,
|
||||
)
|
||||
.map_err(BlitFrameResultError::Rendering)?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// then render the remaining elements if any
|
||||
if !elements_to_render.is_empty() {
|
||||
tracing::trace!("drawing {} frame element(s)", elements_to_render.len());
|
||||
|
||||
let mut frame = renderer
|
||||
.render(framebuffer, size, transform)
|
||||
.map_err(BlitFrameResultError::Rendering)?;
|
||||
|
||||
for element in elements_to_render.iter().rev() {
|
||||
let src = element.src();
|
||||
let dst = element.geometry(scale);
|
||||
let element_damage = damage
|
||||
.iter()
|
||||
.filter_map(|d| {
|
||||
d.intersection(dst).map(|mut d| {
|
||||
d.loc -= dst.loc;
|
||||
d
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// no need to render without damage
|
||||
if element_damage.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
tracing::trace!("drawing frame element with damage: {:#?}", element_damage);
|
||||
|
||||
element
|
||||
.draw(&mut frame, src, dst, &element_damage, &[], None)
|
||||
.map_err(BlitFrameResultError::Rendering)?;
|
||||
}
|
||||
|
||||
Ok(frame.finish().map_err(BlitFrameResultError::Rendering)?)
|
||||
} else {
|
||||
Ok(sync.unwrap_or_default())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Buffer + std::fmt::Debug, F: Framebuffer + std::fmt::Debug, E: std::fmt::Debug> std::fmt::Debug
|
||||
for RenderFrameResult<'_, B, F, E>
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("RenderFrameResult")
|
||||
.field("is_empty", &self.is_empty)
|
||||
.field("states", &self.states)
|
||||
.field("primary_element", &self.primary_element)
|
||||
.field("overlay_elements", &self.overlay_elements)
|
||||
.field("cursor_element", &self.cursor_element)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Defines the element for the primary plane in cases where a composited buffer was used.
|
||||
pub struct PrimarySwapchainElement<B: Buffer, F: Framebuffer> {
|
||||
/// The slot from the swapchain
|
||||
pub(super) slot: DrmScanoutBuffer<B, F>,
|
||||
/// Sync point
|
||||
pub sync: SyncPoint,
|
||||
/// The transform applied during rendering
|
||||
pub transform: Transform,
|
||||
/// The damage on the primary plane
|
||||
pub damage: DamageSnapshot<i32, BufferCoords>,
|
||||
}
|
||||
|
||||
impl<B: Buffer, F: Framebuffer> PrimarySwapchainElement<B, F> {
|
||||
/// Access the underlying swapchain buffer
|
||||
#[inline]
|
||||
pub fn buffer(&self) -> &B {
|
||||
match &self.slot.buffer {
|
||||
ScanoutBuffer::Swapchain(slot) => slot,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
+4445
File diff suppressed because it is too large.
Load diff
+346
@@ -0,0 +1,346 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{
|
||||
Arc, RwLock,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
use drm::control::atomic::AtomicModeReq;
|
||||
use drm::control::{
|
||||
AtomicCommitFlags, Device as ControlDevice, PropertyValueSet, ResourceHandle, connector, crtc,
|
||||
framebuffer, plane, property,
|
||||
};
|
||||
|
||||
use super::DrmDeviceFd;
|
||||
use crate::backend::drm::error::AccessError;
|
||||
use crate::{backend::drm::error::Error, utils::DevPath};
|
||||
|
||||
use tracing::{debug, error, info_span, trace};
|
||||
|
||||
type OldState = (
|
||||
Vec<(connector::Handle, PropertyValueSet)>,
|
||||
Vec<(crtc::Handle, PropertyValueSet)>,
|
||||
Vec<(framebuffer::Handle, PropertyValueSet)>,
|
||||
Vec<(plane::Handle, PropertyValueSet)>,
|
||||
);
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct PropMapping {
|
||||
pub connectors: HashMap<connector::Handle, HashMap<String, property::Handle>>,
|
||||
pub crtcs: HashMap<crtc::Handle, HashMap<String, property::Handle>>,
|
||||
pub planes: HashMap<plane::Handle, HashMap<String, property::Handle>>,
|
||||
}
|
||||
|
||||
impl PropMapping {
|
||||
pub(crate) fn conn_prop_handle(
|
||||
&self,
|
||||
handle: connector::Handle,
|
||||
name: &'static str,
|
||||
) -> Result<property::Handle, Error> {
|
||||
self.connectors
|
||||
.get(&handle)
|
||||
.ok_or(Error::UnknownConnector(handle))?
|
||||
.get(name)
|
||||
.ok_or_else(|| Error::UnknownProperty {
|
||||
handle: handle.into(),
|
||||
name,
|
||||
})
|
||||
.copied()
|
||||
}
|
||||
|
||||
pub(crate) fn crtc_prop_handle(
|
||||
&self,
|
||||
handle: crtc::Handle,
|
||||
name: &'static str,
|
||||
) -> Result<property::Handle, Error> {
|
||||
self.crtcs
|
||||
.get(&handle)
|
||||
.ok_or(Error::UnknownCrtc(handle))?
|
||||
.get(name)
|
||||
.ok_or_else(|| Error::UnknownProperty {
|
||||
handle: handle.into(),
|
||||
name,
|
||||
})
|
||||
.copied()
|
||||
}
|
||||
|
||||
pub(crate) fn plane_prop_handle(
|
||||
&self,
|
||||
handle: plane::Handle,
|
||||
name: &'static str,
|
||||
) -> Result<property::Handle, Error> {
|
||||
self.planes
|
||||
.get(&handle)
|
||||
.ok_or(Error::UnknownPlane(handle))?
|
||||
.get(name)
|
||||
.ok_or_else(|| Error::UnknownProperty {
|
||||
handle: handle.into(),
|
||||
name,
|
||||
})
|
||||
.copied()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AtomicDrmDevice {
|
||||
pub(crate) fd: DrmDeviceFd,
|
||||
pub(crate) active: Arc<AtomicBool>,
|
||||
old_state: OldState,
|
||||
pub(crate) prop_mapping: Arc<RwLock<PropMapping>>,
|
||||
pub(super) span: tracing::Span,
|
||||
}
|
||||
|
||||
impl AtomicDrmDevice {
|
||||
pub fn new(fd: DrmDeviceFd, active: Arc<AtomicBool>, disable_connectors: bool) -> Result<Self, Error> {
|
||||
let span = info_span!("drm_atomic");
|
||||
let mut dev = AtomicDrmDevice {
|
||||
fd,
|
||||
active,
|
||||
old_state: (Vec::new(), Vec::new(), Vec::new(), Vec::new()),
|
||||
prop_mapping: Default::default(),
|
||||
span,
|
||||
};
|
||||
let _guard = dev.span.enter();
|
||||
|
||||
// Enumerate (and save) the current device state.
|
||||
let res_handles = dev.fd.resource_handles().map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error loading drm resources",
|
||||
dev: dev.fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
|
||||
let planes = dev.fd.plane_handles().map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error loading planes",
|
||||
dev: dev.fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
|
||||
let mut old_state = dev.old_state.clone();
|
||||
let mut mapping = dev.prop_mapping.write().unwrap();
|
||||
|
||||
// This helper function takes a snapshot of the current device properties.
|
||||
// (everything in the atomic api is set via properties.)
|
||||
add_props(&dev.fd, res_handles.connectors(), &mut old_state.0)?;
|
||||
add_props(&dev.fd, res_handles.crtcs(), &mut old_state.1)?;
|
||||
add_props(&dev.fd, res_handles.framebuffers(), &mut old_state.2)?;
|
||||
add_props(&dev.fd, &planes, &mut old_state.3)?;
|
||||
|
||||
// And because the mapping is not consistent across devices,
|
||||
// we also need to lookup the handle for a property name.
|
||||
// And we do this a fair bit, so lets cache that mapping.
|
||||
map_props(&dev.fd, res_handles.connectors(), &mut mapping.connectors)?;
|
||||
map_props(&dev.fd, res_handles.crtcs(), &mut mapping.crtcs)?;
|
||||
map_props(&dev.fd, &planes, &mut mapping.planes)?;
|
||||
|
||||
dev.old_state = old_state;
|
||||
trace!("Mapping: {:#?}", mapping);
|
||||
|
||||
drop(mapping);
|
||||
|
||||
// If the user does not explicitly requests us to skip this,
|
||||
// we clear out the complete connector<->crtc mapping on device creation.
|
||||
//
|
||||
// The reason is, that certain operations may be racy otherwise. Surfaces can
|
||||
// exist on different threads: as a result, we cannot really enumerate the current state
|
||||
// (it might be changed on another thread during the enumeration). And commits can fail,
|
||||
// if e.g. a connector is already bound to another surface, which is difficult to analyse at runtime.
|
||||
//
|
||||
// An easy workaround is to set a known state on device creation, so we can only
|
||||
// run into these errors on our own and not because previous compositors left the device
|
||||
// in a funny state.
|
||||
if disable_connectors {
|
||||
debug!("Resetting drm device to known state");
|
||||
dev.reset_state()?;
|
||||
}
|
||||
|
||||
drop(_guard);
|
||||
Ok(dev)
|
||||
}
|
||||
|
||||
pub(super) fn reset_state(&self) -> Result<(), Error> {
|
||||
// reset state sets the connectors into a known state (all disabled),
|
||||
// for the same reasons we do this on device creation.
|
||||
//
|
||||
// We might end up with conflicting commit requirements, if we want to restore our state,
|
||||
// on top of the state the previous compositor left the device in.
|
||||
// This is because we do commits per surface and not per device, so we do a global
|
||||
// commit here, to fix any conflicts.
|
||||
let res_handles = self.fd.resource_handles().map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error loading drm resources",
|
||||
dev: self.fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
let plane_handles = self.fd.plane_handles().map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error loading drm plane resources",
|
||||
dev: self.fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
|
||||
let mut prop_mapping = self.prop_mapping.write().unwrap();
|
||||
|
||||
// Make sure the mapping is up to date
|
||||
prop_mapping.connectors.clear();
|
||||
map_props(&self.fd, res_handles.connectors(), &mut prop_mapping.connectors)?;
|
||||
|
||||
// Disable all connectors (otherwise we might run into conflicting commits when restarting the rendering loop)
|
||||
let mut req = AtomicModeReq::new();
|
||||
for conn in res_handles.connectors() {
|
||||
let prop = prop_mapping
|
||||
.conn_prop_handle(*conn, "CRTC_ID")
|
||||
.expect("Unknown property CRTC_ID");
|
||||
req.add_property(*conn, prop, property::Value::CRTC(None));
|
||||
}
|
||||
// Disable all planes
|
||||
for plane in plane_handles {
|
||||
let prop = prop_mapping
|
||||
.plane_prop_handle(plane, "CRTC_ID")
|
||||
.expect("Unknown property CRTC_ID");
|
||||
req.add_property(plane, prop, property::Value::CRTC(None));
|
||||
|
||||
let prop = prop_mapping
|
||||
.plane_prop_handle(plane, "FB_ID")
|
||||
.expect("Unknown property FB_ID");
|
||||
req.add_property(plane, prop, property::Value::Framebuffer(None));
|
||||
}
|
||||
// A crtc without a connector has no mode, we also need to reset that.
|
||||
// Otherwise the commit will not be accepted.
|
||||
for crtc in res_handles.crtcs() {
|
||||
let mode_prop = prop_mapping
|
||||
.crtc_prop_handle(*crtc, "MODE_ID")
|
||||
.expect("Unknown property MODE_ID");
|
||||
let active_prop = prop_mapping
|
||||
.crtc_prop_handle(*crtc, "ACTIVE")
|
||||
.expect("Unknown property ACTIVE");
|
||||
req.add_property(*crtc, active_prop, property::Value::Boolean(false));
|
||||
req.add_property(*crtc, mode_prop, property::Value::Unknown(0));
|
||||
}
|
||||
self.fd
|
||||
.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req)
|
||||
.map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Failed to disable connectors",
|
||||
dev: self.fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AtomicDrmDevice {
|
||||
fn drop(&mut self) {
|
||||
if self.active.load(Ordering::SeqCst) {
|
||||
let _guard = self.span.enter();
|
||||
|
||||
// Here we restore the card/tty's to it's previous state.
|
||||
// In case e.g. getty was running on the tty sets the correct framebuffer again,
|
||||
// so that getty will be visible.
|
||||
// We do exit correctly if this fails, but the user will be presented with
|
||||
// a black screen if no display handler takes control again.
|
||||
|
||||
// create an atomic mode request consisting of all properties we captured on creation.
|
||||
// TODO, check current connector status and remove deactivated connectors from this req.
|
||||
|
||||
debug!("Device still active, trying to restore previous state");
|
||||
let mut req = AtomicModeReq::new();
|
||||
fn add_multiple_props<T: ResourceHandle>(
|
||||
req: &mut AtomicModeReq,
|
||||
old_state: &[(T, PropertyValueSet)],
|
||||
) {
|
||||
for (handle, set) in old_state {
|
||||
let (prop_handles, values) = set.as_props_and_values();
|
||||
for (&prop_handle, &val) in prop_handles.iter().zip(values.iter()) {
|
||||
req.add_raw_property((*handle).into(), prop_handle, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add_multiple_props(&mut req, &self.old_state.0);
|
||||
add_multiple_props(&mut req, &self.old_state.1);
|
||||
add_multiple_props(&mut req, &self.old_state.2);
|
||||
add_multiple_props(&mut req, &self.old_state.3);
|
||||
|
||||
trace!("Previous state: {:?}", req);
|
||||
if let Err(err) = self.fd.atomic_commit(AtomicCommitFlags::ALLOW_MODESET, req) {
|
||||
error!("Failed to restore previous state. Error: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add all properties of given handles to a given drm resource type to state.
|
||||
// You may use this to snapshot the current state of the drm device (fully or partially).
|
||||
fn add_props<D, T>(fd: &D, handles: &[T], state: &mut Vec<(T, PropertyValueSet)>) -> Result<(), Error>
|
||||
where
|
||||
D: DevPath + ControlDevice,
|
||||
T: ResourceHandle,
|
||||
{
|
||||
let iter = handles.iter().map(|x| (x, fd.get_properties(*x)));
|
||||
if let Some(len) = iter.size_hint().1 {
|
||||
state.reserve_exact(len)
|
||||
}
|
||||
|
||||
iter.map(|(x, y)| (*x, y))
|
||||
.try_for_each(|(x, y)| match y {
|
||||
Ok(y) => {
|
||||
state.push((x, y));
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
})
|
||||
.map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error reading properties",
|
||||
dev: fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a mapping of property names and handles for given handles of a given drm resource type.
|
||||
/// You may use this to easily lookup properties by name instead of going through this procedure manually.
|
||||
pub(in crate::backend::drm) fn map_props<D, T>(
|
||||
fd: &D,
|
||||
handles: &[T],
|
||||
mapping: &mut HashMap<T, HashMap<String, property::Handle>>,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
D: DevPath + ControlDevice,
|
||||
T: ResourceHandle + Eq + std::hash::Hash,
|
||||
{
|
||||
handles
|
||||
.iter()
|
||||
.map(|x| (x, fd.get_properties(*x)))
|
||||
.try_for_each(|(handle, props)| {
|
||||
let mut map = HashMap::new();
|
||||
match props {
|
||||
Ok(props) => {
|
||||
let (prop_handles, _) = props.as_props_and_values();
|
||||
for prop in prop_handles {
|
||||
if let Ok(info) = fd.get_property(*prop) {
|
||||
let name = info.name().to_string_lossy().into_owned();
|
||||
map.insert(name, *prop);
|
||||
}
|
||||
}
|
||||
mapping.insert(*handle, map);
|
||||
Ok(())
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
})
|
||||
.map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error reading properties",
|
||||
dev: fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
use drm::{Device as BasicDevice, control::Device as ControlDevice};
|
||||
use std::{
|
||||
os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd},
|
||||
sync::{Arc, Weak},
|
||||
};
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
use crate::backend::drm::{DrmNode, NodeType};
|
||||
use crate::utils::{DevPath, DeviceFd};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct InternalDrmDeviceFd {
|
||||
fd: DeviceFd,
|
||||
privileged: bool,
|
||||
}
|
||||
|
||||
impl PartialEq for InternalDrmDeviceFd {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.fd == other.fd
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for InternalDrmDeviceFd {
|
||||
fn drop(&mut self) {
|
||||
info!("Dropping device: {:?}", self.fd.dev_path());
|
||||
if self.privileged {
|
||||
if let Err(err) = self.release_master_lock() {
|
||||
error!("Failed to drop drm master state. Error: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsFd for InternalDrmDeviceFd {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
self.fd.as_fd()
|
||||
}
|
||||
}
|
||||
impl BasicDevice for InternalDrmDeviceFd {}
|
||||
impl ControlDevice for InternalDrmDeviceFd {}
|
||||
|
||||
/// Ref-counted file descriptor of an open drm device
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct DrmDeviceFd(Arc<InternalDrmDeviceFd>);
|
||||
|
||||
impl AsFd for DrmDeviceFd {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
self.0.fd.as_fd()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: drop impl once not needed anymore by smithay or dependencies
|
||||
impl AsRawFd for DrmDeviceFd {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.0.fd.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl DrmDeviceFd {
|
||||
/// Create a new `DrmDeviceFd`.
|
||||
///
|
||||
/// This function will try to acquire the master lock for the underlying drm device
|
||||
/// and release the lock on drop again.
|
||||
/// For that reason you should never create multiple `DrmDeviceFd` out of the same
|
||||
/// `DeviceFd`, but instead clone the `DrmDeviceFd`.
|
||||
///
|
||||
/// Failing to do so might fail to acquire set lock and release it early,
|
||||
/// which can cause some drm ioctls to fail later.
|
||||
pub fn new(fd: DeviceFd) -> DrmDeviceFd {
|
||||
let mut dev = InternalDrmDeviceFd {
|
||||
fd,
|
||||
privileged: false,
|
||||
};
|
||||
|
||||
// Render nodes have no master; the kernel refuses SET_MASTER on them.
|
||||
let is_render_node = DrmNode::from_file(&dev.fd).is_ok_and(|node| node.ty() == NodeType::Render);
|
||||
|
||||
// We want to modeset, so we better be the master, if we run via a tty session.
|
||||
// This is only needed on older kernels. Newer kernels grant this permission,
|
||||
// if no other process is already the *master*. So we skip over this error.
|
||||
if !is_render_node {
|
||||
if dev.acquire_master_lock().is_err() {
|
||||
warn!("Unable to become drm master, assuming unprivileged mode");
|
||||
} else {
|
||||
dev.privileged = true;
|
||||
}
|
||||
}
|
||||
|
||||
DrmDeviceFd(Arc::new(dev))
|
||||
}
|
||||
|
||||
pub(in crate::backend::drm) fn is_privileged(&self) -> bool {
|
||||
self.0.privileged
|
||||
}
|
||||
|
||||
/// Returns the underlying `DeviceFd`
|
||||
pub fn device_fd(&self) -> DeviceFd {
|
||||
self.0.fd.clone()
|
||||
}
|
||||
|
||||
/// Returns the `dev_t` of the underlying device
|
||||
pub fn dev_id(&self) -> rustix::io::Result<libc::dev_t> {
|
||||
Ok(rustix::fs::fstat(&self.0.fd)?.st_rdev)
|
||||
}
|
||||
|
||||
/// Returns a weak reference to the underlying device
|
||||
pub fn downgrade(&self) -> WeakDrmDeviceFd {
|
||||
WeakDrmDeviceFd(Arc::downgrade(&self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl BasicDevice for DrmDeviceFd {}
|
||||
impl ControlDevice for DrmDeviceFd {}
|
||||
|
||||
/// Weak variant of [`DrmDeviceFd`]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct WeakDrmDeviceFd(Weak<InternalDrmDeviceFd>);
|
||||
|
||||
impl WeakDrmDeviceFd {
|
||||
/// Construct an empty Weak reference, that will never upgrade successfully
|
||||
pub fn new() -> Self {
|
||||
WeakDrmDeviceFd(Weak::new())
|
||||
}
|
||||
|
||||
/// Try to upgrade to a strong reference
|
||||
pub fn upgrade(&self) -> Option<DrmDeviceFd> {
|
||||
self.0.upgrade().map(DrmDeviceFd)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for WeakDrmDeviceFd {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Weak::ptr_eq(&self.0, &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<DrmDeviceFd> for WeakDrmDeviceFd {
|
||||
fn eq(&self, other: &DrmDeviceFd) -> bool {
|
||||
Weak::upgrade(&self.0).is_some_and(|arc| Arc::ptr_eq(&arc, &other.0))
|
||||
}
|
||||
}
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
use drm::control::{Device as ControlDevice, connector, crtc};
|
||||
|
||||
use super::DrmDeviceFd;
|
||||
use crate::backend::drm::error::{AccessError, Error};
|
||||
use crate::utils::DevPath;
|
||||
|
||||
use tracing::{debug, error, info_span, trace};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LegacyDrmDevice {
|
||||
pub(crate) fd: DrmDeviceFd,
|
||||
pub(crate) active: Arc<AtomicBool>,
|
||||
old_state: HashMap<crtc::Handle, (crtc::Info, Vec<connector::Handle>)>,
|
||||
pub(super) span: tracing::Span,
|
||||
}
|
||||
|
||||
impl LegacyDrmDevice {
|
||||
pub fn new(fd: DrmDeviceFd, active: Arc<AtomicBool>, disable_connectors: bool) -> Result<Self, Error> {
|
||||
let span = info_span!("drm_legacy");
|
||||
let mut dev = LegacyDrmDevice {
|
||||
fd,
|
||||
active,
|
||||
old_state: HashMap::new(),
|
||||
span,
|
||||
};
|
||||
let _guard = dev.span.enter();
|
||||
|
||||
// Enumerate (and save) the current device state.
|
||||
// We need to keep the previous device configuration to restore the state later,
|
||||
// so we query everything, that we can set.
|
||||
let res_handles = dev.fd.resource_handles().map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error loading drm resources",
|
||||
dev: dev.fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
for &con in res_handles.connectors() {
|
||||
let con_info = dev.fd.get_connector(con, false).map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error loading connector info",
|
||||
dev: dev.fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
if let Some(enc) = con_info.current_encoder() {
|
||||
let enc_info = dev.fd.get_encoder(enc).map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error loading encoder info",
|
||||
dev: dev.fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
if let Some(crtc) = enc_info.crtc() {
|
||||
let info = dev.fd.get_crtc(crtc).map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error loading crtc info",
|
||||
dev: dev.fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
dev.old_state
|
||||
.entry(crtc)
|
||||
.or_insert((info, Vec::new()))
|
||||
.1
|
||||
.push(con);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the user does not explicitly requests us to skip this,
|
||||
// we clear out the complete connector<->crtc mapping on device creation.
|
||||
//
|
||||
// The reason is, that certain operations may be racy otherwise, as surfaces can
|
||||
// exist on different threads. As a result, we cannot enumerate the current state
|
||||
// on surface creation (it might be changed on another thread during the enumeration).
|
||||
// An easy workaround is to set a known state on device creation.
|
||||
if disable_connectors {
|
||||
debug!("Resetting drm device to known state");
|
||||
dev.reset_state()?;
|
||||
}
|
||||
|
||||
drop(_guard);
|
||||
Ok(dev)
|
||||
}
|
||||
|
||||
pub(super) fn reset_state(&self) -> Result<(), Error> {
|
||||
let res_handles = self.fd.resource_handles().map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Failed to query resource handles",
|
||||
dev: self.fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
set_connector_state(&self.fd, res_handles.connectors().iter().copied(), false)?;
|
||||
|
||||
for crtc in res_handles.crtcs() {
|
||||
#[allow(deprecated)]
|
||||
let _ = self
|
||||
.fd
|
||||
.set_cursor(*crtc, Option::<&drm::control::dumbbuffer::DumbBuffer>::None);
|
||||
// null commit (necessary to trigger removal on the kernel side with the legacy api.)
|
||||
self.fd
|
||||
.set_crtc(*crtc, None, (0, 0), &[], None)
|
||||
.map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error setting crtc",
|
||||
dev: self.fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LegacyDrmDevice {
|
||||
fn drop(&mut self) {
|
||||
if self.active.load(Ordering::SeqCst) {
|
||||
let _guard = self.span.enter();
|
||||
|
||||
// Here we restore the tty to it's previous state.
|
||||
// In case e.g. getty was running on the tty sets the correct framebuffer again,
|
||||
// so that getty will be visible.
|
||||
// We do exit correctly, if this fails, but the user will be presented with
|
||||
// a black screen, if no display handler takes control again.
|
||||
|
||||
debug!("Device still active, trying to restore previous state");
|
||||
for (handle, (info, connectors)) in self.old_state.drain() {
|
||||
trace!(
|
||||
framebuffer = ?info.framebuffer(),
|
||||
offset = ?info.position(),
|
||||
?connectors,
|
||||
mode = ?info.mode(),
|
||||
"Resetting crtc {:?}",
|
||||
handle,
|
||||
);
|
||||
if let Err(err) = self.fd.set_crtc(
|
||||
handle,
|
||||
info.framebuffer(),
|
||||
info.position(),
|
||||
&connectors,
|
||||
info.mode(),
|
||||
) {
|
||||
error!("Failed to reset crtc ({:?}). Error: {}", handle, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_connector_state<D>(
|
||||
dev: &D,
|
||||
connectors: impl Iterator<Item = connector::Handle>,
|
||||
enabled: bool,
|
||||
) -> Result<(), Error>
|
||||
where
|
||||
D: DevPath + ControlDevice,
|
||||
{
|
||||
// for every connector...
|
||||
for conn in connectors {
|
||||
let info = dev.get_connector(conn, false).map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Failed to get connector infos",
|
||||
dev: dev.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
// that is currently connected ...
|
||||
if info.state() == connector::State::Connected {
|
||||
// get a list of it's properties.
|
||||
let props = dev.get_properties(conn).map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Failed to get properties for connector",
|
||||
dev: dev.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
let (handles, _) = props.as_props_and_values();
|
||||
// for every handle ...
|
||||
for handle in handles {
|
||||
// get information of that property
|
||||
let info = dev.get_property(*handle).map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Failed to get property of connector",
|
||||
dev: dev.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
// to find out, if we got the handle of the "DPMS" property ...
|
||||
if info.name().to_str().map(|x| x == "DPMS").unwrap_or(false) {
|
||||
// so we can use that to turn on / off the connector
|
||||
trace!(connector = ?conn, "Setting DPMS {}", enabled);
|
||||
dev.set_property(
|
||||
conn,
|
||||
*handle,
|
||||
if enabled {
|
||||
drm_ffi::DRM_MODE_DPMS_ON.into()
|
||||
} else {
|
||||
drm_ffi::DRM_MODE_DPMS_OFF.into()
|
||||
},
|
||||
)
|
||||
.map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Failed to set property of connector",
|
||||
dev: dev.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
+604
@@ -0,0 +1,604 @@
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::os::unix::io::{AsFd, BorrowedFd};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::{Arc, Mutex, Weak, atomic::AtomicBool};
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use calloop::{EventSource, Interest, Poll, PostAction, Readiness, Token, TokenFactory};
|
||||
use drm::control::{Device as ControlDevice, Event, Mode, ResourceHandles, connector, crtc, plane};
|
||||
use drm::{ClientCapability, Device as BasicDevice, DriverCapability};
|
||||
use libc::dev_t;
|
||||
|
||||
pub(super) mod atomic;
|
||||
mod fd;
|
||||
pub use self::fd::{DrmDeviceFd, WeakDrmDeviceFd};
|
||||
pub(super) mod legacy;
|
||||
use crate::utils::{Buffer, DevPath, Size};
|
||||
|
||||
use super::error::AccessError;
|
||||
use super::surface::{DrmSurface, DrmSurfaceInternal, atomic::AtomicDrmSurface, legacy::LegacyDrmSurface};
|
||||
use super::{Planes, error::Error, planes};
|
||||
use atomic::AtomicDrmDevice;
|
||||
use legacy::LegacyDrmDevice;
|
||||
|
||||
use tracing::{debug_span, error, info, instrument, trace};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PlaneClaimInner {
|
||||
plane: drm::control::plane::Handle,
|
||||
crtc: drm::control::crtc::Handle,
|
||||
storage: PlaneClaimStorage,
|
||||
}
|
||||
|
||||
impl Drop for PlaneClaimInner {
|
||||
fn drop(&mut self) {
|
||||
self.storage.remove(self.plane);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct PlaneClaimWeak(Weak<PlaneClaimInner>);
|
||||
|
||||
impl PlaneClaimWeak {
|
||||
fn upgrade(&self) -> Option<PlaneClaim> {
|
||||
self.0.upgrade().map(|claim| PlaneClaim { claim })
|
||||
}
|
||||
}
|
||||
|
||||
/// A claim of a plane
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlaneClaim {
|
||||
claim: Arc<PlaneClaimInner>,
|
||||
}
|
||||
|
||||
impl PlaneClaim {
|
||||
/// The plane the claim was taken for
|
||||
pub fn plane(&self) -> drm::control::plane::Handle {
|
||||
self.claim.plane
|
||||
}
|
||||
|
||||
/// The crtc the claim was taken for
|
||||
pub fn crtc(&self) -> drm::control::crtc::Handle {
|
||||
self.claim.crtc
|
||||
}
|
||||
}
|
||||
|
||||
impl PlaneClaim {
|
||||
fn downgrade(&self) -> PlaneClaimWeak {
|
||||
PlaneClaimWeak(Arc::downgrade(&self.claim))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct PlaneClaimStorage {
|
||||
claimed_planes: Arc<Mutex<HashMap<drm::control::plane::Handle, PlaneClaimWeak>>>,
|
||||
}
|
||||
|
||||
impl PlaneClaimStorage {
|
||||
pub fn claim(
|
||||
&self,
|
||||
plane: drm::control::plane::Handle,
|
||||
crtc: drm::control::crtc::Handle,
|
||||
) -> Option<PlaneClaim> {
|
||||
let mut guard = self.claimed_planes.lock().unwrap();
|
||||
if let Some(claim) = guard.get(&plane).and_then(|claim| claim.upgrade()) {
|
||||
if claim.crtc() == crtc { Some(claim) } else { None }
|
||||
} else {
|
||||
let claim = PlaneClaim {
|
||||
claim: Arc::new(PlaneClaimInner {
|
||||
plane,
|
||||
crtc,
|
||||
storage: self.clone(),
|
||||
}),
|
||||
};
|
||||
guard.insert(plane, claim.downgrade());
|
||||
Some(claim)
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&self, plane: drm::control::plane::Handle) {
|
||||
let mut guard = self.claimed_planes.lock().unwrap();
|
||||
guard.remove(&plane);
|
||||
}
|
||||
}
|
||||
|
||||
/// An open drm device
|
||||
#[derive(Debug)]
|
||||
pub struct DrmDevice {
|
||||
pub(super) dev_id: dev_t,
|
||||
pub(crate) internal: Arc<DrmDeviceInternal>,
|
||||
has_universal_planes: bool,
|
||||
cursor_size: Size<u32, Buffer>,
|
||||
resources: ResourceHandles,
|
||||
plane_claim_storage: PlaneClaimStorage,
|
||||
surfaces: Vec<Weak<DrmSurfaceInternal>>,
|
||||
}
|
||||
|
||||
impl AsFd for DrmDevice {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
match &*self.internal {
|
||||
DrmDeviceInternal::Atomic(dev) => dev.fd.as_fd(),
|
||||
DrmDeviceInternal::Legacy(dev) => dev.fd.as_fd(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BasicDevice for DrmDevice {}
|
||||
impl ControlDevice for DrmDevice {}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum DrmDeviceInternal {
|
||||
Atomic(AtomicDrmDevice),
|
||||
Legacy(LegacyDrmDevice),
|
||||
}
|
||||
|
||||
impl DrmDeviceInternal {
|
||||
pub(crate) fn device_fd(&self) -> &DrmDeviceFd {
|
||||
match self {
|
||||
DrmDeviceInternal::Atomic(dev) => &dev.fd,
|
||||
DrmDeviceInternal::Legacy(dev) => &dev.fd,
|
||||
}
|
||||
}
|
||||
|
||||
fn span(&self) -> &tracing::Span {
|
||||
match self {
|
||||
DrmDeviceInternal::Atomic(internal) => &internal.span,
|
||||
DrmDeviceInternal::Legacy(internal) => &internal.span,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsFd for DrmDeviceInternal {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
match self {
|
||||
DrmDeviceInternal::Atomic(dev) => dev.fd.as_fd(),
|
||||
DrmDeviceInternal::Legacy(dev) => dev.fd.as_fd(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BasicDevice for DrmDeviceInternal {}
|
||||
impl ControlDevice for DrmDeviceInternal {}
|
||||
|
||||
impl DrmDevice {
|
||||
/// Create a new [`DrmDevice`] from an open drm node
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `fd` - Open drm node
|
||||
/// - `disable_connectors` - Setting this to true will initialize all connectors \
|
||||
/// as disabled on device creation. smithay enables connectors, when attached \
|
||||
/// to a surface, and disables them, when detached. Setting this to `false` \
|
||||
/// requires usage of `drm-rs` to disable unused connectors to prevent them \
|
||||
/// showing garbage, but will also prevent flickering of already turned on \
|
||||
/// connectors (assuming you won't change the resolution).
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// Returns an error if the file is no valid drm node or the device is not accessible.
|
||||
pub fn new(fd: DrmDeviceFd, disable_connectors: bool) -> Result<(Self, DrmDeviceNotifier), Error> {
|
||||
// setup parent span for internal device types
|
||||
let span = debug_span!(
|
||||
"drm_device",
|
||||
device = ?fd.dev_path()
|
||||
);
|
||||
let _guard = span.enter();
|
||||
|
||||
info!("DrmDevice initializing");
|
||||
|
||||
let dev_id = fd.dev_id().map_err(Error::UnableToGetDeviceId)?;
|
||||
let active = Arc::new(AtomicBool::new(true));
|
||||
|
||||
let has_universal_planes = fd
|
||||
.set_client_capability(ClientCapability::UniversalPlanes, true)
|
||||
.is_ok();
|
||||
let has_monotonic_timestamps = fd
|
||||
.get_driver_capability(DriverCapability::MonotonicTimestamp)
|
||||
.unwrap_or(0)
|
||||
== 1;
|
||||
let cursor_width = fd
|
||||
.get_driver_capability(DriverCapability::CursorWidth)
|
||||
.unwrap_or(64);
|
||||
let cursor_height = fd
|
||||
.get_driver_capability(DriverCapability::CursorHeight)
|
||||
.unwrap_or(64);
|
||||
let cursor_size = Size::from((cursor_width as u32, cursor_height as u32));
|
||||
let resources = fd.resource_handles().map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error loading resource handles",
|
||||
dev: fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
|
||||
let internal = Arc::new(DrmDevice::create_internal(fd, active, disable_connectors)?);
|
||||
|
||||
Ok((
|
||||
DrmDevice {
|
||||
dev_id,
|
||||
internal: internal.clone(),
|
||||
has_universal_planes,
|
||||
cursor_size,
|
||||
resources,
|
||||
plane_claim_storage: Default::default(),
|
||||
surfaces: Default::default(),
|
||||
},
|
||||
DrmDeviceNotifier {
|
||||
internal,
|
||||
has_monotonic_timestamps,
|
||||
token: None,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
fn create_internal(
|
||||
fd: DrmDeviceFd,
|
||||
active: Arc<AtomicBool>,
|
||||
disable_connectors: bool,
|
||||
) -> Result<DrmDeviceInternal, Error> {
|
||||
let force_legacy = std::env::var("SMITHAY_USE_LEGACY")
|
||||
.map(|x| {
|
||||
x == "1" || x.to_lowercase() == "true" || x.to_lowercase() == "yes" || x.to_lowercase() == "y"
|
||||
})
|
||||
.unwrap_or(false);
|
||||
|
||||
if force_legacy {
|
||||
info!("SMITHAY_USE_LEGACY is set. Forcing LegacyDrmDevice.");
|
||||
};
|
||||
|
||||
Ok(
|
||||
if !force_legacy && fd.set_client_capability(ClientCapability::Atomic, true).is_ok() {
|
||||
DrmDeviceInternal::Atomic(AtomicDrmDevice::new(fd, active, disable_connectors)?)
|
||||
} else {
|
||||
info!("Falling back to LegacyDrmDevice");
|
||||
DrmDeviceInternal::Legacy(LegacyDrmDevice::new(fd, active, disable_connectors)?)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns if the underlying implementation uses atomic-modesetting or not.
|
||||
pub fn is_atomic(&self) -> bool {
|
||||
match *self.internal {
|
||||
DrmDeviceInternal::Atomic(_) => true,
|
||||
DrmDeviceInternal::Legacy(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a list of crtcs for this device
|
||||
pub fn crtcs(&self) -> &[crtc::Handle] {
|
||||
self.resources.crtcs()
|
||||
}
|
||||
|
||||
/// Returns a set of available planes for a given crtc
|
||||
pub fn planes(&self, crtc: &crtc::Handle) -> Result<Planes, Error> {
|
||||
planes(self, crtc, self.has_universal_planes)
|
||||
}
|
||||
|
||||
/// Claim a plane so that it won't be used by a different crtc
|
||||
///
|
||||
/// Returns `None` if the plane could not be claimed
|
||||
pub fn claim_plane(&self, plane: plane::Handle, crtc: crtc::Handle) -> Option<PlaneClaim> {
|
||||
self.plane_claim_storage.claim(plane, crtc)
|
||||
}
|
||||
|
||||
/// Returns the size of the hardware cursor
|
||||
///
|
||||
/// Note: In case of universal planes this is the
|
||||
/// maximum size of a buffer that can be used on
|
||||
/// the cursor plane.
|
||||
pub fn cursor_size(&self) -> Size<u32, Buffer> {
|
||||
self.cursor_size
|
||||
}
|
||||
|
||||
/// Creates a new rendering surface.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// Initialization of surfaces happens through the types provided by
|
||||
/// [`drm-rs`](drm).
|
||||
///
|
||||
/// - [`crtcs`](drm::control::crtc) represent scanout engines of the device pointing to one framebuffer. \
|
||||
/// Their responsibility is to read the data of the framebuffer and export it into an "Encoder". \
|
||||
/// The number of crtc's represent the number of independent output devices the hardware may handle.
|
||||
/// - [`mode`](drm::control::Mode) describes the resolution and rate of images produced by the crtc and \
|
||||
/// has to be compatible with the provided `connectors`.
|
||||
/// - [`connectors`](drm::control::connector) - List of connectors driven by the crtc. At least one(!) connector needs to be \
|
||||
/// attached to a crtc in smithay.
|
||||
#[instrument(skip(self), parent = self.internal.span(), err)]
|
||||
pub fn create_surface(
|
||||
&mut self,
|
||||
crtc: crtc::Handle,
|
||||
mode: Mode,
|
||||
connectors: &[connector::Handle],
|
||||
) -> Result<DrmSurface, Error> {
|
||||
self.surfaces.retain(|surface| surface.strong_count() != 0);
|
||||
|
||||
if connectors.is_empty() {
|
||||
return Err(Error::SurfaceWithoutConnectors(crtc));
|
||||
}
|
||||
|
||||
if !self.is_active() {
|
||||
return Err(Error::DeviceInactive);
|
||||
}
|
||||
|
||||
let planes = self.planes(&crtc)?;
|
||||
|
||||
let selected_primary_plane = planes.primary.iter().find_map(|plane| {
|
||||
let claim = match self.plane_claim_storage.claim(plane.handle, crtc) {
|
||||
Some(claim) => claim,
|
||||
None => {
|
||||
tracing::debug!(?crtc, ?plane, "skipping already claimed primary plane");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let info = match self.get_plane(plane.handle) {
|
||||
Ok(info) => info,
|
||||
Err(err) => {
|
||||
tracing::warn!(?crtc, ?err, ?plane, "failed to get primary plane info");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let filter = info.possible_crtcs();
|
||||
if !self.resources.filter_crtcs(filter).contains(&crtc) {
|
||||
tracing::warn!(?crtc, ?plane, "primary plane not compatible with crtc");
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((plane.clone(), claim))
|
||||
});
|
||||
|
||||
let Some((plane, claim)) = selected_primary_plane else {
|
||||
return Err(Error::NoPlane);
|
||||
};
|
||||
|
||||
let active = match &*self.internal {
|
||||
DrmDeviceInternal::Atomic(dev) => dev.active.clone(),
|
||||
DrmDeviceInternal::Legacy(dev) => dev.active.clone(),
|
||||
};
|
||||
|
||||
let internal = if self.is_atomic() {
|
||||
let mapping = match &*self.internal {
|
||||
DrmDeviceInternal::Atomic(dev) => dev.prop_mapping.clone(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
DrmSurfaceInternal::Atomic(AtomicDrmSurface::new(
|
||||
self.internal.clone(),
|
||||
active,
|
||||
crtc,
|
||||
plane.handle,
|
||||
mapping,
|
||||
mode,
|
||||
connectors,
|
||||
)?)
|
||||
} else {
|
||||
DrmSurfaceInternal::Legacy(LegacyDrmSurface::new(
|
||||
self.internal.clone(),
|
||||
active,
|
||||
crtc,
|
||||
mode,
|
||||
connectors,
|
||||
)?)
|
||||
};
|
||||
let internal = Arc::new(internal);
|
||||
self.surfaces.push(Arc::downgrade(&internal));
|
||||
|
||||
Ok(DrmSurface {
|
||||
dev_id: self.dev_id,
|
||||
crtc,
|
||||
planes,
|
||||
internal,
|
||||
plane_claim_storage: self.plane_claim_storage.clone(),
|
||||
primary_plane: (plane, claim),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the device_id of the underlying drm node
|
||||
pub fn device_id(&self) -> dev_t {
|
||||
self.dev_id
|
||||
}
|
||||
|
||||
/// Returns the underlying file descriptor
|
||||
pub fn device_fd(&self) -> &DrmDeviceFd {
|
||||
self.internal.device_fd()
|
||||
}
|
||||
|
||||
/// Pauses the device.
|
||||
///
|
||||
/// This will cause the `DrmDevice` to avoid making calls to the file descriptor e.g. on drop.
|
||||
/// Note that calls directly utilizing the underlying file descriptor, like the traits of the `drm-rs` crate,
|
||||
/// will ignore this state. Use [`DrmDevice::is_active`] to guard these calls.
|
||||
pub fn pause(&mut self) {
|
||||
self.set_active(false);
|
||||
self.surfaces.retain(|surface| surface.strong_count() != 0);
|
||||
if self.device_fd().is_privileged() {
|
||||
if let Err(err) = self.release_master_lock() {
|
||||
error!("Failed to drop drm master state Error: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Activates a previously paused device.
|
||||
///
|
||||
/// Specifying `true` for `disable_connectors` will call [`DrmDevice::reset_state`] if
|
||||
/// the device was not active before. Otherwise you need to make sure there are no
|
||||
/// conflicting requirements when enabling or creating surfaces or you are prepared
|
||||
/// to handle errors caused by those.
|
||||
pub fn activate(&mut self, disable_connectors: bool) -> Result<(), Error> {
|
||||
if self.device_fd().is_privileged() {
|
||||
if let Err(err) = self.acquire_master_lock() {
|
||||
error!("Failed to acquire drm master on activate. Error: {}", err);
|
||||
return Err(Error::DrmMasterFailed);
|
||||
}
|
||||
}
|
||||
if !self.set_active(true) && disable_connectors {
|
||||
self.reset_state()
|
||||
} else {
|
||||
self.surfaces.retain(|surface| surface.strong_count() != 0);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns if the device is currently paused or not.
|
||||
pub fn is_active(&self) -> bool {
|
||||
match &*self.internal {
|
||||
DrmDeviceInternal::Atomic(internal) => internal.active.load(Ordering::SeqCst),
|
||||
DrmDeviceInternal::Legacy(internal) => internal.active.load(Ordering::SeqCst),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset the state of this device
|
||||
///
|
||||
/// This will disable all connectors and reset all planes.
|
||||
/// Additional this will also reset the state on all known surfaces.
|
||||
pub fn reset_state(&mut self) -> Result<(), Error> {
|
||||
if !self.is_active() {
|
||||
return Err(Error::DeviceInactive);
|
||||
}
|
||||
|
||||
match &*self.internal {
|
||||
DrmDeviceInternal::Atomic(internal) => internal.reset_state(),
|
||||
DrmDeviceInternal::Legacy(internal) => internal.reset_state(),
|
||||
}?;
|
||||
|
||||
let mut i = 0;
|
||||
while i != self.surfaces.len() {
|
||||
if let Some(surface) = self.surfaces[i].upgrade() {
|
||||
match &*surface {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.reset_state::<Self>(None),
|
||||
DrmSurfaceInternal::Legacy(surf) => surf.reset_state::<Self>(None),
|
||||
}?;
|
||||
i += 1;
|
||||
} else {
|
||||
self.surfaces.remove(i);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_active(&self, active: bool) -> bool {
|
||||
match &*self.internal {
|
||||
DrmDeviceInternal::Atomic(internal) => internal.active.swap(active, Ordering::SeqCst),
|
||||
DrmDeviceInternal::Legacy(internal) => internal.active.swap(active, Ordering::SeqCst),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Events that can be generated by a DrmDevice
|
||||
#[derive(Debug)]
|
||||
pub enum DrmEvent {
|
||||
/// A vblank blank event on the provided crtc has happened
|
||||
VBlank(crtc::Handle),
|
||||
/// An error happened while processing events
|
||||
Error(Error),
|
||||
}
|
||||
|
||||
/// Timing metadata for page-flip events
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct EventMetadata {
|
||||
/// The time the frame flip happened
|
||||
pub time: Time,
|
||||
/// The sequence number of the frame
|
||||
pub sequence: u32,
|
||||
}
|
||||
|
||||
/// Either a realtime or monotonic timestamp
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Time {
|
||||
/// Monotonic time stamp
|
||||
Monotonic(Duration),
|
||||
/// Realtime time stamp
|
||||
Realtime(SystemTime),
|
||||
}
|
||||
|
||||
/// Even source of [`DrmDevice`]
|
||||
#[derive(Debug)]
|
||||
pub struct DrmDeviceNotifier {
|
||||
internal: Arc<DrmDeviceInternal>,
|
||||
has_monotonic_timestamps: bool,
|
||||
token: Option<Token>,
|
||||
}
|
||||
|
||||
impl EventSource for DrmDeviceNotifier {
|
||||
type Event = DrmEvent;
|
||||
type Metadata = Option<EventMetadata>;
|
||||
type Ret = ();
|
||||
type Error = io::Error;
|
||||
|
||||
fn process_events<F>(&mut self, _: Readiness, token: Token, mut callback: F) -> io::Result<PostAction>
|
||||
where
|
||||
F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret,
|
||||
{
|
||||
let _guard = self.internal.span().enter();
|
||||
|
||||
if Some(token) != self.token {
|
||||
return Ok(PostAction::Continue);
|
||||
}
|
||||
|
||||
match self.internal.receive_events() {
|
||||
Ok(events) => {
|
||||
for event in events {
|
||||
if let Event::PageFlip(event) = event {
|
||||
trace!("Got a page-flip event for crtc ({:?})", event.crtc);
|
||||
let metadata = EventMetadata {
|
||||
time: if self.has_monotonic_timestamps {
|
||||
Time::Monotonic(event.duration)
|
||||
} else {
|
||||
Time::Realtime(SystemTime::UNIX_EPOCH + event.duration)
|
||||
},
|
||||
sequence: event.frame,
|
||||
};
|
||||
callback(DrmEvent::VBlank(event.crtc), &mut Some(metadata));
|
||||
} else {
|
||||
trace!(
|
||||
"Got a non-page-flip event of device '{:?}'.",
|
||||
self.internal.dev_path()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(source) => {
|
||||
callback(
|
||||
DrmEvent::Error(Error::Access(AccessError {
|
||||
errmsg: "Error processing drm events",
|
||||
dev: self.internal.dev_path(),
|
||||
source,
|
||||
})),
|
||||
&mut None,
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(PostAction::Continue)
|
||||
}
|
||||
|
||||
fn register(&mut self, poll: &mut Poll, factory: &mut TokenFactory) -> calloop::Result<()> {
|
||||
self.token = Some(factory.token());
|
||||
// Safety: the FD cannot be closed without removing the DrmDeviceNotifier from the event loop
|
||||
unsafe {
|
||||
poll.register(
|
||||
self.internal.as_fd(),
|
||||
Interest::READ,
|
||||
calloop::Mode::Level,
|
||||
self.token.unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn reregister(&mut self, poll: &mut Poll, factory: &mut TokenFactory) -> calloop::Result<()> {
|
||||
self.token = Some(factory.token());
|
||||
poll.reregister(
|
||||
self.internal.as_fd(),
|
||||
Interest::READ,
|
||||
calloop::Mode::Level,
|
||||
self.token.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
fn unregister(&mut self, poll: &mut Poll) -> calloop::Result<()> {
|
||||
self.token = None;
|
||||
poll.unregister(self.internal.as_fd())
|
||||
}
|
||||
}
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
//! Utilities to attach [`framebuffer::Handle`]s to dumb buffers
|
||||
|
||||
use drm_fourcc::{DrmFormat, DrmModifier};
|
||||
|
||||
use drm::{
|
||||
buffer::{Buffer as DrmBuffer, PlanarBuffer},
|
||||
control::{Device, FbCmd2Flags, framebuffer},
|
||||
};
|
||||
use tracing::{trace, warn};
|
||||
|
||||
use crate::utils::DevPath;
|
||||
use crate::{
|
||||
backend::{
|
||||
allocator::{
|
||||
Buffer, Fourcc,
|
||||
dumb::DumbBuffer,
|
||||
format::{get_bpp, get_depth, get_opaque},
|
||||
},
|
||||
drm::DrmDeviceFd,
|
||||
},
|
||||
utils::{Buffer as BufferCoords, Size},
|
||||
};
|
||||
|
||||
use super::{Framebuffer, error::AccessError, warn_legacy_fb_export};
|
||||
|
||||
/// A GBM backed framebuffer
|
||||
#[derive(Debug)]
|
||||
pub struct DumbFramebuffer {
|
||||
fb: framebuffer::Handle,
|
||||
format: drm_fourcc::DrmFormat,
|
||||
drm: DrmDeviceFd,
|
||||
}
|
||||
|
||||
impl Drop for DumbFramebuffer {
|
||||
fn drop(&mut self) {
|
||||
trace!(fb = ?self.fb, "destroying framebuffer");
|
||||
if let Err(err) = self.drm.destroy_framebuffer(self.fb) {
|
||||
warn!(fb = ?self.fb, ?err, "failed to destroy framebuffer");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<framebuffer::Handle> for DumbFramebuffer {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &framebuffer::Handle {
|
||||
&self.fb
|
||||
}
|
||||
}
|
||||
|
||||
impl Framebuffer for DumbFramebuffer {
|
||||
#[inline]
|
||||
fn format(&self) -> drm_fourcc::DrmFormat {
|
||||
self.format
|
||||
}
|
||||
}
|
||||
|
||||
struct PlanarDumbBuffer<'a>(&'a DumbBuffer);
|
||||
|
||||
impl Buffer for PlanarDumbBuffer<'_> {
|
||||
#[inline]
|
||||
fn size(&self) -> Size<i32, BufferCoords> {
|
||||
self.0.size()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn format(&self) -> DrmFormat {
|
||||
self.0.format()
|
||||
}
|
||||
}
|
||||
|
||||
impl PlanarBuffer for PlanarDumbBuffer<'_> {
|
||||
#[inline]
|
||||
fn size(&self) -> (u32, u32) {
|
||||
let size = self.0.size();
|
||||
(size.w as u32, size.h as u32)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn format(&self) -> Fourcc {
|
||||
self.0.format().code
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn modifier(&self) -> Option<DrmModifier> {
|
||||
Some(self.0.format().modifier)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pitches(&self) -> [u32; 4] {
|
||||
[self.0.handle().pitch(), 0, 0, 0]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn handles(&self) -> [Option<drm::buffer::Handle>; 4] {
|
||||
[Some(self.0.handle().handle()), None, None, None]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn offsets(&self) -> [u32; 4] {
|
||||
[0, 0, 0, 0]
|
||||
}
|
||||
}
|
||||
|
||||
struct OpaqueBufferWrapper<'a, B>(&'a B);
|
||||
impl<B> PlanarBuffer for OpaqueBufferWrapper<'_, B>
|
||||
where
|
||||
B: PlanarBuffer,
|
||||
{
|
||||
#[inline]
|
||||
fn size(&self) -> (u32, u32) {
|
||||
self.0.size()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn format(&self) -> Fourcc {
|
||||
let fmt = self.0.format();
|
||||
get_opaque(fmt).unwrap_or(fmt)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn modifier(&self) -> Option<DrmModifier> {
|
||||
self.0.modifier()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pitches(&self) -> [u32; 4] {
|
||||
self.0.pitches()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn handles(&self) -> [Option<drm::buffer::Handle>; 4] {
|
||||
self.0.handles()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn offsets(&self) -> [u32; 4] {
|
||||
self.0.offsets()
|
||||
}
|
||||
}
|
||||
|
||||
/// Attach a [`framebuffer::Handle`] to an [`DumbBuffer`]
|
||||
#[profiling::function]
|
||||
pub fn framebuffer_from_dumb_buffer(
|
||||
drm: &DrmDeviceFd,
|
||||
buffer: &DumbBuffer,
|
||||
use_opaque: bool,
|
||||
) -> Result<DumbFramebuffer, AccessError> {
|
||||
let buffer = PlanarDumbBuffer(buffer);
|
||||
|
||||
let format = Buffer::format(&buffer);
|
||||
let modifier = buffer.modifier();
|
||||
let flags = if modifier.is_some() {
|
||||
FbCmd2Flags::MODIFIERS
|
||||
} else {
|
||||
FbCmd2Flags::empty()
|
||||
};
|
||||
|
||||
let ret = if use_opaque {
|
||||
let opaque_wrapper = OpaqueBufferWrapper(&buffer);
|
||||
drm.add_planar_framebuffer(&opaque_wrapper, flags).map(|fb| {
|
||||
(
|
||||
fb,
|
||||
drm_fourcc::DrmFormat {
|
||||
code: opaque_wrapper.format(),
|
||||
modifier: modifier.unwrap_or(DrmModifier::Invalid),
|
||||
},
|
||||
)
|
||||
})
|
||||
} else {
|
||||
drm.add_planar_framebuffer(&buffer, flags).map(|fb| (fb, format))
|
||||
};
|
||||
|
||||
let (fb, format) = match ret {
|
||||
Ok(fb) => fb,
|
||||
Err(source) => {
|
||||
warn_legacy_fb_export();
|
||||
|
||||
let fourcc = format.code;
|
||||
let (depth, bpp) = get_depth(fourcc)
|
||||
.zip(get_bpp(fourcc))
|
||||
.ok_or_else(|| AccessError {
|
||||
errmsg: "Unknown format for legacy framebuffer",
|
||||
dev: drm.dev_path(),
|
||||
source,
|
||||
})?;
|
||||
|
||||
let fb = drm
|
||||
.add_framebuffer(buffer.0.handle(), depth as u32, bpp as u32)
|
||||
.map_err(|source| AccessError {
|
||||
errmsg: "Failed to add framebuffer",
|
||||
dev: drm.dev_path(),
|
||||
source,
|
||||
})?;
|
||||
(fb, format)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(DumbFramebuffer {
|
||||
fb,
|
||||
format,
|
||||
drm: drm.clone(),
|
||||
})
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
use crate::backend::SwapBuffersError;
|
||||
use drm::control::{Mode, RawResourceHandle, connector, crtc, plane};
|
||||
use std::{
|
||||
io::{self, ErrorKind},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
/// DRM access error
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("DRM access error: {errmsg} on device `{dev:?}` ({source:})")]
|
||||
pub struct AccessError {
|
||||
/// Error message associated to the access error
|
||||
pub(crate) errmsg: &'static str,
|
||||
/// Device on which the error was generated
|
||||
pub(crate) dev: Option<PathBuf>,
|
||||
/// Underlying device error
|
||||
#[source]
|
||||
pub source: io::Error,
|
||||
}
|
||||
|
||||
/// Errors thrown by the [`DrmDevice`](crate::backend::drm::DrmDevice)
|
||||
/// and the [`DrmSurface`](crate::backend::drm::DrmSurface).
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum Error {
|
||||
/// Unable to acquire DRM master
|
||||
#[error("Failed to acquire DRM master")]
|
||||
DrmMasterFailed,
|
||||
/// The `DrmDevice` encountered an access error
|
||||
#[error(transparent)]
|
||||
Access(#[from] AccessError),
|
||||
/// Unable to determine device id of drm device
|
||||
#[error("Unable to determine device id of drm device")]
|
||||
UnableToGetDeviceId(#[source] rustix::io::Errno),
|
||||
/// Device is currently paused
|
||||
#[error("Device is currently paused, operation rejected")]
|
||||
DeviceInactive,
|
||||
/// Mode is not compatible with all given connectors
|
||||
#[error("Mode `{0:?}` is not compatible with all given connectors")]
|
||||
ModeNotSuitable(Mode),
|
||||
/// The given crtc is already in use by another surface
|
||||
#[error("Crtc `{0:?}` is already in use by another surface")]
|
||||
CrtcAlreadyInUse(crtc::Handle),
|
||||
/// This operation would result in a surface without connectors.
|
||||
#[error("Surface of crtc `{0:?}` would have no connectors, which is not accepted")]
|
||||
SurfaceWithoutConnectors(crtc::Handle),
|
||||
/// The given plane cannot be used with the given crtc
|
||||
#[error("Plane `{1:?}` is not compatible for use with crtc `{0:?}`")]
|
||||
PlaneNotCompatible(crtc::Handle, plane::Handle),
|
||||
/// The given configuration does not specify a plane which is not supported by the underlying implementation
|
||||
#[error("No Plane has been specified which is not supported by the underlying implementation")]
|
||||
NoPlane,
|
||||
/// The given plane is not a primary plane and therefor not supported by the underlying implementation
|
||||
#[error("Non-Primary Planes (provided was `{0:?}`) are not available for use with legacy devices")]
|
||||
NonPrimaryPlane(plane::Handle),
|
||||
/// The given plane does not allow to clear the framebuffer
|
||||
#[error("Clearing the framebuffer on plane `{0:?}` is not supported")]
|
||||
NoFramebuffer(plane::Handle),
|
||||
/// The configuration is not supported on the given plane
|
||||
#[error("The configuration is not supported on plane `{0:?}`")]
|
||||
UnsupportedPlaneConfiguration(plane::Handle),
|
||||
/// No encoder was found for a given connector on the set crtc
|
||||
#[error("No encoder found for the given connector '{connector:?}' on crtc `{crtc:?}`")]
|
||||
NoSuitableEncoder {
|
||||
/// Connector
|
||||
connector: connector::Handle,
|
||||
/// CRTC
|
||||
crtc: crtc::Handle,
|
||||
},
|
||||
/// Unknown connector handle
|
||||
#[error("The connector ({0:?}) is unknown")]
|
||||
UnknownConnector(connector::Handle),
|
||||
/// Unknown crtc handle
|
||||
#[error("The crtc ({0:?}) is unknown")]
|
||||
UnknownCrtc(crtc::Handle),
|
||||
/// Unknown crtc handle
|
||||
#[error("The plane ({0:?}) is unknown")]
|
||||
UnknownPlane(plane::Handle),
|
||||
/// The DrmDevice is missing a required property
|
||||
#[error("The DrmDevice is missing a required property '{name}' for handle ({handle:?})")]
|
||||
UnknownProperty {
|
||||
/// Property handle
|
||||
handle: RawResourceHandle,
|
||||
/// Property name
|
||||
name: &'static str,
|
||||
},
|
||||
/// Atomic Test failed for new properties
|
||||
#[error("Atomic Test failed for new properties on crtc ({0:?})")]
|
||||
TestFailed(crtc::Handle),
|
||||
}
|
||||
|
||||
impl From<Error> for SwapBuffersError {
|
||||
#[inline]
|
||||
fn from(err: Error) -> SwapBuffersError {
|
||||
// FIXME: replace the special handling for EBUSY with ErrorKind::ResourceBusy once
|
||||
// we reach MSRV >= 1.83
|
||||
match err {
|
||||
x @ Error::DeviceInactive | x @ Error::DrmMasterFailed => {
|
||||
SwapBuffersError::TemporaryFailure(Box::new(x))
|
||||
}
|
||||
Error::Access(AccessError {
|
||||
errmsg, dev, source, ..
|
||||
}) if matches!(
|
||||
source.kind(),
|
||||
ErrorKind::PermissionDenied | ErrorKind::WouldBlock | ErrorKind::Interrupted
|
||||
) || rustix::io::Errno::from_io_error(&source) == Some(rustix::io::Errno::BUSY) =>
|
||||
{
|
||||
SwapBuffersError::TemporaryFailure(Box::new(Error::Access(AccessError {
|
||||
errmsg,
|
||||
dev,
|
||||
source,
|
||||
})))
|
||||
}
|
||||
x => SwapBuffersError::ContextLost(Box::new(x)),
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
//! Implementation for ExportFramebuffer and related utilities for dumb buffers
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use super::{ExportBuffer, ExportFramebuffer};
|
||||
use crate::backend::{
|
||||
allocator::dumb::DumbBuffer,
|
||||
drm::{
|
||||
DrmDeviceFd,
|
||||
dumb::{DumbFramebuffer, framebuffer_from_dumb_buffer},
|
||||
error::AccessError,
|
||||
},
|
||||
};
|
||||
|
||||
/// Possible errors for attaching a [`framebuffer::Handle`](::drm::control::framebuffer::Handle)
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
/// The buffer is not supported
|
||||
#[error("Unsupported buffer supplied")]
|
||||
Unsupported,
|
||||
/// Failed to add a framebuffer for the dumb buffer
|
||||
#[error("failed to add a framebuffer for the dumb buffer")]
|
||||
Drm(AccessError),
|
||||
}
|
||||
|
||||
impl ExportFramebuffer<DumbBuffer> for DrmDeviceFd {
|
||||
type Framebuffer = DumbFramebuffer;
|
||||
type Error = Error;
|
||||
|
||||
#[profiling::function]
|
||||
fn add_framebuffer(
|
||||
&self,
|
||||
_drm: &DrmDeviceFd,
|
||||
buffer: ExportBuffer<'_, DumbBuffer>,
|
||||
use_opaque: bool,
|
||||
) -> Result<Option<Self::Framebuffer>, Self::Error> {
|
||||
match buffer {
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
ExportBuffer::Wayland(_) => return Err(Error::Unsupported),
|
||||
ExportBuffer::Allocator(buffer) => framebuffer_from_dumb_buffer(self, buffer, use_opaque)
|
||||
.map_err(Error::Drm)
|
||||
.map(Some),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn can_add_framebuffer(&self, buffer: &ExportBuffer<'_, DumbBuffer>) -> bool {
|
||||
match buffer {
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
ExportBuffer::Wayland(_) => false,
|
||||
ExportBuffer::Allocator(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
//! Implementation for ExportFramebuffer and related utilizes for gbm types
|
||||
|
||||
use std::os::unix::io::AsFd;
|
||||
|
||||
use drm::node::DrmNode;
|
||||
|
||||
use super::{ExportBuffer, ExportFramebuffer};
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
use crate::backend::drm::gbm::framebuffer_from_wayland_buffer;
|
||||
use crate::backend::{
|
||||
allocator::{
|
||||
dmabuf::AsDmabuf,
|
||||
gbm::{GbmBuffer, GbmConvertError},
|
||||
},
|
||||
drm::{
|
||||
DrmDeviceFd,
|
||||
gbm::{Error as GbmError, GbmFramebuffer, framebuffer_from_bo, framebuffer_from_dmabuf},
|
||||
},
|
||||
};
|
||||
|
||||
/// Error for [`GbmFramebufferExporter`]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// Exporting the [`GbmBuffer`] as a [`Dmabuf`](crate::backend::allocator::dmabuf::Dmabuf) failed
|
||||
#[error(transparent)]
|
||||
Dmabuf(#[from] GbmConvertError),
|
||||
/// Exporting the [`GbmBuffer`] failed
|
||||
#[error(transparent)]
|
||||
Gbm(#[from] GbmError),
|
||||
}
|
||||
|
||||
/// Export framebuffers based on [`gbm::Device`]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct GbmFramebufferExporter<A: AsFd + 'static> {
|
||||
gbm: gbm::Device<A>,
|
||||
drm_node: Option<DrmNode>,
|
||||
#[cfg_attr(not(feature = "wayland_frontend"), allow(unused))]
|
||||
import_node: NodeFilter,
|
||||
}
|
||||
|
||||
impl<A: AsFd + 'static> GbmFramebufferExporter<A> {
|
||||
/// Initialize a new framebuffer exporter.
|
||||
///
|
||||
/// `import_node` will be used to filter dmabufs to originate from a particular
|
||||
/// device before considering them for direct scanout.
|
||||
///
|
||||
/// If `import_node` is [`NodeFilter::None`], direct-scanout of client-buffers
|
||||
/// won't be used.
|
||||
pub fn new(gbm: gbm::Device<A>, import_node: NodeFilter) -> Self {
|
||||
let drm_node = DrmNode::from_file(gbm.as_fd()).ok();
|
||||
Self {
|
||||
gbm,
|
||||
drm_node,
|
||||
import_node,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: AsFd + 'static> ExportFramebuffer<GbmBuffer> for GbmFramebufferExporter<A> {
|
||||
type Framebuffer = GbmFramebuffer;
|
||||
type Error = Error;
|
||||
|
||||
#[profiling::function]
|
||||
fn add_framebuffer(
|
||||
&self,
|
||||
drm: &DrmDeviceFd,
|
||||
buffer: ExportBuffer<'_, GbmBuffer>,
|
||||
use_opaque: bool,
|
||||
) -> Result<Option<Self::Framebuffer>, Self::Error> {
|
||||
let framebuffer = match buffer {
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
ExportBuffer::Wayland(wl_buffer) => {
|
||||
framebuffer_from_wayland_buffer(drm, &self.gbm, wl_buffer, use_opaque)?
|
||||
}
|
||||
ExportBuffer::Allocator(buffer) => {
|
||||
let foreign = self.drm_node.is_none()
|
||||
|| buffer.device_node().is_none()
|
||||
|| self.drm_node != buffer.device_node();
|
||||
if foreign {
|
||||
tracing::debug!("importing foreign buffer");
|
||||
let dmabuf = buffer.export()?;
|
||||
framebuffer_from_dmabuf(drm, &self.gbm, &dmabuf, use_opaque, true).map(Some)?
|
||||
} else {
|
||||
framebuffer_from_bo(drm, buffer, use_opaque)
|
||||
.map_err(GbmError::Drm)
|
||||
.map(Some)?
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(framebuffer)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
fn can_add_framebuffer(&self, buffer: &ExportBuffer<'_, GbmBuffer>) -> bool {
|
||||
match buffer {
|
||||
#[cfg(not(all(feature = "backend_egl", feature = "use_system_lib")))]
|
||||
ExportBuffer::Wayland(buffer) => {
|
||||
let node = crate::wayland::dmabuf::get_dmabuf(buffer)
|
||||
.ok()
|
||||
.and_then(|buf| buf.node());
|
||||
self.import_node == node
|
||||
}
|
||||
#[cfg(all(feature = "backend_egl", feature = "use_system_lib"))]
|
||||
ExportBuffer::Wayland(buffer) => match crate::backend::renderer::buffer_type(buffer) {
|
||||
Some(crate::backend::renderer::BufferType::Dma) => {
|
||||
let node = crate::wayland::dmabuf::get_dmabuf(buffer).unwrap().node();
|
||||
self.import_node == node
|
||||
}
|
||||
// Arguably we need specialization here. If the renderer (which we have in `element_config`, which calls this function)
|
||||
// has `ImportEGL`, we can verify that `EGLBufferRender` is some, which means we have the renderer advertised via wl_drm,
|
||||
// which means this is probably fine.
|
||||
// If we don't have `ImportEGL` or `EGLBufferReader` is none, we should reject this, but we don't want to require `ImportEGL`.
|
||||
//
|
||||
// So for now hope that `gbm_framebuffer_from_wayland_buffer` is smart enough to deal with this correctly.
|
||||
// (And most modern compositor don't use wl_drm anyway.)
|
||||
Some(crate::backend::renderer::BufferType::Egl) => true,
|
||||
_ => false,
|
||||
},
|
||||
ExportBuffer::Allocator(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(not(feature = "wayland_frontend"))]
|
||||
fn can_add_framebuffer(&self, buffer: &ExportBuffer<'_, GbmBuffer>) -> bool {
|
||||
match buffer {
|
||||
ExportBuffer::Allocator(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Filter to matching nodes against.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum NodeFilter {
|
||||
/// Consider no nodes a match.
|
||||
None,
|
||||
/// Consider all nodes a match.
|
||||
All,
|
||||
/// Consider only the specified node a match.
|
||||
Node(DrmNode),
|
||||
}
|
||||
|
||||
impl PartialEq<Option<DrmNode>> for NodeFilter {
|
||||
fn eq(&self, other: &Option<DrmNode>) -> bool {
|
||||
match self {
|
||||
Self::None => false,
|
||||
Self::All => true,
|
||||
Self::Node(node) => other.is_some_and(|n| &n == node),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DrmNode> for NodeFilter {
|
||||
fn from(node: DrmNode) -> Self {
|
||||
Self::Node(node)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<DrmNode>> for NodeFilter {
|
||||
fn from(node: Option<DrmNode>) -> Self {
|
||||
match node {
|
||||
Some(node) => Self::Node(node),
|
||||
None => Self::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
//! Trait and data structures to describe types, that can be exported to a drm framebuffer
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
rc::Rc,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
use wayland_server::protocol::wl_buffer::WlBuffer;
|
||||
|
||||
use crate::backend::{allocator::Buffer, renderer::element::UnderlyingStorage};
|
||||
|
||||
use super::{DrmDeviceFd, Framebuffer};
|
||||
|
||||
#[cfg(feature = "backend_drm")]
|
||||
pub mod dumb;
|
||||
#[cfg(feature = "backend_gbm")]
|
||||
pub mod gbm;
|
||||
|
||||
/// Possible buffers to export as a framebuffer using [`ExportFramebuffer`]
|
||||
#[derive(Debug)]
|
||||
pub enum ExportBuffer<'a, B: Buffer> {
|
||||
/// A wayland buffer
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
Wayland(&'a WlBuffer),
|
||||
/// A [`Allocator`][crate::backend::allocator::Allocator] buffer
|
||||
Allocator(&'a B),
|
||||
}
|
||||
|
||||
impl<'a, B: Buffer> ExportBuffer<'a, B> {
|
||||
/// Create the export buffer from an [`UnderlyingStorage`]
|
||||
#[inline]
|
||||
pub fn from_underlying_storage(storage: &'a UnderlyingStorage<'_>) -> Option<Self> {
|
||||
match storage {
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
UnderlyingStorage::Wayland(buffer) => Some(Self::Wayland(buffer)),
|
||||
UnderlyingStorage::Memory { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Export a [`ExportBuffer`] as a framebuffer
|
||||
pub trait ExportFramebuffer<B: Buffer>
|
||||
where
|
||||
B: Buffer,
|
||||
{
|
||||
/// Type of the framebuffer
|
||||
type Framebuffer: Framebuffer;
|
||||
|
||||
/// Type of the error
|
||||
type Error: std::error::Error;
|
||||
|
||||
/// Add a framebuffer for the specified buffer
|
||||
fn add_framebuffer(
|
||||
&self,
|
||||
drm: &DrmDeviceFd,
|
||||
buffer: ExportBuffer<'_, B>,
|
||||
use_opaque: bool,
|
||||
) -> Result<Option<Self::Framebuffer>, Self::Error>;
|
||||
|
||||
/// Test if the provided buffer is eligible for adding a framebuffer
|
||||
fn can_add_framebuffer(&self, buffer: &ExportBuffer<'_, B>) -> bool;
|
||||
}
|
||||
|
||||
impl<F, B> ExportFramebuffer<B> for Arc<Mutex<F>>
|
||||
where
|
||||
F: ExportFramebuffer<B>,
|
||||
B: Buffer,
|
||||
{
|
||||
type Framebuffer = <F as ExportFramebuffer<B>>::Framebuffer;
|
||||
type Error = <F as ExportFramebuffer<B>>::Error;
|
||||
|
||||
#[inline]
|
||||
fn add_framebuffer(
|
||||
&self,
|
||||
drm: &DrmDeviceFd,
|
||||
buffer: ExportBuffer<'_, B>,
|
||||
use_opaque: bool,
|
||||
) -> Result<Option<Self::Framebuffer>, Self::Error> {
|
||||
let guard = self.lock().unwrap();
|
||||
guard.add_framebuffer(drm, buffer, use_opaque)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn can_add_framebuffer(&self, buffer: &ExportBuffer<'_, B>) -> bool {
|
||||
let guard = self.lock().unwrap();
|
||||
guard.can_add_framebuffer(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, B> ExportFramebuffer<B> for Rc<RefCell<F>>
|
||||
where
|
||||
F: ExportFramebuffer<B>,
|
||||
B: Buffer,
|
||||
{
|
||||
type Framebuffer = <F as ExportFramebuffer<B>>::Framebuffer;
|
||||
type Error = <F as ExportFramebuffer<B>>::Error;
|
||||
|
||||
#[inline]
|
||||
fn add_framebuffer(
|
||||
&self,
|
||||
drm: &DrmDeviceFd,
|
||||
buffer: ExportBuffer<'_, B>,
|
||||
use_opaque: bool,
|
||||
) -> Result<Option<Self::Framebuffer>, Self::Error> {
|
||||
self.borrow().add_framebuffer(drm, buffer, use_opaque)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn can_add_framebuffer(&self, buffer: &ExportBuffer<'_, B>) -> bool {
|
||||
self.borrow().can_add_framebuffer(buffer)
|
||||
}
|
||||
}
|
||||
+381
@@ -0,0 +1,381 @@
|
||||
//! Utilities to attach [`framebuffer::Handle`]s to gbm backed buffers
|
||||
|
||||
use std::os::unix::io::AsFd;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use drm::{
|
||||
buffer::PlanarBuffer,
|
||||
control::{Device, FbCmd2Flags, framebuffer},
|
||||
};
|
||||
use drm_fourcc::DrmModifier;
|
||||
use tracing::{trace, warn};
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
use wayland_server::protocol::wl_buffer::WlBuffer;
|
||||
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
use crate::backend::allocator::Buffer;
|
||||
use crate::backend::{
|
||||
allocator::{
|
||||
Fourcc,
|
||||
dmabuf::Dmabuf,
|
||||
format::{get_bpp, get_depth, get_opaque},
|
||||
gbm::GbmBuffer,
|
||||
},
|
||||
drm::DrmDeviceFd,
|
||||
};
|
||||
use crate::utils::DevPath;
|
||||
|
||||
use super::{Framebuffer, error::AccessError, warn_legacy_fb_export};
|
||||
|
||||
/// A GBM backed framebuffer
|
||||
#[derive(Debug)]
|
||||
pub struct GbmFramebuffer {
|
||||
fb: framebuffer::Handle,
|
||||
format: drm_fourcc::DrmFormat,
|
||||
drm: DrmDeviceFd,
|
||||
}
|
||||
|
||||
impl Drop for GbmFramebuffer {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
trace!(fb = ?self.fb, "destroying framebuffer");
|
||||
if let Err(err) = self.drm.destroy_framebuffer(self.fb) {
|
||||
warn!(fb = ?self.fb, ?err, "failed to destroy framebuffer");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<framebuffer::Handle> for GbmFramebuffer {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &framebuffer::Handle {
|
||||
&self.fb
|
||||
}
|
||||
}
|
||||
|
||||
impl Framebuffer for GbmFramebuffer {
|
||||
#[inline]
|
||||
fn format(&self) -> drm_fourcc::DrmFormat {
|
||||
self.format
|
||||
}
|
||||
}
|
||||
|
||||
/// Attach a framebuffer for a [`WlBuffer`]
|
||||
///
|
||||
/// This tries to import the buffer to gbm and attach a [`framebuffer::Handle`] for
|
||||
/// the imported [`BufferObject`][gbm::BufferObject].
|
||||
///
|
||||
/// Returns `Ok(None)` for unknown buffer types and buffer types that do not
|
||||
/// support attaching a framebuffer (e.g. shm-buffers)
|
||||
#[cfg(feature = "wayland_frontend")]
|
||||
#[profiling::function]
|
||||
pub fn framebuffer_from_wayland_buffer<A: AsFd + 'static>(
|
||||
drm: &DrmDeviceFd,
|
||||
gbm: &gbm::Device<A>,
|
||||
buffer: &WlBuffer,
|
||||
use_opaque: bool,
|
||||
) -> Result<Option<GbmFramebuffer>, Error> {
|
||||
if let Ok(dmabuf) = crate::wayland::dmabuf::get_dmabuf(buffer) {
|
||||
// From weston:
|
||||
/* We should not import to KMS a buffer that has been allocated using no
|
||||
* modifiers. Usually drivers use linear layouts to allocate with no
|
||||
* modifiers, but this is not a rule. The driver could use, for
|
||||
* instance, a tiling layout under the hood - and both Weston and the
|
||||
* KMS driver can't know. So giving the buffer to KMS is not safe, as
|
||||
* not knowing its layout can result in garbage being displayed. In
|
||||
* short, importing a buffer to KMS requires explicit modifiers. */
|
||||
if dmabuf.format().modifier != DrmModifier::Invalid {
|
||||
return Ok(Some(framebuffer_from_dmabuf(
|
||||
drm, gbm, dmabuf, use_opaque, false,
|
||||
)?));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "backend_egl", feature = "use_system_lib"))]
|
||||
if matches!(
|
||||
crate::backend::renderer::buffer_type(buffer),
|
||||
Some(crate::backend::renderer::BufferType::Egl)
|
||||
) {
|
||||
let bo = gbm
|
||||
.import_buffer_object_from_wayland::<()>(buffer, gbm::BufferObjectFlags::SCANOUT)
|
||||
.map(|bo| GbmBuffer::from_bo(bo, true))
|
||||
.map_err(Error::Import)?;
|
||||
let (fb, format) = framebuffer_from_bo_internal(
|
||||
drm,
|
||||
BufferObjectInternal {
|
||||
bo: &bo,
|
||||
offsets: None,
|
||||
pitches: None,
|
||||
},
|
||||
use_opaque,
|
||||
true,
|
||||
)
|
||||
.map_err(Error::Drm)?;
|
||||
|
||||
return Ok(Some(GbmFramebuffer {
|
||||
fb,
|
||||
format,
|
||||
drm: drm.clone(),
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Possible errors for attaching a [`framebuffer::Handle`]
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
/// Importing the [`Dmabuf`] to gbm failed
|
||||
#[error("failed to import the dmabuf to gbm")]
|
||||
Import(std::io::Error),
|
||||
/// Failed to add a framebuffer for the bo
|
||||
#[error("failed to add a framebuffer for the bo")]
|
||||
Drm(AccessError),
|
||||
}
|
||||
|
||||
/// Attach a framebuffer for a [`Dmabuf`]
|
||||
///
|
||||
/// This tries to import the [`Dmabuf`] using gbm and attach
|
||||
/// a [`framebuffer::Handle`] for the imported [`BufferObject`][gbm::BufferObject]
|
||||
#[profiling::function]
|
||||
pub fn framebuffer_from_dmabuf<A: AsFd + 'static>(
|
||||
drm: &DrmDeviceFd,
|
||||
gbm: &gbm::Device<A>,
|
||||
dmabuf: &Dmabuf,
|
||||
use_opaque: bool,
|
||||
allow_legacy: bool,
|
||||
) -> Result<GbmFramebuffer, Error> {
|
||||
let bo: GbmBuffer = dmabuf
|
||||
.import_to(gbm, gbm::BufferObjectFlags::SCANOUT)
|
||||
.map_err(Error::Import)?;
|
||||
|
||||
// We override the offsets and pitches here cause the imported bo
|
||||
// can return the wrong values. bo will only return the correct values
|
||||
// for buffers we have allocated, but not for all client provided buffers.
|
||||
let mut offsets: [u32; 4] = [0; 4];
|
||||
let mut pitches: [u32; 4] = [0; 4];
|
||||
|
||||
for (index, offset) in dmabuf.offsets().enumerate() {
|
||||
offsets[index] = offset;
|
||||
}
|
||||
|
||||
for (index, stride) in dmabuf.strides().enumerate() {
|
||||
pitches[index] = stride;
|
||||
}
|
||||
|
||||
framebuffer_from_bo_internal(
|
||||
drm,
|
||||
BufferObjectInternal {
|
||||
bo: &bo,
|
||||
offsets: Some(offsets),
|
||||
pitches: Some(pitches),
|
||||
},
|
||||
use_opaque,
|
||||
allow_legacy,
|
||||
)
|
||||
.map_err(Error::Drm)
|
||||
.map(|(fb, format)| GbmFramebuffer {
|
||||
fb,
|
||||
format,
|
||||
drm: drm.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Attach a [`framebuffer::Handle`] to an [`BufferObject`][gbm::BufferObject]
|
||||
#[profiling::function]
|
||||
pub fn framebuffer_from_bo(
|
||||
drm: &DrmDeviceFd,
|
||||
bo: &GbmBuffer,
|
||||
use_opaque: bool,
|
||||
) -> Result<GbmFramebuffer, AccessError> {
|
||||
framebuffer_from_bo_internal(
|
||||
drm,
|
||||
BufferObjectInternal {
|
||||
bo,
|
||||
offsets: None,
|
||||
pitches: None,
|
||||
},
|
||||
use_opaque,
|
||||
true,
|
||||
)
|
||||
.map(|(fb, format)| GbmFramebuffer {
|
||||
fb,
|
||||
format,
|
||||
drm: drm.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
struct BufferObjectInternal<'a> {
|
||||
bo: &'a GbmBuffer,
|
||||
pitches: Option<[u32; 4]>,
|
||||
offsets: Option<[u32; 4]>,
|
||||
}
|
||||
|
||||
impl std::ops::Deref for BufferObjectInternal<'_> {
|
||||
type Target = GbmBuffer;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.bo
|
||||
}
|
||||
}
|
||||
|
||||
impl PlanarBuffer for BufferObjectInternal<'_> {
|
||||
#[inline]
|
||||
fn size(&self) -> (u32, u32) {
|
||||
PlanarBuffer::size(self.bo)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn format(&self) -> drm_fourcc::DrmFourcc {
|
||||
PlanarBuffer::format(self.bo)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn modifier(&self) -> Option<DrmModifier> {
|
||||
PlanarBuffer::modifier(self.bo)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pitches(&self) -> [u32; 4] {
|
||||
self.pitches.unwrap_or_else(|| PlanarBuffer::pitches(self.bo))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn handles(&self) -> [Option<drm::buffer::Handle>; 4] {
|
||||
PlanarBuffer::handles(self.bo)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn offsets(&self) -> [u32; 4] {
|
||||
self.offsets.unwrap_or_else(|| PlanarBuffer::offsets(self.bo))
|
||||
}
|
||||
}
|
||||
|
||||
struct OpaqueBufferWrapper<'a, B>(&'a B);
|
||||
impl<B> PlanarBuffer for OpaqueBufferWrapper<'_, B>
|
||||
where
|
||||
B: PlanarBuffer,
|
||||
{
|
||||
#[inline]
|
||||
fn size(&self) -> (u32, u32) {
|
||||
self.0.size()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn format(&self) -> Fourcc {
|
||||
let fmt = self.0.format();
|
||||
get_opaque(fmt).unwrap_or(fmt)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn modifier(&self) -> Option<DrmModifier> {
|
||||
self.0.modifier()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn pitches(&self) -> [u32; 4] {
|
||||
self.0.pitches()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn handles(&self) -> [Option<drm::buffer::Handle>; 4] {
|
||||
self.0.handles()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn offsets(&self) -> [u32; 4] {
|
||||
self.0.offsets()
|
||||
}
|
||||
}
|
||||
|
||||
#[profiling::function]
|
||||
#[inline]
|
||||
fn framebuffer_from_bo_internal<D>(
|
||||
drm: &D,
|
||||
bo: BufferObjectInternal<'_>,
|
||||
use_opaque: bool,
|
||||
allow_legacy: bool,
|
||||
) -> Result<(framebuffer::Handle, drm_fourcc::DrmFormat), AccessError>
|
||||
where
|
||||
D: drm::control::Device + DevPath,
|
||||
{
|
||||
let modifier = bo.modifier();
|
||||
let flags = if bo.modifier().is_some() {
|
||||
FbCmd2Flags::MODIFIERS
|
||||
} else {
|
||||
FbCmd2Flags::empty()
|
||||
};
|
||||
|
||||
let ret = if use_opaque {
|
||||
let opaque_wrapper = OpaqueBufferWrapper(&bo);
|
||||
drm.add_planar_framebuffer(&opaque_wrapper, flags).map(|fb| {
|
||||
(
|
||||
fb,
|
||||
drm_fourcc::DrmFormat {
|
||||
code: opaque_wrapper.format(),
|
||||
modifier: modifier.unwrap_or(DrmModifier::Invalid),
|
||||
},
|
||||
)
|
||||
})
|
||||
} else {
|
||||
drm.add_planar_framebuffer(&bo, flags).map(|fb| {
|
||||
(
|
||||
fb,
|
||||
drm_fourcc::DrmFormat {
|
||||
code: bo.format(),
|
||||
modifier: modifier.unwrap_or(DrmModifier::Invalid),
|
||||
},
|
||||
)
|
||||
})
|
||||
};
|
||||
|
||||
let (fb, format) = match ret {
|
||||
Ok(fb) => fb,
|
||||
Err(source) => {
|
||||
if !allow_legacy {
|
||||
return Err(AccessError {
|
||||
errmsg: "Failed to add framebuffer",
|
||||
dev: drm.dev_path(),
|
||||
source,
|
||||
});
|
||||
}
|
||||
|
||||
// We only support this as a fallback of last resort like xf86-video-modesetting does.
|
||||
warn_legacy_fb_export();
|
||||
|
||||
if bo.plane_count() > 1 {
|
||||
return Err(AccessError {
|
||||
errmsg: "Failed to add framebuffer",
|
||||
dev: drm.dev_path(),
|
||||
source,
|
||||
});
|
||||
}
|
||||
|
||||
let fourcc = bo.format();
|
||||
let (depth, bpp) = get_depth(fourcc)
|
||||
.zip(get_bpp(fourcc))
|
||||
.ok_or_else(|| AccessError {
|
||||
errmsg: "Unknown format for legacy framebuffer",
|
||||
dev: drm.dev_path(),
|
||||
source,
|
||||
})?;
|
||||
|
||||
let fb = drm
|
||||
.add_framebuffer(&*bo, depth as u32, bpp as u32)
|
||||
.map_err(|source| AccessError {
|
||||
errmsg: "Failed to add framebuffer",
|
||||
dev: drm.dev_path(),
|
||||
source,
|
||||
})?;
|
||||
(
|
||||
fb,
|
||||
drm_fourcc::DrmFormat {
|
||||
code: fourcc,
|
||||
modifier: drm_fourcc::DrmModifier::Invalid,
|
||||
},
|
||||
)
|
||||
}
|
||||
};
|
||||
Ok((fb, format))
|
||||
}
|
||||
+517
@@ -0,0 +1,517 @@
|
||||
//! This module represents abstraction on top the linux direct rendering manager api (drm).
|
||||
//!
|
||||
//! ## [`DrmDevice`]
|
||||
//!
|
||||
//! A device exposes certain properties, which are directly derived
|
||||
//! from the *device* as perceived by the direct rendering manager api (drm). These resources consists
|
||||
//! out of:
|
||||
//! - [`connectors`](drm::control::connector) represents a port on your computer, possibly with a connected monitor, TV, capture card, etc.
|
||||
//! - [`encoder`](drm::control::encoder) encodes the data of connected crtcs into a video signal for a fixed set of connectors.
|
||||
//! E.g. you might have an analog encoder based on a DAG for VGA ports, but another one for digital ones.
|
||||
//! Also not every encoder might be connected to every crtc.
|
||||
//! - [`framebuffer`] represents a buffer you may display, see `DrmSurface` below.
|
||||
//! - [`plane`] adds another layer on top of the crtcs, which allow us to layer multiple images on top of each other more efficiently
|
||||
//! then by combining the rendered images in the rendering phase, e.g. via OpenGL. Planes have to be explicitly used by the user to be useful.
|
||||
//! Every device has at least one primary plane used to display an image to the whole crtc. Additionally cursor and overlay planes may be present.
|
||||
//! Cursor planes are usually very restricted in size and meant to be used for hardware cursors, while overlay planes may
|
||||
//! be used for performance reasons to display any overlay on top of the image, e.g. the top-most windows.
|
||||
//! Overlay planes may have a bunch of weird limitation, that you cannot query, e.g. only working on round pixel coordinates.
|
||||
//! You code should never rely on a fixed set of overlay planes, but always have a fallback solution in place.
|
||||
//! - [`crtc`]s represent scanout engines of the device pointer to one framebuffer.
|
||||
//! Their responsibility is to read the data of the framebuffer and export it into an "Encoder".
|
||||
//! The number of crtc's represent the number of independent output devices the hardware may handle.
|
||||
//!
|
||||
//! On modern graphic cards it is better to think about the `crtc` as some sort of rendering engine.
|
||||
//! You can only have so many different pictures, you may display, as you have `crtc`s, but a single image
|
||||
//! may be put onto multiple displays.
|
||||
//!
|
||||
//! The main functionality of a `Device` in smithay is to give access to all these properties for the user to
|
||||
//! choose an appropriate rendering configuration. What that means is defined by the requirements and constraints documented
|
||||
//! in the specific device implementations. The second functionality is the creation of a `Surface`.
|
||||
//! Surface creation requires a `crtc` (which cannot be the same as another existing `Surface`'s crtc), a plane,
|
||||
//! as well as a `Mode` and a set of `connectors`.
|
||||
//!
|
||||
//! smithay does not make sure that `connectors` are not already in use by another `Surface`. Overlapping `connector`-Sets may
|
||||
//! be an error or result in undefined rendering behavior depending on the `Surface` implementation.
|
||||
//!
|
||||
//! ## [`DrmSurface`]
|
||||
//!
|
||||
//! A surface is a part of a `Device` that may output a picture to a number of connectors. It pumps pictures of buffers to outputs.
|
||||
//!
|
||||
//! On surface creation a matching encoder for your `connector` is automatically selected, if one exists(!),
|
||||
//! which means you still need to check your configuration beforehand, even if you do not need to provide an encoder.
|
||||
//!
|
||||
//! A surface consists of one `crtc` that is rendered to by the user. This is fixed for the `Surface`s lifetime and cannot be changed.
|
||||
//! A surface also always needs at least one connector to output the resulting image to as well as a `Mode` that is valid for the given connectors.
|
||||
//!
|
||||
//! The state of a `Surface` is double-buffered, meaning all operations that chance the set of `connector`s or their `Mode` are stored and
|
||||
//! only applied on the next commit. `Surface`s do their best to validate these changes, if possible.
|
||||
//!
|
||||
//! A commit/page_flip may be triggered to apply the pending state.
|
||||
//!
|
||||
//! ## Rendering
|
||||
//!
|
||||
//! The drm infrastructure makes no assumptions about the used renderer and does not interface with them directly.
|
||||
//! It just provides a way to create framebuffers from various buffer types (mainly `DumbBuffer`s and hardware-backed gbm `BufferObject`s).
|
||||
//!
|
||||
//! Buffer management and details about the various types can be found in the [`allocator`-Module](crate::backend::allocator) and
|
||||
//! rendering abstractions, which can target these buffers can be found in the [`renderer`-Module](crate::backend::renderer).
|
||||
//!
|
||||
//! ### Hardware composition
|
||||
//!
|
||||
//! The [`DrmCompositor`](crate::backend::drm::compositor::DrmCompositor) provides a simplified way to utilize drm planes for
|
||||
//! using hardware composition.
|
||||
//! See the [`compositor`] module docs for more information on that topic.
|
||||
//!
|
||||
//! ## [`DrmNode`]
|
||||
//!
|
||||
//! A drm node refers to a drm device and the capabilities that may be performed using the node.
|
||||
//! Generally [`DrmNode`] is primarily used by clients (such as the output backends) which need
|
||||
//! to allocate buffers for use in X11 or Wayland. If you need to do mode setting, you should use
|
||||
//! [`DrmDevice`] instead.
|
||||
|
||||
#[cfg(all(feature = "wayland_frontend", feature = "backend_gbm"))]
|
||||
pub mod compositor;
|
||||
pub(crate) mod device;
|
||||
#[cfg(feature = "backend_drm")]
|
||||
pub mod dumb;
|
||||
mod error;
|
||||
pub mod exporter;
|
||||
#[cfg(feature = "backend_gbm")]
|
||||
pub mod gbm;
|
||||
#[cfg(all(feature = "wayland_frontend", feature = "backend_gbm"))]
|
||||
pub mod output;
|
||||
|
||||
mod surface;
|
||||
|
||||
use std::sync::Once;
|
||||
|
||||
use crate::utils::{DevPath, Physical, Size};
|
||||
pub use device::{
|
||||
DrmDevice, DrmDeviceFd, DrmDeviceNotifier, DrmEvent, EventMetadata as DrmEventMetadata, PlaneClaim,
|
||||
Time as DrmEventTime, WeakDrmDeviceFd,
|
||||
};
|
||||
pub use drm::node::{CreateDrmNodeError, DrmNode, NodeType};
|
||||
use drm_fourcc::{DrmFormat, DrmFourcc, DrmModifier};
|
||||
pub use error::AccessError as DrmAccessError;
|
||||
pub use error::Error as DrmError;
|
||||
use indexmap::IndexSet;
|
||||
#[cfg(feature = "backend_gbm")]
|
||||
pub use surface::gbm::{Error as GbmBufferedSurfaceError, GbmBufferedSurface};
|
||||
pub use surface::{DrmSurface, PlaneConfig, PlaneDamageClips, PlaneState, VrrSupport};
|
||||
|
||||
use drm::{
|
||||
DriverCapability,
|
||||
control::{Device as ControlDevice, PlaneType, crtc, framebuffer, plane},
|
||||
};
|
||||
use tracing::trace;
|
||||
|
||||
use self::error::AccessError;
|
||||
|
||||
use super::allocator::format::FormatSet;
|
||||
|
||||
fn warn_legacy_fb_export() {
|
||||
static WARN_LEGACY_FB_EXPORT: Once = Once::new();
|
||||
WARN_LEGACY_FB_EXPORT.call_once(|| {
|
||||
tracing::warn!("using legacy fbadd");
|
||||
});
|
||||
}
|
||||
|
||||
/// Common framebuffer operations
|
||||
pub trait Framebuffer: AsRef<framebuffer::Handle> {
|
||||
/// Retrieve the format of the framebuffer
|
||||
fn format(&self) -> drm_fourcc::DrmFormat;
|
||||
}
|
||||
|
||||
/// A set of planes as supported by a crtc
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Planes {
|
||||
/// The primary plane(s) of the crtc
|
||||
pub primary: Vec<PlaneInfo>,
|
||||
/// The cursor plane(s) of the crtc, if available
|
||||
pub cursor: Vec<PlaneInfo>,
|
||||
/// Overlay planes supported by the crtc, if available
|
||||
pub overlay: Vec<PlaneInfo>,
|
||||
}
|
||||
|
||||
/// Info about a single plane
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlaneInfo {
|
||||
/// Handle of the plane
|
||||
pub handle: plane::Handle,
|
||||
/// Type of the plane
|
||||
pub type_: PlaneType,
|
||||
/// z-position of the plane if available
|
||||
pub zpos: Option<i32>,
|
||||
/// Formats supported by this plane
|
||||
pub formats: FormatSet,
|
||||
/// Recommended plane size in order of preference
|
||||
pub size_hints: Option<Vec<Size<u16, Physical>>>,
|
||||
}
|
||||
|
||||
fn planes(
|
||||
dev: &(impl DevPath + ControlDevice),
|
||||
crtc: &crtc::Handle,
|
||||
has_universal_planes: bool,
|
||||
) -> Result<Planes, DrmError> {
|
||||
let mut primary = Vec::with_capacity(1);
|
||||
let mut cursor = Vec::new();
|
||||
let mut overlay = Vec::new();
|
||||
|
||||
let planes = dev.plane_handles().map_err(|source| {
|
||||
DrmError::Access(AccessError {
|
||||
errmsg: "Error loading plane handles",
|
||||
dev: dev.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
|
||||
let resources = dev.resource_handles().map_err(|source| {
|
||||
DrmError::Access(AccessError {
|
||||
errmsg: "Error loading resource handles",
|
||||
dev: dev.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
|
||||
for plane in planes {
|
||||
let info = dev.get_plane(plane).map_err(|source| {
|
||||
DrmError::Access(AccessError {
|
||||
errmsg: "Failed to get plane information",
|
||||
dev: dev.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
let filter = info.possible_crtcs();
|
||||
if resources.filter_crtcs(filter).contains(crtc) {
|
||||
let zpos = plane_zpos(dev, plane).ok().flatten();
|
||||
let type_ = plane_type(dev, plane)?;
|
||||
let formats = plane_formats(dev, plane)?;
|
||||
let size_hints = plane_size_hints(dev, plane)?;
|
||||
let plane_info = PlaneInfo {
|
||||
handle: plane,
|
||||
type_,
|
||||
zpos,
|
||||
formats,
|
||||
size_hints,
|
||||
};
|
||||
match type_ {
|
||||
PlaneType::Primary => {
|
||||
primary.push(plane_info);
|
||||
}
|
||||
PlaneType::Cursor => {
|
||||
cursor.push(plane_info);
|
||||
}
|
||||
PlaneType::Overlay => {
|
||||
overlay.push(plane_info);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Planes {
|
||||
primary,
|
||||
cursor: if has_universal_planes { cursor } else { Vec::new() },
|
||||
overlay: if has_universal_planes { overlay } else { Vec::new() },
|
||||
})
|
||||
}
|
||||
|
||||
fn plane_type(dev: &(impl ControlDevice + DevPath), plane: plane::Handle) -> Result<PlaneType, DrmError> {
|
||||
let props = dev.get_properties(plane).map_err(|source| {
|
||||
DrmError::Access(AccessError {
|
||||
errmsg: "Failed to get properties of plane",
|
||||
dev: dev.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
let (ids, vals) = props.as_props_and_values();
|
||||
for (&id, &val) in ids.iter().zip(vals.iter()) {
|
||||
let info = dev.get_property(id).map_err(|source| {
|
||||
DrmError::Access(AccessError {
|
||||
errmsg: "Failed to get property info",
|
||||
dev: dev.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
if info.name().to_str().map(|x| x == "type").unwrap_or(false) {
|
||||
return Ok(match val {
|
||||
x if x == (PlaneType::Primary as u64) => PlaneType::Primary,
|
||||
x if x == (PlaneType::Cursor as u64) => PlaneType::Cursor,
|
||||
_ => PlaneType::Overlay,
|
||||
});
|
||||
}
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
fn plane_zpos(dev: &(impl ControlDevice + DevPath), plane: plane::Handle) -> Result<Option<i32>, DrmError> {
|
||||
let props = dev.get_properties(plane).map_err(|source| {
|
||||
DrmError::Access(AccessError {
|
||||
errmsg: "Failed to get properties of plane",
|
||||
dev: dev.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
let (ids, vals) = props.as_props_and_values();
|
||||
for (&id, &val) in ids.iter().zip(vals.iter()) {
|
||||
let info = dev.get_property(id).map_err(|source| {
|
||||
DrmError::Access(AccessError {
|
||||
errmsg: "Failed to get property info",
|
||||
dev: dev.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
if info.name().to_str().map(|x| x == "zpos").unwrap_or(false) {
|
||||
let plane_zpos = match info.value_type().convert_value(val) {
|
||||
drm::control::property::Value::UnsignedRange(u) => Some(u as i32),
|
||||
drm::control::property::Value::SignedRange(i) => Some(i as i32),
|
||||
// A range from [0,1] will be interpreted as Boolean in drm-rs
|
||||
// TODO: Once that has been changed we can remove this special handling here
|
||||
drm::control::property::Value::Boolean(b) => Some(b.into()),
|
||||
_ => None,
|
||||
};
|
||||
return Ok(plane_zpos);
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn plane_formats(dev: &(impl ControlDevice + DevPath), plane: plane::Handle) -> Result<FormatSet, DrmError> {
|
||||
// get plane formats
|
||||
let plane_info = dev.get_plane(plane).map_err(|source| {
|
||||
DrmError::Access(AccessError {
|
||||
errmsg: "Error loading plane info",
|
||||
dev: dev.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
let mut formats = IndexSet::new();
|
||||
for code in plane_info
|
||||
.formats()
|
||||
.iter()
|
||||
.flat_map(|x| DrmFourcc::try_from(*x).ok())
|
||||
{
|
||||
formats.insert(DrmFormat {
|
||||
code,
|
||||
modifier: DrmModifier::Invalid,
|
||||
});
|
||||
}
|
||||
|
||||
if let Ok(1) = dev.get_driver_capability(DriverCapability::AddFB2Modifiers) {
|
||||
let set = dev.get_properties(plane).map_err(|source| {
|
||||
DrmError::Access(AccessError {
|
||||
errmsg: "Failed to query properties",
|
||||
dev: dev.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
let (handles, _) = set.as_props_and_values();
|
||||
// for every handle ...
|
||||
let prop = handles
|
||||
.iter()
|
||||
.find(|handle| {
|
||||
// get information of that property
|
||||
if let Ok(info) = dev.get_property(**handle) {
|
||||
// to find out, if we got the handle of the "IN_FORMATS" property ...
|
||||
if info.name().to_str().map(|x| x == "IN_FORMATS").unwrap_or(false) {
|
||||
// so we can use that to get formats
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
})
|
||||
.copied();
|
||||
if let Some(prop) = prop {
|
||||
let prop_info = dev.get_property(prop).map_err(|source| {
|
||||
DrmError::Access(AccessError {
|
||||
errmsg: "Failed to query property",
|
||||
dev: dev.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
let (handles, raw_values) = set.as_props_and_values();
|
||||
let raw_value = raw_values[handles
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(i, handle)| if *handle == prop { Some(i) } else { None })
|
||||
.unwrap()];
|
||||
if let drm::control::property::Value::Blob(blob) = prop_info.value_type().convert_value(raw_value)
|
||||
{
|
||||
let data = dev.get_property_blob(blob).map_err(|source| {
|
||||
DrmError::Access(AccessError {
|
||||
errmsg: "Failed to query property blob data",
|
||||
dev: dev.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
// be careful here, we have no idea about the alignment inside the blob, so always copy using `read_unaligned`,
|
||||
// although slice::from_raw_parts would be so much nicer to iterate and to read.
|
||||
unsafe {
|
||||
let fmt_mod_blob_ptr = data.as_ptr() as *const drm_ffi::drm_format_modifier_blob;
|
||||
let fmt_mod_blob = &*fmt_mod_blob_ptr;
|
||||
|
||||
let formats_ptr: *const u32 = fmt_mod_blob_ptr
|
||||
.cast::<u8>()
|
||||
.offset(fmt_mod_blob.formats_offset as isize)
|
||||
as *const _;
|
||||
let modifiers_ptr: *const drm_ffi::drm_format_modifier = fmt_mod_blob_ptr
|
||||
.cast::<u8>()
|
||||
.offset(fmt_mod_blob.modifiers_offset as isize)
|
||||
as *const _;
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
let formats_ptr = formats_ptr as *const u32;
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
let modifiers_ptr = modifiers_ptr as *const drm_ffi::drm_format_modifier;
|
||||
|
||||
for i in 0..fmt_mod_blob.count_modifiers {
|
||||
let mod_info = modifiers_ptr.offset(i as isize).read_unaligned();
|
||||
for j in 0..64 {
|
||||
if mod_info.formats & (1u64 << j) != 0 {
|
||||
let code = DrmFourcc::try_from(
|
||||
formats_ptr
|
||||
.offset((j + mod_info.offset) as isize)
|
||||
.read_unaligned(),
|
||||
)
|
||||
.ok();
|
||||
let modifier = DrmModifier::from(mod_info.modifier);
|
||||
if let Some(code) = code {
|
||||
formats.insert(DrmFormat { code, modifier });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if plane_type(dev, plane)? == PlaneType::Cursor {
|
||||
// Force a LINEAR layout for the cursor if the driver doesn't support modifiers
|
||||
for format in formats.clone() {
|
||||
formats.insert(DrmFormat {
|
||||
code: format.code,
|
||||
modifier: DrmModifier::Linear,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if formats.is_empty() {
|
||||
formats.insert(DrmFormat {
|
||||
code: DrmFourcc::Argb8888,
|
||||
modifier: DrmModifier::Invalid,
|
||||
});
|
||||
}
|
||||
|
||||
trace!(
|
||||
"Supported scan-out formats for plane ({:?}): {:?}",
|
||||
plane, formats
|
||||
);
|
||||
|
||||
Ok(FormatSet::from_formats(formats))
|
||||
}
|
||||
|
||||
#[cfg(feature = "backend_gbm")]
|
||||
fn plane_has_property(
|
||||
dev: &(impl drm::control::Device + DevPath),
|
||||
plane: plane::Handle,
|
||||
name: &str,
|
||||
) -> Result<bool, DrmError> {
|
||||
let props = dev.get_properties(plane).map_err(|source| {
|
||||
DrmError::Access(AccessError {
|
||||
errmsg: "Failed to get properties of plane",
|
||||
dev: dev.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
let (ids, _) = props.as_props_and_values();
|
||||
for &id in ids {
|
||||
let info = dev.get_property(id).map_err(|source| {
|
||||
DrmError::Access(AccessError {
|
||||
errmsg: "Failed to get property info",
|
||||
dev: dev.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
if info.name().to_str().map(|x| x == name).unwrap_or(false) {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
// FIXME: use definition from drm_ffi once available
|
||||
struct drm_plane_size_hint {
|
||||
width: u16,
|
||||
height: u16,
|
||||
}
|
||||
|
||||
fn plane_size_hints(
|
||||
dev: &(impl ControlDevice + DevPath),
|
||||
plane: plane::Handle,
|
||||
) -> Result<Option<Vec<Size<u16, Physical>>>, DrmError> {
|
||||
let props = dev.get_properties(plane).map_err(|source| {
|
||||
DrmError::Access(AccessError {
|
||||
errmsg: "Failed to get properties of plane",
|
||||
dev: dev.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
let (ids, vals) = props.as_props_and_values();
|
||||
for (&id, &val) in ids.iter().zip(vals.iter()) {
|
||||
let info = dev.get_property(id).map_err(|source| {
|
||||
DrmError::Access(AccessError {
|
||||
errmsg: "Failed to get property info",
|
||||
dev: dev.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
if info.name().to_str().map(|x| x == "SIZE_HINTS").unwrap_or(false) {
|
||||
let size_hints = if let drm::control::property::Value::Blob(blob_id) =
|
||||
info.value_type().convert_value(val)
|
||||
{
|
||||
// Note that property value 0 (ie. no blob) is reserved for potential
|
||||
// future use. Current userspace is expected to ignore the property
|
||||
// if the value is 0
|
||||
if blob_id == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let blob_info = drm_ffi::mode::get_property_blob(dev.as_fd(), blob_id as u32, None).map_err(
|
||||
|source| {
|
||||
DrmError::Access(AccessError {
|
||||
errmsg: "Failed to get SIZE_HINTS blob info",
|
||||
dev: dev.dev_path(),
|
||||
source,
|
||||
})
|
||||
},
|
||||
)?;
|
||||
|
||||
let mut data = Vec::with_capacity(blob_info.length as usize);
|
||||
drm_ffi::mode::get_property_blob(dev.as_fd(), blob_id as u32, Some(&mut data)).map_err(
|
||||
|source| {
|
||||
DrmError::Access(AccessError {
|
||||
errmsg: "Failed to get SIZE_HINTS blob data",
|
||||
dev: dev.dev_path(),
|
||||
source,
|
||||
})
|
||||
},
|
||||
)?;
|
||||
|
||||
let num_size_hints = data.len() / std::mem::size_of::<drm_plane_size_hint>();
|
||||
let size_hints = unsafe {
|
||||
std::slice::from_raw_parts(data.as_ptr() as *const drm_plane_size_hint, num_size_hints)
|
||||
};
|
||||
Some(
|
||||
size_hints
|
||||
.iter()
|
||||
.map(|size_hint| Size::from((size_hint.width, size_hint.height)))
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
tracing::debug!(?plane, "SIZE_HINTS property has wrong value type");
|
||||
None
|
||||
};
|
||||
return Ok(size_hints);
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
+1030
File diff suppressed because it is too large.
Load diff
+1677
File diff suppressed because it is too large.
Load diff
+582
@@ -0,0 +1,582 @@
|
||||
use std::os::unix::io::AsFd;
|
||||
use std::sync::Arc;
|
||||
|
||||
use drm::control::{Mode, connector, crtc, plane};
|
||||
use drm::{Device, DriverCapability};
|
||||
use indexmap::IndexSet;
|
||||
|
||||
use crate::backend::SwapBuffersError;
|
||||
use crate::backend::allocator::dmabuf::{AsDmabuf, Dmabuf};
|
||||
use crate::backend::allocator::format::get_opaque;
|
||||
use crate::backend::allocator::gbm::{GbmBuffer, GbmConvertError};
|
||||
use crate::backend::allocator::{Allocator, Format, Fourcc, Modifier, Slot, Swapchain};
|
||||
use crate::backend::drm::error::AccessError;
|
||||
use crate::backend::drm::gbm::{GbmFramebuffer, framebuffer_from_bo};
|
||||
use crate::backend::drm::{DrmError, DrmSurface, plane_has_property};
|
||||
use crate::backend::renderer::sync::SyncPoint;
|
||||
use crate::utils::{DevPath, Physical, Rectangle, Transform};
|
||||
|
||||
use tracing::{debug, info_span, instrument, trace, warn};
|
||||
|
||||
use super::{PlaneConfig, PlaneDamageClips, PlaneState, VrrSupport};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct QueuedFb<U> {
|
||||
slot: Slot<GbmBuffer>,
|
||||
sync: Option<SyncPoint>,
|
||||
damage: Option<Vec<Rectangle<i32, Physical>>>,
|
||||
user_data: U,
|
||||
}
|
||||
|
||||
/// Simplified abstraction of a swapchain for gbm-buffers displayed on a [`DrmSurface`].
|
||||
#[derive(Debug)]
|
||||
pub struct GbmBufferedSurface<A: Allocator<Buffer = GbmBuffer> + 'static, U> {
|
||||
current_fb: Slot<GbmBuffer>,
|
||||
pending_fb: Option<(Slot<GbmBuffer>, U)>,
|
||||
queued_fb: Option<QueuedFb<U>>,
|
||||
next_fb: Option<Slot<GbmBuffer>>,
|
||||
swapchain: Swapchain<A>,
|
||||
drm: Arc<DrmSurface>,
|
||||
is_opaque: bool,
|
||||
supports_fencing: bool,
|
||||
span: tracing::Span,
|
||||
}
|
||||
|
||||
impl<A, U> GbmBufferedSurface<A, U>
|
||||
where
|
||||
A: Allocator<Buffer = GbmBuffer>,
|
||||
A::Error: std::error::Error + Send + Sync,
|
||||
{
|
||||
/// Create a new `GbmBufferedSurface` from a given compatible combination
|
||||
/// of a surface, an allocator and renderer formats.
|
||||
///
|
||||
/// The provided color_formats are tested in order until a working configuration is found.
|
||||
///
|
||||
/// To successfully call this function, you need to have a renderer,
|
||||
/// which can render into a Dmabuf, and a gbm allocator that can produce
|
||||
/// buffers of a supported format for rendering.
|
||||
pub fn new(
|
||||
drm: DrmSurface,
|
||||
mut allocator: A,
|
||||
color_formats: &[Fourcc],
|
||||
renderer_formats: impl IntoIterator<Item = Format>,
|
||||
) -> Result<GbmBufferedSurface<A, U>, Error<A::Error>> {
|
||||
let span = info_span!(parent: drm.span(), "drm_gbm");
|
||||
let _guard = span.enter();
|
||||
|
||||
let mut error = None;
|
||||
let drm = Arc::new(drm);
|
||||
let renderer_formats = renderer_formats.into_iter().collect::<Vec<_>>();
|
||||
|
||||
for format in color_formats {
|
||||
debug!("Testing color format: {}", format);
|
||||
match Self::new_internal(drm.clone(), allocator, renderer_formats.clone(), *format) {
|
||||
Ok((current_fb, swapchain, is_opaque)) => {
|
||||
drop(_guard);
|
||||
let supports_fencing = !drm.is_legacy()
|
||||
&& drm
|
||||
.get_driver_capability(DriverCapability::SyncObj)
|
||||
.map(|val| val != 0)
|
||||
.map_err(|err| {
|
||||
Error::DrmError(DrmError::Access(AccessError {
|
||||
errmsg: "Failed to query driver capability",
|
||||
dev: drm.dev_path(),
|
||||
source: err,
|
||||
}))
|
||||
})?
|
||||
&& plane_has_property(&*drm, drm.plane(), "IN_FENCE_FD")?;
|
||||
|
||||
return Ok(GbmBufferedSurface {
|
||||
current_fb,
|
||||
pending_fb: None,
|
||||
queued_fb: None,
|
||||
next_fb: None,
|
||||
swapchain,
|
||||
drm,
|
||||
is_opaque,
|
||||
supports_fencing,
|
||||
span,
|
||||
});
|
||||
}
|
||||
Err((alloc, err)) => {
|
||||
warn!("Preferred format {} not available: {:?}", format, err);
|
||||
allocator = alloc;
|
||||
error = Some(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(error.unwrap())
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn new_internal(
|
||||
drm: Arc<DrmSurface>,
|
||||
allocator: A,
|
||||
mut renderer_formats: Vec<Format>,
|
||||
code: Fourcc,
|
||||
) -> Result<(Slot<GbmBuffer>, Swapchain<A>, bool), (A, Error<A::Error>)> {
|
||||
// select a format
|
||||
let mut plane_formats = drm.plane_info().formats.iter().copied().collect::<IndexSet<_>>();
|
||||
let opaque_code = get_opaque(code).unwrap_or(code);
|
||||
if !plane_formats
|
||||
.iter()
|
||||
.any(|fmt| fmt.code == code || fmt.code == opaque_code)
|
||||
{
|
||||
return Err((allocator, Error::NoSupportedPlaneFormat));
|
||||
}
|
||||
plane_formats.retain(|fmt| fmt.code == code || fmt.code == opaque_code);
|
||||
renderer_formats.retain(|fmt| fmt.code == code);
|
||||
|
||||
let plane_modifiers = plane_formats
|
||||
.iter()
|
||||
.map(|fmt| fmt.modifier)
|
||||
.collect::<IndexSet<_>>();
|
||||
let renderer_modifiers = renderer_formats
|
||||
.iter()
|
||||
.map(|fmt| fmt.modifier)
|
||||
.collect::<IndexSet<_>>();
|
||||
|
||||
trace!("Plane formats: {:?}", plane_formats);
|
||||
trace!("Renderer formats: {:?}", renderer_formats);
|
||||
debug!(
|
||||
"Remaining intersected modifiers: {:?}",
|
||||
plane_modifiers
|
||||
.intersection(&renderer_modifiers)
|
||||
.collect::<IndexSet<_>>()
|
||||
);
|
||||
|
||||
if plane_formats.is_empty() {
|
||||
return Err((allocator, Error::NoSupportedPlaneFormat));
|
||||
} else if renderer_formats.is_empty() {
|
||||
return Err((allocator, Error::NoSupportedRendererFormat));
|
||||
}
|
||||
|
||||
let formats = {
|
||||
// Special case: if a format supports explicit LINEAR (but no implicit Modifiers)
|
||||
// and the other doesn't support any modifier, force Implicit.
|
||||
// This should at least result in a working pipeline possibly with a linear buffer,
|
||||
// but we cannot be sure.
|
||||
if (plane_formats.len() == 1
|
||||
&& plane_formats.iter().next().unwrap().modifier == Modifier::Invalid
|
||||
&& renderer_formats.iter().all(|x| x.modifier != Modifier::Invalid)
|
||||
&& renderer_formats.iter().any(|x| x.modifier == Modifier::Linear))
|
||||
|| (renderer_formats.len() == 1
|
||||
&& renderer_formats.first().unwrap().modifier == Modifier::Invalid
|
||||
&& plane_formats.iter().all(|x| x.modifier != Modifier::Invalid)
|
||||
&& plane_formats.iter().any(|x| x.modifier == Modifier::Linear))
|
||||
{
|
||||
vec![Format {
|
||||
code,
|
||||
modifier: Modifier::Invalid,
|
||||
}]
|
||||
} else {
|
||||
plane_modifiers
|
||||
.intersection(&renderer_modifiers)
|
||||
.cloned()
|
||||
.map(|modifier| Format { code, modifier })
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
};
|
||||
debug!("Testing Formats: {:?}", formats);
|
||||
|
||||
let modifiers = formats.iter().map(|x| x.modifier).collect::<Vec<_>>();
|
||||
let mode = drm.pending_mode();
|
||||
|
||||
let mut swapchain: Swapchain<A> = Swapchain::new(
|
||||
allocator,
|
||||
mode.size().0 as u32,
|
||||
mode.size().1 as u32,
|
||||
code,
|
||||
modifiers,
|
||||
);
|
||||
|
||||
// Test format
|
||||
let buffer = match swapchain.acquire() {
|
||||
Ok(buffer) => buffer.unwrap(),
|
||||
Err(err) => return Err((swapchain.allocator, Error::GbmError(err))),
|
||||
};
|
||||
let format = Format {
|
||||
code,
|
||||
modifier: buffer.modifier(), // no guarantee
|
||||
// that this is stable across allocations, but
|
||||
// we want to print that here for debugging proposes.
|
||||
// It has no further use.
|
||||
};
|
||||
|
||||
let use_opaque = !plane_formats.iter().any(|f| f.code == code);
|
||||
let fb = match framebuffer_from_bo(drm.device_fd(), &buffer, use_opaque) {
|
||||
Ok(fb) => fb,
|
||||
Err(err) => return Err((swapchain.allocator, Error::DrmError(err.into()))),
|
||||
};
|
||||
match buffer.export() {
|
||||
Ok(dmabuf) => dmabuf,
|
||||
Err(err) => return Err((swapchain.allocator, err.into())),
|
||||
};
|
||||
buffer.userdata().insert_if_missing_threadsafe(|| fb);
|
||||
|
||||
let handle = buffer.userdata().get::<GbmFramebuffer>().unwrap();
|
||||
|
||||
let plane_state = PlaneState {
|
||||
handle: drm.plane(),
|
||||
config: Some(PlaneConfig {
|
||||
src: Rectangle::from_size((mode.size().0 as i32, mode.size().1 as i32).into()).to_f64(),
|
||||
dst: Rectangle::from_size((mode.size().0 as i32, mode.size().1 as i32).into()),
|
||||
alpha: 1.0,
|
||||
transform: Transform::Normal,
|
||||
damage_clips: None,
|
||||
fb: *handle.as_ref(),
|
||||
fence: None,
|
||||
}),
|
||||
};
|
||||
|
||||
match drm.test_state([plane_state], true) {
|
||||
Ok(_) => {
|
||||
debug!("Chosen format: {:?}", format);
|
||||
Ok((buffer, swapchain, use_opaque))
|
||||
}
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"Mode-setting failed with automatically selected buffer format {:?}: {}",
|
||||
format, err
|
||||
);
|
||||
Err((swapchain.allocator, err.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the next buffer to be rendered into and it's age.
|
||||
///
|
||||
/// *Note*: This function can be called multiple times and
|
||||
/// will return the same buffer until it is queued (see [`GbmBufferedSurface::queue_buffer`]).
|
||||
#[instrument(level = "trace", skip_all, parent = &self.span, err)]
|
||||
#[profiling::function]
|
||||
pub fn next_buffer(&mut self) -> Result<(Dmabuf, u8), Error<A::Error>> {
|
||||
if !self.drm.is_active() {
|
||||
return Err(Error::<A::Error>::DrmError(DrmError::DeviceInactive));
|
||||
}
|
||||
|
||||
if self.next_fb.is_none() {
|
||||
let slot = self
|
||||
.swapchain
|
||||
.acquire()
|
||||
.map_err(Error::GbmError)?
|
||||
.ok_or(Error::NoFreeSlotsError)?;
|
||||
|
||||
let maybe_buffer = slot.userdata().get::<GbmFramebuffer>();
|
||||
if maybe_buffer.is_none() {
|
||||
let fb = framebuffer_from_bo(self.drm.device_fd(), &slot, self.is_opaque)
|
||||
.map_err(|err| Error::DrmError(err.into()))?;
|
||||
slot.userdata().insert_if_missing_threadsafe(|| fb);
|
||||
}
|
||||
|
||||
self.next_fb = Some(slot);
|
||||
}
|
||||
|
||||
let slot = self.next_fb.as_ref().unwrap();
|
||||
Ok((slot.export()?, slot.age()))
|
||||
}
|
||||
|
||||
/// Queues the current buffer for rendering.
|
||||
///
|
||||
/// Returns [`Error::NoBuffer`] in case [`GbmBufferedSurface::next_buffer`] has not been called
|
||||
/// prior to this function.
|
||||
///
|
||||
/// *Note*: This function needs to be followed up with [`GbmBufferedSurface::frame_submitted`]
|
||||
/// when a vblank event is received, that denotes successful scanout of the buffer.
|
||||
/// Otherwise the underlying swapchain will eventually run out of buffers.
|
||||
///
|
||||
/// `user_data` can be used to attach some data to a specific buffer and later retrieved with [`GbmBufferedSurface::frame_submitted`]
|
||||
#[profiling::function]
|
||||
pub fn queue_buffer(
|
||||
&mut self,
|
||||
sync: Option<SyncPoint>,
|
||||
damage: Option<Vec<Rectangle<i32, Physical>>>,
|
||||
user_data: U,
|
||||
) -> Result<(), Error<A::Error>> {
|
||||
if !self.drm.is_active() {
|
||||
return Err(Error::<A::Error>::DrmError(DrmError::DeviceInactive));
|
||||
}
|
||||
|
||||
let next_fb = self.next_fb.take().ok_or(Error::<A::Error>::NoBuffer)?;
|
||||
|
||||
self.swapchain.submitted(&next_fb);
|
||||
|
||||
self.queued_fb = Some(QueuedFb {
|
||||
slot: next_fb,
|
||||
sync,
|
||||
damage,
|
||||
user_data,
|
||||
});
|
||||
if self.pending_fb.is_none() {
|
||||
self.submit()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Marks the current frame as submitted.
|
||||
///
|
||||
/// *Note*: Needs to be called, after the vblank event of the matching [`DrmDevice`](super::super::DrmDevice)
|
||||
/// was received after calling [`GbmBufferedSurface::queue_buffer`] on this surface.
|
||||
/// Otherwise the underlying swapchain will run out of buffers eventually.
|
||||
///
|
||||
/// Returns the user data that was stored with [`GbmBufferedSurface::queue_buffer`] if a buffer was pending, otherwise
|
||||
/// `None` is returned.
|
||||
#[profiling::function]
|
||||
pub fn frame_submitted(&mut self) -> Result<Option<U>, Error<A::Error>> {
|
||||
if let Some((mut pending, user_data)) = self.pending_fb.take() {
|
||||
std::mem::swap(&mut pending, &mut self.current_fb);
|
||||
if self.queued_fb.is_some() {
|
||||
self.submit()?;
|
||||
}
|
||||
Ok(Some(user_data))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
#[profiling::function]
|
||||
fn submit(&mut self) -> Result<(), Error<A::Error>> {
|
||||
// yes it does not look like it, but both of these lines should be safe in all cases.
|
||||
let QueuedFb {
|
||||
slot,
|
||||
sync,
|
||||
damage,
|
||||
user_data,
|
||||
} = self.queued_fb.take().unwrap();
|
||||
let handle = slot.userdata().get::<GbmFramebuffer>().unwrap();
|
||||
let mode = self.drm.pending_mode();
|
||||
let src = Rectangle::from_size((mode.size().0 as i32, mode.size().1 as i32).into()).to_f64();
|
||||
let dst = Rectangle::from_size((mode.size().0 as i32, mode.size().1 as i32).into());
|
||||
|
||||
let damage_clips = damage.and_then(|damage| {
|
||||
PlaneDamageClips::from_damage(
|
||||
self.drm.device_fd(),
|
||||
src,
|
||||
dst,
|
||||
Transform::Normal,
|
||||
Transform::Normal,
|
||||
damage,
|
||||
)
|
||||
.ok()
|
||||
.flatten()
|
||||
});
|
||||
|
||||
// Try to extract a native fence out of the supplied sync point if any
|
||||
// If the sync point has no native fence or the surface does not support
|
||||
// fencing force a wait
|
||||
let fence = if let Some(sync) = sync {
|
||||
if !self.supports_fencing {
|
||||
// we probably don't want to fail to submit in this case, lets hope on implicit sync
|
||||
let _ = sync.wait();
|
||||
None
|
||||
} else {
|
||||
let fence = sync.export();
|
||||
|
||||
if fence.is_none() {
|
||||
let _ = sync.wait();
|
||||
}
|
||||
|
||||
fence
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let plane_state = PlaneState {
|
||||
handle: self.plane(),
|
||||
config: Some(PlaneConfig {
|
||||
src,
|
||||
dst,
|
||||
transform: Transform::Normal,
|
||||
alpha: 1.0,
|
||||
damage_clips: damage_clips.as_ref().map(|d| d.blob()),
|
||||
fb: *handle.as_ref(),
|
||||
fence: fence.as_ref().map(|fence| fence.as_fd()),
|
||||
}),
|
||||
};
|
||||
|
||||
let flip = if self.drm.commit_pending() {
|
||||
self.drm.commit([plane_state], true)
|
||||
} else {
|
||||
self.drm.page_flip([plane_state], true)
|
||||
};
|
||||
if flip.is_ok() {
|
||||
self.pending_fb = Some((slot, user_data));
|
||||
}
|
||||
flip.map_err(Error::DrmError)
|
||||
}
|
||||
|
||||
/// Reset the underlying buffers
|
||||
pub fn reset_buffers(&mut self) {
|
||||
self.swapchain.reset_buffers()
|
||||
}
|
||||
|
||||
/// Reset the age for all buffers.
|
||||
///
|
||||
/// This can be used to efficiently clear the damage history without having to
|
||||
/// modify the damage for each surface.
|
||||
pub fn reset_buffer_ages(&mut self) {
|
||||
self.swapchain.reset_buffer_ages();
|
||||
}
|
||||
|
||||
/// Returns the underlying [`crtc`](drm::control::crtc) of this surface
|
||||
pub fn crtc(&self) -> crtc::Handle {
|
||||
self.drm.crtc()
|
||||
}
|
||||
|
||||
/// Returns the underlying [`plane`](drm::control::plane) of this surface
|
||||
pub fn plane(&self) -> plane::Handle {
|
||||
self.drm.plane()
|
||||
}
|
||||
|
||||
/// Currently used [`connector`](drm::control::connector)s of this `Surface`
|
||||
pub fn current_connectors(&self) -> impl IntoIterator<Item = connector::Handle> {
|
||||
self.drm.current_connectors()
|
||||
}
|
||||
|
||||
/// Returns the pending [`connector`](drm::control::connector)s
|
||||
/// used for the next frame queued via [`queue_buffer`](GbmBufferedSurface::queue_buffer).
|
||||
pub fn pending_connectors(&self) -> impl IntoIterator<Item = connector::Handle> {
|
||||
self.drm.pending_connectors()
|
||||
}
|
||||
|
||||
/// Tries to add a new [`connector`](drm::control::connector)
|
||||
/// to be used after the next commit.
|
||||
///
|
||||
/// **Warning**: You need to make sure, that the connector is not used with another surface
|
||||
/// or was properly removed via `remove_connector` + `commit` before adding it to another surface.
|
||||
/// Behavior if failing to do so is undefined, but might result in rendering errors or the connector
|
||||
/// getting removed from the other surface without updating it's internal state.
|
||||
///
|
||||
/// Fails if the `connector` is not compatible with the underlying [`crtc`](drm::control::crtc)
|
||||
/// (e.g. no suitable [`encoder`](drm::control::encoder) may be found)
|
||||
/// or is not compatible with the currently pending
|
||||
/// [`Mode`](drm::control::Mode).
|
||||
pub fn add_connector(&self, connector: connector::Handle) -> Result<(), Error<A::Error>> {
|
||||
self.drm.add_connector(connector).map_err(Error::DrmError)
|
||||
}
|
||||
|
||||
/// Tries to mark a [`connector`](drm::control::connector)
|
||||
/// for removal on the next commit.
|
||||
pub fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error<A::Error>> {
|
||||
self.drm.remove_connector(connector).map_err(Error::DrmError)
|
||||
}
|
||||
|
||||
/// Tries to replace the current connector set with the newly provided one on the next commit.
|
||||
///
|
||||
/// Fails if one new `connector` is not compatible with the underlying [`crtc`](drm::control::crtc)
|
||||
/// (e.g. no suitable [`encoder`](drm::control::encoder) may be found)
|
||||
/// or is not compatible with the currently pending
|
||||
/// [`Mode`](drm::control::Mode).
|
||||
pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error<A::Error>> {
|
||||
self.drm.set_connectors(connectors).map_err(Error::DrmError)
|
||||
}
|
||||
|
||||
/// Returns the currently active [`Mode`](drm::control::Mode)
|
||||
/// of the underlying [`crtc`](drm::control::crtc)
|
||||
pub fn current_mode(&self) -> Mode {
|
||||
self.drm.current_mode()
|
||||
}
|
||||
|
||||
/// Returns the currently pending [`Mode`](drm::control::Mode)
|
||||
/// to be used after the next commit.
|
||||
pub fn pending_mode(&self) -> Mode {
|
||||
self.drm.pending_mode()
|
||||
}
|
||||
|
||||
/// Tries to set a new [`Mode`](drm::control::Mode)
|
||||
/// to be used after the next commit.
|
||||
///
|
||||
/// Fails if the mode is not compatible with the underlying
|
||||
/// [`crtc`](drm::control::crtc) or any of the
|
||||
/// pending [`connector`](drm::control::connector)s.
|
||||
pub fn use_mode(&mut self, mode: Mode) -> Result<(), Error<A::Error>> {
|
||||
self.drm.use_mode(mode).map_err(Error::DrmError)?;
|
||||
let (w, h) = mode.size();
|
||||
self.swapchain.resize(w as _, h as _);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns if Variable Refresh Rate is advertised as supported by the given connector.
|
||||
///
|
||||
/// See [`DrmSurface::vrr_supported`] for more details.
|
||||
pub fn vrr_supported(&self, conn: connector::Handle) -> Result<VrrSupport, Error<A::Error>> {
|
||||
self.drm.vrr_supported(conn).map_err(Error::DrmError)
|
||||
}
|
||||
|
||||
/// Returns whether the next frame state would set Variable Refresh Rate as enabled.
|
||||
///
|
||||
/// See [`DrmSurface::vrr_enabled`] for more details.
|
||||
pub fn vrr_enabled(&self) -> bool {
|
||||
self.drm.vrr_enabled()
|
||||
}
|
||||
|
||||
/// Tries to set Variable Refresh Rate (VRR) for the next frame.
|
||||
//
|
||||
/// Doing so might cause the next frame to trigger a modeset.
|
||||
/// Check [`GbmBufferedSurface::vrr_supported`], which indicates if VRR can be
|
||||
/// used without a modeset on the attached connectors./
|
||||
pub fn use_vrr(&self, vrr: bool) -> Result<(), Error<A::Error>> {
|
||||
self.drm.use_vrr(vrr).map_err(Error::DrmError)
|
||||
}
|
||||
|
||||
/// Returns a reference to the underlying drm surface
|
||||
pub fn surface(&self) -> &DrmSurface {
|
||||
&self.drm
|
||||
}
|
||||
|
||||
/// Get the format of the underlying swapchain
|
||||
pub fn format(&self) -> Fourcc {
|
||||
self.swapchain.format()
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors thrown by a [`GbmBufferedSurface`]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error<E: std::error::Error + Send + Sync + 'static> {
|
||||
/// No supported pixel format for the given plane could be determined
|
||||
#[error("No supported plane buffer format found")]
|
||||
NoSupportedPlaneFormat,
|
||||
/// No supported pixel format for the given renderer could be determined
|
||||
#[error("No supported renderer buffer format found")]
|
||||
NoSupportedRendererFormat,
|
||||
/// The supported pixel formats of the renderer and plane are incompatible
|
||||
#[error("Supported plane and renderer buffer formats are incompatible")]
|
||||
FormatsNotCompatible,
|
||||
/// The swapchain is exhausted, you need to call `frame_submitted`
|
||||
#[error("Failed to allocate a new buffer")]
|
||||
NoFreeSlotsError,
|
||||
/// Failed to renderer using the given renderer
|
||||
#[error("Failed to render test frame")]
|
||||
InitialRenderingError,
|
||||
/// Error accessing the drm device
|
||||
#[error("The underlying drm surface encountered an error: {0}")]
|
||||
DrmError(#[from] DrmError),
|
||||
/// Error importing the rendered buffer to libgbm for scan-out
|
||||
#[error("The underlying gbm device encountered an error: {0}")]
|
||||
GbmError(#[source] E),
|
||||
/// Error exporting as Dmabuf
|
||||
#[error("The allocated buffer could not be exported as a dmabuf: {0}")]
|
||||
AsDmabufError(#[from] GbmConvertError),
|
||||
/// No buffer to queue
|
||||
#[error("No buffer has been acquired to get queued")]
|
||||
NoBuffer,
|
||||
}
|
||||
|
||||
impl<E: std::error::Error + Send + Sync + 'static> From<Error<E>> for SwapBuffersError {
|
||||
#[inline]
|
||||
fn from(err: Error<E>) -> SwapBuffersError {
|
||||
match err {
|
||||
x @ Error::NoSupportedPlaneFormat
|
||||
| x @ Error::NoSupportedRendererFormat
|
||||
| x @ Error::FormatsNotCompatible
|
||||
| x @ Error::InitialRenderingError => SwapBuffersError::ContextLost(Box::new(x)),
|
||||
x @ Error::NoFreeSlotsError | x @ Error::NoBuffer => {
|
||||
SwapBuffersError::TemporaryFailure(Box::new(x))
|
||||
}
|
||||
Error::DrmError(err) => err.into(),
|
||||
Error::GbmError(err) => SwapBuffersError::ContextLost(Box::new(err)),
|
||||
Error::AsDmabufError(err) => SwapBuffersError::ContextLost(Box::new(err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
+515
@@ -0,0 +1,515 @@
|
||||
use drm::control::{Device as ControlDevice, Mode, PageFlipFlags, connector, crtc, encoder, framebuffer};
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::sync::{
|
||||
Arc, Mutex, RwLock,
|
||||
atomic::{AtomicBool, Ordering},
|
||||
};
|
||||
|
||||
use crate::backend::drm::error::AccessError;
|
||||
use crate::{
|
||||
backend::drm::{
|
||||
DrmDeviceFd, device::DrmDeviceInternal, device::legacy::set_connector_state, error::Error,
|
||||
},
|
||||
utils::DevPath,
|
||||
};
|
||||
|
||||
use tracing::{debug, info, info_span, instrument, trace};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct State {
|
||||
pub mode: Mode,
|
||||
pub connectors: HashSet<connector::Handle>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn current_state<A: DevPath + ControlDevice>(fd: &A, crtc: crtc::Handle) -> Result<Self, Error> {
|
||||
// Try to enumerate the current state to set the initial state variable correctly.
|
||||
// We need an accurate state to handle `commit_pending`.
|
||||
let crtc_info = fd.get_crtc(crtc).map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error loading crtc info",
|
||||
dev: fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
|
||||
let current_mode = crtc_info.mode();
|
||||
|
||||
let mut current_connectors = HashSet::new();
|
||||
let res_handles = fd.resource_handles().map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error loading drm resources",
|
||||
dev: fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
for &con in res_handles.connectors() {
|
||||
let con_info = fd.get_connector(con, false).map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error loading connector info",
|
||||
dev: fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
if let Some(enc) = con_info.current_encoder() {
|
||||
let enc_info = fd.get_encoder(enc).map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error loading encoder info",
|
||||
dev: fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
if let Some(current_crtc) = enc_info.crtc() {
|
||||
if crtc == current_crtc {
|
||||
current_connectors.insert(con);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we have no current mode, we create a fake one, which will not match (and thus gets overridden on the commit below).
|
||||
// A better fix would probably be making mode an `Option`, but that would mean
|
||||
// we need to be sure, we require a mode to always be set without relying on the compiler.
|
||||
// So we cheat, because it works and is easier to handle later.
|
||||
Ok(State {
|
||||
mode: current_mode.unwrap_or_else(|| unsafe { std::mem::zeroed() }),
|
||||
connectors: current_connectors,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LegacyDrmSurface {
|
||||
pub(super) fd: Arc<DrmDeviceInternal>,
|
||||
pub(super) active: Arc<AtomicBool>,
|
||||
crtc: crtc::Handle,
|
||||
state: RwLock<State>,
|
||||
pending: RwLock<State>,
|
||||
dpms: Mutex<bool>,
|
||||
pub(super) span: tracing::Span,
|
||||
}
|
||||
|
||||
impl LegacyDrmSurface {
|
||||
pub fn new(
|
||||
fd: Arc<DrmDeviceInternal>,
|
||||
active: Arc<AtomicBool>,
|
||||
crtc: crtc::Handle,
|
||||
mode: Mode,
|
||||
connectors: &[connector::Handle],
|
||||
) -> Result<Self, Error> {
|
||||
let span = info_span!("drm_legacy", crtc = ?crtc);
|
||||
let _guard = span.enter();
|
||||
info!(?mode, ?connectors, ?crtc, "Initializing drm surface",);
|
||||
|
||||
let state = State::current_state(&*fd, crtc)?;
|
||||
let pending = State {
|
||||
mode,
|
||||
connectors: connectors.iter().copied().collect(),
|
||||
};
|
||||
|
||||
drop(_guard);
|
||||
let surface = LegacyDrmSurface {
|
||||
fd,
|
||||
active,
|
||||
crtc,
|
||||
state: RwLock::new(state),
|
||||
pending: RwLock::new(pending),
|
||||
dpms: Mutex::new(true),
|
||||
span,
|
||||
};
|
||||
|
||||
Ok(surface)
|
||||
}
|
||||
|
||||
pub fn current_connectors(&self) -> HashSet<connector::Handle> {
|
||||
self.state.read().unwrap().connectors.clone()
|
||||
}
|
||||
|
||||
pub fn pending_connectors(&self) -> HashSet<connector::Handle> {
|
||||
self.pending.read().unwrap().connectors.clone()
|
||||
}
|
||||
|
||||
pub fn current_mode(&self) -> Mode {
|
||||
self.state.read().unwrap().mode
|
||||
}
|
||||
|
||||
pub fn pending_mode(&self) -> Mode {
|
||||
self.pending.read().unwrap().mode
|
||||
}
|
||||
|
||||
#[instrument(parent = &self.span, skip(self))]
|
||||
pub fn add_connector(&self, conn: connector::Handle) -> Result<(), Error> {
|
||||
if !self.active.load(Ordering::SeqCst) {
|
||||
return Err(Error::DeviceInactive);
|
||||
}
|
||||
|
||||
let mut pending = self.pending.write().unwrap();
|
||||
|
||||
if self.check_connector(conn, &pending.mode)? {
|
||||
pending.connectors.insert(conn);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(parent = &self.span, skip(self))]
|
||||
pub fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> {
|
||||
let mut pending = self.pending.write().unwrap();
|
||||
|
||||
if pending.connectors.contains(&connector) && pending.connectors.len() == 1 {
|
||||
return Err(Error::SurfaceWithoutConnectors(self.crtc));
|
||||
}
|
||||
|
||||
pending.connectors.remove(&connector);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(parent = &self.span, skip(self))]
|
||||
pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> {
|
||||
if connectors.is_empty() {
|
||||
return Err(Error::SurfaceWithoutConnectors(self.crtc));
|
||||
}
|
||||
|
||||
if !self.active.load(Ordering::SeqCst) {
|
||||
return Err(Error::DeviceInactive);
|
||||
}
|
||||
|
||||
let mut pending = self.pending.write().unwrap();
|
||||
|
||||
if connectors
|
||||
.iter()
|
||||
.map(|conn| self.check_connector(*conn, &pending.mode))
|
||||
.collect::<Result<Vec<bool>, _>>()?
|
||||
.iter()
|
||||
.all(|v| *v)
|
||||
{
|
||||
pending.connectors = connectors.iter().cloned().collect();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", parent = &self.span, skip(self))]
|
||||
pub fn use_mode(&self, mode: Mode) -> Result<(), Error> {
|
||||
if !self.active.load(Ordering::SeqCst) {
|
||||
return Err(Error::DeviceInactive);
|
||||
}
|
||||
|
||||
let mut pending = self.pending.write().unwrap();
|
||||
|
||||
// check the connectors to see if this mode is supported
|
||||
for connector in &pending.connectors {
|
||||
if !self
|
||||
.fd
|
||||
.get_connector(*connector, false)
|
||||
.map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error loading connector info",
|
||||
dev: self.fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?
|
||||
.modes()
|
||||
.contains(&mode)
|
||||
{
|
||||
return Err(Error::ModeNotSuitable(mode));
|
||||
}
|
||||
}
|
||||
|
||||
pending.mode = mode;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn commit_pending(&self) -> bool {
|
||||
*self.pending.read().unwrap() != *self.state.read().unwrap()
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", parent = &self.span, skip(self))]
|
||||
#[profiling::function]
|
||||
pub fn commit(&self, framebuffer: framebuffer::Handle, event: bool) -> Result<(), Error> {
|
||||
if !self.active.load(Ordering::SeqCst) {
|
||||
return Err(Error::DeviceInactive);
|
||||
}
|
||||
|
||||
let mut current = self.state.write().unwrap();
|
||||
let pending = self.pending.read().unwrap();
|
||||
let mut dpms = self.dpms.lock().unwrap();
|
||||
|
||||
if !*dpms {
|
||||
let connectors = current.connectors.intersection(&pending.connectors);
|
||||
set_connector_state(&*self.fd, connectors.copied(), true)?;
|
||||
*dpms = true;
|
||||
}
|
||||
|
||||
{
|
||||
let removed = current.connectors.difference(&pending.connectors);
|
||||
let added = pending.connectors.difference(¤t.connectors);
|
||||
|
||||
let mut conn_removed = false;
|
||||
for conn in removed.clone() {
|
||||
if let Ok(info) = self.fd.get_connector(*conn, false) {
|
||||
info!("Removing connector: {:?}", info.interface());
|
||||
} else {
|
||||
info!("Removing unknown connector");
|
||||
}
|
||||
// if the connector was mapped to our crtc, we need to ack the disconnect.
|
||||
// the graphics pipeline will not be freed otherwise
|
||||
conn_removed = true;
|
||||
}
|
||||
set_connector_state(&*self.fd, removed.copied(), false)?;
|
||||
|
||||
if conn_removed {
|
||||
// null commit (necessary to trigger removal on the kernel side with the legacy api.)
|
||||
self.fd
|
||||
.set_crtc(self.crtc, None, (0, 0), &[], None)
|
||||
.map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error setting crtc",
|
||||
dev: self.fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
}
|
||||
|
||||
for conn in added.clone() {
|
||||
if let Ok(info) = self.fd.get_connector(*conn, false) {
|
||||
info!("Adding connector: {:?}", info.interface());
|
||||
} else {
|
||||
info!("Adding unknown connector");
|
||||
}
|
||||
}
|
||||
set_connector_state(&*self.fd, added.copied(), true)?;
|
||||
|
||||
if current.mode != pending.mode {
|
||||
info!("Setting new mode: {:?}", pending.mode.name());
|
||||
}
|
||||
}
|
||||
|
||||
debug!("Setting screen");
|
||||
// do a modeset and attach the given framebuffer
|
||||
self.fd
|
||||
.set_crtc(
|
||||
self.crtc,
|
||||
Some(framebuffer),
|
||||
(0, 0),
|
||||
&pending
|
||||
.connectors
|
||||
.iter()
|
||||
.copied()
|
||||
.collect::<Vec<connector::Handle>>(),
|
||||
Some(pending.mode),
|
||||
)
|
||||
.map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error setting crtc",
|
||||
dev: self.fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
|
||||
*current = pending.clone();
|
||||
|
||||
if event {
|
||||
// set crtc does not trigger page_flip events, so we immediately queue a flip
|
||||
// with the same framebuffer.
|
||||
// this will result in wasting a frame, because this flip will need to wait
|
||||
// for `set_crtc`, but is necessary to drive the event loop and thus provide
|
||||
// a more consistent api.
|
||||
ControlDevice::page_flip(&*self.fd, self.crtc, framebuffer, PageFlipFlags::EVENT, None).map_err(
|
||||
|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Failed to queue page flip",
|
||||
dev: self.fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", parent = &self.span, skip(self))]
|
||||
#[profiling::function]
|
||||
pub fn page_flip(&self, framebuffer: framebuffer::Handle, event: bool) -> Result<(), Error> {
|
||||
trace!("Queueing Page flip");
|
||||
|
||||
if !self.active.load(Ordering::SeqCst) {
|
||||
return Err(Error::DeviceInactive);
|
||||
}
|
||||
|
||||
let current = self.state.read().unwrap();
|
||||
let mut dpms = self.dpms.lock().unwrap();
|
||||
if !*dpms {
|
||||
set_connector_state(&*self.fd, current.connectors.iter().copied(), true)?;
|
||||
*dpms = true;
|
||||
}
|
||||
|
||||
ControlDevice::page_flip(
|
||||
&*self.fd,
|
||||
self.crtc,
|
||||
framebuffer,
|
||||
if event {
|
||||
PageFlipFlags::EVENT
|
||||
} else {
|
||||
PageFlipFlags::empty()
|
||||
},
|
||||
None,
|
||||
)
|
||||
.map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Failed to page flip",
|
||||
dev: self.fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument(level = "trace", parent = &self.span, skip(self))]
|
||||
#[profiling::function]
|
||||
pub fn test_buffer(&self, fb: framebuffer::Handle, mode: &Mode) -> Result<(), Error> {
|
||||
if !self.active.load(Ordering::SeqCst) {
|
||||
return Err(Error::DeviceInactive);
|
||||
}
|
||||
|
||||
let pending = self.pending.read().unwrap();
|
||||
|
||||
debug!("Setting screen for buffer *testing*");
|
||||
self.fd
|
||||
.set_crtc(
|
||||
self.crtc,
|
||||
Some(fb),
|
||||
(0, 0),
|
||||
&pending
|
||||
.connectors
|
||||
.iter()
|
||||
.copied()
|
||||
.collect::<Vec<connector::Handle>>(),
|
||||
Some(*mode),
|
||||
)
|
||||
.map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Failed to test buffer",
|
||||
dev: self.fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// we use this function to verify, if a certain connector/mode combination
|
||||
// is valid on our crtc. We do this with the most basic information we have:
|
||||
// - is there a matching encoder
|
||||
// - does the connector support the provided Mode.
|
||||
//
|
||||
// Better would be some kind of test commit to ask the driver,
|
||||
// but that only exists for the atomic api.
|
||||
fn check_connector(&self, conn: connector::Handle, mode: &Mode) -> Result<bool, Error> {
|
||||
let info = self.fd.get_connector(conn, false).map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error loading connector info",
|
||||
dev: self.fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
|
||||
// check if the connector can handle the current mode
|
||||
if info.modes().contains(mode) {
|
||||
// check if there is a valid encoder
|
||||
let encoders = info
|
||||
.encoders()
|
||||
.iter()
|
||||
.map(|encoder| {
|
||||
self.fd.get_encoder(*encoder).map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error loading encoder info",
|
||||
dev: self.fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<encoder::Info>, _>>()?;
|
||||
|
||||
// and if any encoder supports the selected crtc
|
||||
let resource_handles = self.fd.resource_handles().map_err(|source| {
|
||||
Error::Access(AccessError {
|
||||
errmsg: "Error loading resources",
|
||||
dev: self.fd.dev_path(),
|
||||
source,
|
||||
})
|
||||
})?;
|
||||
if !encoders
|
||||
.iter()
|
||||
.map(|encoder| encoder.possible_crtcs())
|
||||
.all(|crtc_list| resource_handles.filter_crtcs(crtc_list).contains(&self.crtc))
|
||||
{
|
||||
Ok(false)
|
||||
} else {
|
||||
Ok(true)
|
||||
}
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn reset_state<B: DevPath + ControlDevice + 'static>(
|
||||
&self,
|
||||
fd: Option<&B>,
|
||||
) -> Result<(), Error> {
|
||||
*self.state.write().unwrap() = if let Some(fd) = fd {
|
||||
State::current_state(fd, self.crtc)?
|
||||
} else {
|
||||
State::current_state(&*self.fd, self.crtc)?
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn device_fd(&self) -> &DrmDeviceFd {
|
||||
self.fd.device_fd()
|
||||
}
|
||||
|
||||
pub fn clear(&self) -> Result<(), Error> {
|
||||
let current = self.state.read().unwrap();
|
||||
let mut dpms = self.dpms.lock().unwrap();
|
||||
if *dpms {
|
||||
set_connector_state(&*self.fd, current.connectors.iter().copied(), false)?;
|
||||
*dpms = false;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LegacyDrmSurface {
|
||||
fn drop(&mut self) {
|
||||
let _guard = self.span.enter();
|
||||
// ignore failure at this point
|
||||
|
||||
if !self.active.load(Ordering::SeqCst) {
|
||||
// the device is gone or we are on another tty
|
||||
// old state has been restored, we shouldn't touch it.
|
||||
// if we are on another tty the connectors will get disabled
|
||||
// by the device, when switching back
|
||||
return;
|
||||
}
|
||||
|
||||
// disable connectors again
|
||||
let current = self.state.read().unwrap();
|
||||
if set_connector_state(&*self.fd, current.connectors.iter().copied(), false).is_ok() {
|
||||
// null commit
|
||||
let _ = self.fd.set_crtc(self.crtc, None, (0, 0), &[], None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::LegacyDrmSurface;
|
||||
|
||||
fn is_send<S: Send>() {}
|
||||
|
||||
#[test]
|
||||
fn surface_is_send() {
|
||||
is_send::<LegacyDrmSurface>();
|
||||
}
|
||||
}
|
||||
+555
@@ -0,0 +1,555 @@
|
||||
use std::io;
|
||||
use std::os::unix::io::{AsFd, BorrowedFd};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use drm::Device as BasicDevice;
|
||||
use drm::control::{Device as ControlDevice, Mode, connector, crtc, framebuffer, plane};
|
||||
|
||||
use libc::dev_t;
|
||||
|
||||
pub(super) mod atomic;
|
||||
#[cfg(feature = "backend_gbm")]
|
||||
pub(super) mod gbm;
|
||||
pub(super) mod legacy;
|
||||
use super::{
|
||||
DrmDeviceFd, PlaneClaim, PlaneInfo, PlaneType, Planes, device::PlaneClaimStorage, error::Error,
|
||||
plane_type,
|
||||
};
|
||||
use crate::utils::DevPath;
|
||||
use crate::utils::{Buffer, Physical, Point, Rectangle, Transform};
|
||||
use atomic::AtomicDrmSurface;
|
||||
use legacy::LegacyDrmSurface;
|
||||
|
||||
/// An open crtc + plane combination that can be used for scan-out
|
||||
#[derive(Debug)]
|
||||
pub struct DrmSurface {
|
||||
// This field is only read when 'backend_session' is enabled
|
||||
#[allow(dead_code)]
|
||||
pub(super) dev_id: dev_t,
|
||||
pub(super) crtc: crtc::Handle,
|
||||
pub(super) planes: Planes,
|
||||
pub(super) internal: Arc<DrmSurfaceInternal>,
|
||||
pub(super) plane_claim_storage: PlaneClaimStorage,
|
||||
pub(super) primary_plane: (PlaneInfo, PlaneClaim),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PlaneDamageInner {
|
||||
drm: DrmDeviceFd,
|
||||
blob: Option<drm::control::property::Value<'static>>,
|
||||
}
|
||||
|
||||
impl Drop for PlaneDamageInner {
|
||||
fn drop(&mut self) {
|
||||
// There is nothing we can do if that fails
|
||||
if let Some(drm::control::property::Value::Blob(id)) = self.blob.take() {
|
||||
let _ = self.drm.destroy_property_blob(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// Helper for `FB_DAMAGE_CLIPS`
|
||||
pub struct PlaneDamageClips {
|
||||
inner: Arc<PlaneDamageInner>,
|
||||
}
|
||||
|
||||
impl PlaneDamageClips {
|
||||
/// Returns the underlying blob
|
||||
pub fn blob(&self) -> drm::control::property::Value<'_> {
|
||||
self.inner.blob.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl PlaneDamageClips {
|
||||
/// Initialize damage clips for a a plane
|
||||
#[profiling::function]
|
||||
pub fn from_damage(
|
||||
device: &DrmDeviceFd,
|
||||
src: Rectangle<f64, Buffer>,
|
||||
dst: Rectangle<i32, Physical>,
|
||||
dst_transform: Transform,
|
||||
damage_transform: Transform,
|
||||
damage: impl IntoIterator<Item = Rectangle<i32, Physical>>,
|
||||
) -> io::Result<Option<Self>> {
|
||||
let scale = src.size / dst.size.to_logical(1).to_buffer(1, dst_transform).to_f64();
|
||||
|
||||
let mut rects = damage
|
||||
.into_iter()
|
||||
.map(|rect| {
|
||||
let mut rect = rect
|
||||
.to_f64()
|
||||
.to_logical(1f64)
|
||||
.to_buffer(
|
||||
1f64,
|
||||
damage_transform,
|
||||
&src.size.to_logical(scale, damage_transform),
|
||||
)
|
||||
.upscale(scale);
|
||||
rect.loc += src.loc;
|
||||
let rect = rect.to_i32_up();
|
||||
|
||||
drm_ffi::drm_mode_rect {
|
||||
x1: rect.loc.x,
|
||||
y1: rect.loc.y,
|
||||
x2: rect.loc.x.saturating_add(rect.size.w),
|
||||
y2: rect.loc.y.saturating_add(rect.size.h),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if rects.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let data = unsafe {
|
||||
std::slice::from_raw_parts_mut(
|
||||
rects.as_mut_ptr() as *mut u8,
|
||||
std::mem::size_of::<drm_ffi::drm_mode_rect>() * rects.len(),
|
||||
)
|
||||
};
|
||||
|
||||
let blob = drm_ffi::mode::create_property_blob(device.as_fd(), data)?;
|
||||
|
||||
Ok(Some(PlaneDamageClips {
|
||||
inner: Arc::new(PlaneDamageInner {
|
||||
drm: device.clone(),
|
||||
blob: Some(drm::control::property::Value::Blob(blob.blob_id as u64)),
|
||||
}),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for PlaneDamageClips {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
inner: self.inner.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// State of a single plane
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlaneState<'a> {
|
||||
/// Handle of the plane
|
||||
pub handle: plane::Handle,
|
||||
/// Configuration for that plane
|
||||
///
|
||||
/// Can be `None` if nothing is attached
|
||||
pub config: Option<PlaneConfig<'a>>,
|
||||
}
|
||||
|
||||
/// Configuration for a single plane
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PlaneConfig<'a> {
|
||||
/// Source [`Rectangle`] of the attached framebuffer
|
||||
pub src: Rectangle<f64, Buffer>,
|
||||
/// Destination [`Rectangle`] on the CRTC
|
||||
pub dst: Rectangle<i32, Physical>,
|
||||
/// Transform for the attached framebuffer
|
||||
pub transform: Transform,
|
||||
/// Alpha value for the plane
|
||||
pub alpha: f32,
|
||||
/// Damage clips of the attached framebuffer
|
||||
pub damage_clips: Option<drm::control::property::Value<'a>>,
|
||||
/// Framebuffer handle
|
||||
pub fb: framebuffer::Handle,
|
||||
/// Optional fence
|
||||
pub fence: Option<BorrowedFd<'a>>,
|
||||
}
|
||||
|
||||
/// VRR support state
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum VrrSupport {
|
||||
/// VRR is generally not supported
|
||||
NotSupported,
|
||||
/// VRR is supported, but changing it requires a modeset
|
||||
RequiresModeset,
|
||||
/// VRR is supported and can be changed without a modeset
|
||||
Supported,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum DrmSurfaceInternal {
|
||||
Atomic(AtomicDrmSurface),
|
||||
Legacy(LegacyDrmSurface),
|
||||
}
|
||||
|
||||
impl AsFd for DrmSurface {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.fd.as_fd(),
|
||||
DrmSurfaceInternal::Legacy(surf) => surf.fd.as_fd(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl BasicDevice for DrmSurface {}
|
||||
impl ControlDevice for DrmSurface {}
|
||||
|
||||
impl DrmSurface {
|
||||
/// Returns the underlying [`DrmDeviceFd`]
|
||||
pub fn device_fd(&self) -> &DrmDeviceFd {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.device_fd(),
|
||||
DrmSurfaceInternal::Legacy(surf) => surf.device_fd(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether this surface is using the legacy api
|
||||
pub fn is_legacy(&self) -> bool {
|
||||
matches!(&*self.internal, DrmSurfaceInternal::Legacy(_))
|
||||
}
|
||||
|
||||
/// Returns the underlying [`crtc`](drm::control::crtc) of this surface
|
||||
pub fn crtc(&self) -> crtc::Handle {
|
||||
self.crtc
|
||||
}
|
||||
|
||||
/// Returns the underlying primary [`plane`](drm::control::plane) of this surface
|
||||
pub fn plane(&self) -> plane::Handle {
|
||||
self.primary_plane.0.handle
|
||||
}
|
||||
|
||||
/// Returns the [`PlaneInfo`] of the underlying primary [`plane`](drm::control::plane) of this surface
|
||||
pub fn plane_info(&self) -> &PlaneInfo {
|
||||
&self.primary_plane.0
|
||||
}
|
||||
|
||||
/// Currently used [`connector`](drm::control::connector)s of this surface
|
||||
pub fn current_connectors(&self) -> impl IntoIterator<Item = connector::Handle> {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.current_connectors(),
|
||||
DrmSurfaceInternal::Legacy(surf) => surf.current_connectors(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the pending [`connector`](drm::control::connector)s
|
||||
/// used after the next [`commit`](DrmSurface::commit) of this surface
|
||||
pub fn pending_connectors(&self) -> impl IntoIterator<Item = connector::Handle> {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.pending_connectors(),
|
||||
DrmSurfaceInternal::Legacy(surf) => surf.pending_connectors(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to add a new [`connector`](drm::control::connector)
|
||||
/// to be used after the next commit.
|
||||
///
|
||||
/// **Warning**: You need to make sure, that the connector is not used with another surface
|
||||
/// or was properly removed via `remove_connector` + `commit` before adding it to another surface.
|
||||
/// Behavior if failing to do so is undefined, but might result in rendering errors or the connector
|
||||
/// getting removed from the other surface without updating it's internal state.
|
||||
///
|
||||
/// Fails if the `connector` is not compatible with the underlying [`crtc`](drm::control::crtc)
|
||||
/// (e.g. no suitable [`encoder`](drm::control::encoder) may be found)
|
||||
/// or is not compatible with the currently pending
|
||||
/// [`Mode`](drm::control::Mode).
|
||||
pub fn add_connector(&self, connector: connector::Handle) -> Result<(), Error> {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.add_connector(connector),
|
||||
DrmSurfaceInternal::Legacy(surf) => surf.add_connector(connector),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to mark a [`connector`](drm::control::connector)
|
||||
/// for removal on the next commit.
|
||||
pub fn remove_connector(&self, connector: connector::Handle) -> Result<(), Error> {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.remove_connector(connector),
|
||||
DrmSurfaceInternal::Legacy(surf) => surf.remove_connector(connector),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to replace the current connector set with the newly provided one on the next commit.
|
||||
///
|
||||
/// Fails if one new `connector` is not compatible with the underlying [`crtc`](drm::control::crtc)
|
||||
/// (e.g. no suitable [`encoder`](drm::control::encoder) may be found)
|
||||
/// or is not compatible with the currently pending
|
||||
/// [`Mode`](drm::control::Mode).
|
||||
pub fn set_connectors(&self, connectors: &[connector::Handle]) -> Result<(), Error> {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.set_connectors(connectors),
|
||||
DrmSurfaceInternal::Legacy(surf) => surf.set_connectors(connectors),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the currently active [`Mode`](drm::control::Mode)
|
||||
/// of the underlying [`crtc`](drm::control::crtc)
|
||||
pub fn current_mode(&self) -> Mode {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.current_mode(),
|
||||
DrmSurfaceInternal::Legacy(surf) => surf.current_mode(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the currently pending [`Mode`](drm::control::Mode)
|
||||
/// to be used after the next commit.
|
||||
pub fn pending_mode(&self) -> Mode {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.pending_mode(),
|
||||
DrmSurfaceInternal::Legacy(surf) => surf.pending_mode(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to set a new [`Mode`](drm::control::Mode)
|
||||
/// to be used after the next commit.
|
||||
///
|
||||
/// Fails if the mode is not compatible with the underlying
|
||||
/// [`crtc`](drm::control::crtc) or any of the
|
||||
/// pending [`connector`](drm::control::connector)s.
|
||||
pub fn use_mode(&self, mode: Mode) -> Result<(), Error> {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.use_mode(mode),
|
||||
DrmSurfaceInternal::Legacy(surf) => surf.use_mode(mode),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns if Variable Refresh Rate is advertised as supported by the given connector.
|
||||
///
|
||||
/// Note: This will always return [`VrrSupport::NotSupported`] if the underlying
|
||||
/// implementation is using the legacy DRM api.
|
||||
pub fn vrr_supported(&self, conn: connector::Handle) -> Result<VrrSupport, Error> {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.vrr_supported(conn),
|
||||
DrmSurfaceInternal::Legacy(_) => Ok(VrrSupport::NotSupported),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether the next frame state would set Variable Refresh Rate as enabled.
|
||||
///
|
||||
/// Note: This will always return `false` if the underlying implementation is using the
|
||||
/// legacy DRM api.
|
||||
pub fn vrr_enabled(&self) -> bool {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.vrr_enabled(),
|
||||
DrmSurfaceInternal::Legacy(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to set Variable Refresh Rate (VRR) for the next frame.
|
||||
///
|
||||
/// Doing so might cause [`DrmSurface::commit_pending`] to return `true`.
|
||||
/// Check [`DrmSurface::vrr_supported`], which indicates if VRR can be
|
||||
/// used without a modeset on the attached connectors.
|
||||
///
|
||||
/// Disabling VRR on legacy DRM is a no-op. Enabling VRR returns [`Error::UnknownProperty`].
|
||||
pub fn use_vrr(&self, vrr: bool) -> Result<(), Error> {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.use_vrr(vrr),
|
||||
DrmSurfaceInternal::Legacy(_) => {
|
||||
if vrr {
|
||||
Err(Error::UnknownProperty {
|
||||
handle: self.crtc.into(),
|
||||
name: "VRR_ENABLED",
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Disables the given plane.
|
||||
///
|
||||
/// Errors if the plane is not supported by this crtc or if the underlying
|
||||
/// implementation does not support the use of planes.
|
||||
pub fn clear_plane(&self, plane: plane::Handle) -> Result<(), Error> {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.clear_plane(plane),
|
||||
DrmSurfaceInternal::Legacy(_) => Err(Error::NonPrimaryPlane(plane)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true whenever any state changes are pending to be committed
|
||||
///
|
||||
/// The following functions may trigger a pending commit:
|
||||
/// - [`add_connector`](DrmSurface::add_connector)
|
||||
/// - [`remove_connector`](DrmSurface::remove_connector)
|
||||
/// - [`use_mode`](DrmSurface::use_mode)
|
||||
pub fn commit_pending(&self) -> bool {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.commit_pending(),
|
||||
DrmSurfaceInternal::Legacy(surf) => surf.commit_pending(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Test a state given a set of framebuffers.
|
||||
///
|
||||
/// *Note*: This will always return `Ok` for legacy devices if `allow_modeset = false`.
|
||||
/// The legacy drm api has no way to test a buffer without triggering a modeset.
|
||||
#[profiling::function]
|
||||
pub fn test_state<'a>(
|
||||
&self,
|
||||
planes: impl IntoIterator<Item = PlaneState<'a>>,
|
||||
allow_modeset: bool,
|
||||
) -> Result<(), Error> {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.test_state(planes, allow_modeset),
|
||||
DrmSurfaceInternal::Legacy(surf) => {
|
||||
let fb = ensure_legacy_planes(self, planes)?;
|
||||
|
||||
if allow_modeset {
|
||||
surf.test_buffer(fb, &self.pending_mode())
|
||||
} else {
|
||||
// Legacy can not test a buffer without triggering a modeset, so we can
|
||||
// only assume it works and hope for the best. A later call to commit or
|
||||
// page_flip will show the correct result
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Commit the pending state rendering a given set of framebuffers.
|
||||
///
|
||||
/// *Note*: This will trigger a full modeset on the underlying device,
|
||||
/// potentially causing some flickering. Check before performing this
|
||||
/// operation if a commit really is necessary using [`commit_pending`](DrmSurface::commit_pending).
|
||||
///
|
||||
/// This operation is not necessarily blocking until the crtc is in the desired state,
|
||||
/// but will trigger a `vblank` event once done.
|
||||
/// Make sure to have the device registered in your event loop prior to invoking this, to not miss
|
||||
/// any generated event.
|
||||
#[profiling::function]
|
||||
pub fn commit<'a>(
|
||||
&self,
|
||||
planes: impl IntoIterator<Item = PlaneState<'a>>,
|
||||
event: bool,
|
||||
) -> Result<(), Error> {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.commit(planes, event),
|
||||
DrmSurfaceInternal::Legacy(surf) => {
|
||||
let fb = ensure_legacy_planes(self, planes)?;
|
||||
surf.commit(fb, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Page-flip the underlying [`crtc`](drm::control::crtc)
|
||||
/// to a new given set of [`framebuffer`]s.
|
||||
///
|
||||
/// This will not cause the crtc to modeset.
|
||||
///
|
||||
/// This operation is not blocking and will produce a `vblank` event once swapping is done.
|
||||
/// Make sure to have the device registered in your event loop to not miss the event.
|
||||
#[profiling::function]
|
||||
pub fn page_flip<'a>(
|
||||
&self,
|
||||
planes: impl IntoIterator<Item = PlaneState<'a>>,
|
||||
event: bool,
|
||||
) -> Result<(), Error> {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.page_flip(planes, event),
|
||||
DrmSurfaceInternal::Legacy(surf) => {
|
||||
let fb = ensure_legacy_planes(self, planes)?;
|
||||
surf.page_flip(fb, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a set of available planes for this surface
|
||||
pub fn planes(&self) -> &Planes {
|
||||
&self.planes
|
||||
}
|
||||
|
||||
/// Claim a plane so that it won't be used by a different crtc
|
||||
///
|
||||
/// Returns `None` if the plane could not be claimed
|
||||
pub fn claim_plane(&self, plane: plane::Handle) -> Option<PlaneClaim> {
|
||||
// Validate that we are called with an plane that belongs to us
|
||||
if self.planes.primary.iter().any(|p| p.handle == plane)
|
||||
|| self.planes.cursor.iter().any(|p| p.handle == plane)
|
||||
|| self.planes.overlay.iter().any(|p| p.handle == plane)
|
||||
{
|
||||
self.plane_claim_storage.claim(plane, self.crtc)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-evaluates the current state of the crtc.
|
||||
///
|
||||
/// It is recommended to call this function after this used [`Session`](crate::backend::session::Session)
|
||||
/// gets re-activated / VT switched to.
|
||||
///
|
||||
/// Usually you do not need to call this in other circumstances, but if
|
||||
/// the state of the crtc is modified elsewhere, you may call this function
|
||||
/// to reset it's internal state.
|
||||
pub fn reset_state(&self) -> Result<(), Error> {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.reset_state::<Self>(None),
|
||||
DrmSurfaceInternal::Legacy(surf) => surf.reset_state::<Self>(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns if the underlying device is currently paused or not.
|
||||
pub fn is_active(&self) -> bool {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.active.load(Ordering::SeqCst),
|
||||
DrmSurfaceInternal::Legacy(surf) => surf.active.load(Ordering::SeqCst),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "backend_gbm")]
|
||||
pub(super) fn span(&self) -> &tracing::Span {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => &surf.span,
|
||||
DrmSurfaceInternal::Legacy(surf) => &surf.span,
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear the surface, setting DPMS state to off and disabling all planes.
|
||||
///
|
||||
/// The surface will be re-enabled on the next [`page_flip`][Self::page_flip] or
|
||||
/// [`commit`][Self::commit].
|
||||
pub fn clear(&self) -> Result<(), Error> {
|
||||
match &*self.internal {
|
||||
DrmSurfaceInternal::Atomic(surf) => surf.clear(),
|
||||
DrmSurfaceInternal::Legacy(surf) => surf.clear(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_legacy_planes<'a>(
|
||||
dev: &(impl ControlDevice + DevPath),
|
||||
planes: impl IntoIterator<Item = PlaneState<'a>>,
|
||||
) -> Result<framebuffer::Handle, Error> {
|
||||
let state = planes.into_iter().next().ok_or(Error::NoPlane)?;
|
||||
|
||||
if plane_type(dev, state.handle)? != PlaneType::Primary {
|
||||
return Err(Error::NonPrimaryPlane(state.handle));
|
||||
}
|
||||
|
||||
let Some(config) = state.config else {
|
||||
// we need a config on the primary plane
|
||||
return Err(Error::NoFramebuffer(state.handle));
|
||||
};
|
||||
|
||||
if config.dst.loc != Point::default() {
|
||||
// legacy does not support crtc position (technically we could do it,
|
||||
// but the position can only be changed by commit, not by page-flip,
|
||||
// so we just not allow it)
|
||||
return Err(Error::UnsupportedPlaneConfiguration(state.handle));
|
||||
}
|
||||
|
||||
if config.src.loc != Point::default()
|
||||
|| config
|
||||
.src
|
||||
.size
|
||||
.to_logical(1.0, Transform::Normal)
|
||||
.to_physical(1.0)
|
||||
!= config.dst.size.to_f64()
|
||||
{
|
||||
// legacy does not support crop nor scale
|
||||
return Err(Error::UnsupportedPlaneConfiguration(state.handle));
|
||||
}
|
||||
|
||||
if config.transform != Transform::Normal {
|
||||
// legacy does not support transform
|
||||
return Err(Error::UnsupportedPlaneConfiguration(state.handle));
|
||||
}
|
||||
|
||||
Ok(config.fb)
|
||||
}
|
||||
+650
@@ -0,0 +1,650 @@
|
||||
//! EGL context related structs
|
||||
use std::{
|
||||
os::raw::c_int,
|
||||
sync::{Arc, atomic::Ordering},
|
||||
};
|
||||
|
||||
use libc::c_void;
|
||||
|
||||
use super::{EGLError, Error, MakeCurrentError, ffi, wrap_egl_call_bool, wrap_egl_call_ptr};
|
||||
use crate::{
|
||||
backend::{
|
||||
allocator::format::FormatSet,
|
||||
egl::{
|
||||
EGLSurface,
|
||||
display::{EGLDisplay, PixelFormat},
|
||||
},
|
||||
},
|
||||
utils::user_data::UserDataMap,
|
||||
};
|
||||
|
||||
use tracing::{info, info_span, instrument, trace, warn};
|
||||
|
||||
/// EGL context for rendering
|
||||
#[derive(Debug)]
|
||||
pub struct EGLContext {
|
||||
context: ffi::egl::types::EGLContext,
|
||||
display: EGLDisplay,
|
||||
config_id: ffi::egl::types::EGLConfig,
|
||||
pixel_format: Option<PixelFormat>,
|
||||
user_data: Arc<UserDataMap>,
|
||||
externally_managed: bool,
|
||||
pub(crate) span: tracing::Span,
|
||||
}
|
||||
|
||||
/// Defines the priority for an [`EGLContext`]
|
||||
///
|
||||
/// see: <https://registry.khronos.org/EGL/extensions/IMG/EGL_IMG_context_priority.txt>
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum ContextPriority {
|
||||
/// High priority
|
||||
///
|
||||
/// Note: This might require special system privileges like `CAP_SYS_NICE` to succeed.
|
||||
High,
|
||||
/// Medium priority
|
||||
///
|
||||
/// Default if not specified
|
||||
#[default]
|
||||
Medium,
|
||||
/// Low priority
|
||||
Low,
|
||||
}
|
||||
|
||||
impl TryFrom<ffi::egl::types::EGLenum> for ContextPriority {
|
||||
type Error = EGLError;
|
||||
|
||||
#[inline]
|
||||
fn try_from(value: ffi::egl::types::EGLenum) -> Result<Self, Self::Error> {
|
||||
let priority = match value {
|
||||
ffi::egl::CONTEXT_PRIORITY_HIGH_IMG => ContextPriority::High,
|
||||
ffi::egl::CONTEXT_PRIORITY_MEDIUM_IMG => ContextPriority::Medium,
|
||||
ffi::egl::CONTEXT_PRIORITY_LOW_IMG => ContextPriority::Low,
|
||||
_ => return Err(EGLError::BadParameter),
|
||||
};
|
||||
Ok(priority)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ContextPriority> for ffi::egl::types::EGLenum {
|
||||
#[inline]
|
||||
fn from(value: ContextPriority) -> Self {
|
||||
match value {
|
||||
ContextPriority::High => ffi::egl::CONTEXT_PRIORITY_HIGH_IMG,
|
||||
ContextPriority::Medium => ffi::egl::CONTEXT_PRIORITY_MEDIUM_IMG,
|
||||
ContextPriority::Low => ffi::egl::CONTEXT_PRIORITY_LOW_IMG,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: A context can be sent to another thread when this context is not current.
|
||||
//
|
||||
// If the context is made current, the safety requirements for calling `EGLDisplay::make_current` must be
|
||||
// upheld. This means a context which is made current must be unbound (using `unbind`) in order to send this
|
||||
// context to another thread.
|
||||
unsafe impl Send for EGLContext {}
|
||||
|
||||
impl EGLContext {
|
||||
/// Creates a new `EGLContext` from raw handles.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - The context must be created from the system default EGL library (`dlopen("libEGL.so")`)
|
||||
/// - The `display`, `config`, and `context` must be valid for the lifetime of the returned context.
|
||||
pub unsafe fn from_raw(
|
||||
display: *const c_void,
|
||||
config_id: *const c_void,
|
||||
context: *const c_void,
|
||||
) -> Result<EGLContext, Error> {
|
||||
assert!(!display.is_null(), "EGLDisplay pointer is null");
|
||||
assert!(!config_id.is_null(), "EGL configuration id pointer is null");
|
||||
assert!(!context.is_null(), "EGLContext pointer is null");
|
||||
|
||||
let display = unsafe { EGLDisplay::from_raw(display, config_id)? };
|
||||
let pixel_format = unsafe { display.get_pixel_format(config_id)? };
|
||||
|
||||
let span = info_span!(parent: &display.span, "egl_context", ptr = context as usize);
|
||||
|
||||
Ok(EGLContext {
|
||||
context,
|
||||
display,
|
||||
config_id,
|
||||
pixel_format: Some(pixel_format),
|
||||
user_data: Arc::new(UserDataMap::default()),
|
||||
externally_managed: true,
|
||||
span,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new configless `EGLContext` from a given `EGLDisplay`
|
||||
pub fn new(display: &EGLDisplay) -> Result<EGLContext, Error> {
|
||||
Self::new_internal(display, None, None, None)
|
||||
}
|
||||
|
||||
/// Creates a new configless `EGLContext` with the specified priority from a given `EGLDisplay`
|
||||
///
|
||||
/// Note: The priority is a hint that might be ignored by the underlying platform.
|
||||
/// It also requires `EGL_IMG_context_priority` to be available, otherwise the priority will be
|
||||
/// ignored.
|
||||
pub fn new_with_priority(display: &EGLDisplay, priority: ContextPriority) -> Result<EGLContext, Error> {
|
||||
Self::new_internal(display, None, None, Some(priority))
|
||||
}
|
||||
|
||||
/// Create a new [`EGLContext`] from a given `EGLDisplay` and configuration requirements
|
||||
pub fn new_with_config(
|
||||
display: &EGLDisplay,
|
||||
attributes: GlAttributes,
|
||||
reqs: PixelFormatRequirements,
|
||||
) -> Result<EGLContext, Error> {
|
||||
Self::new_internal(display, None, Some((attributes, reqs)), None)
|
||||
}
|
||||
|
||||
/// Create a new [`EGLContext`] from a given `EGLDisplay`, configuration requirements and priority
|
||||
///
|
||||
/// Note: The priority is a hint that might be ignored by the underlying platform.
|
||||
/// It also requires `EGL_IMG_context_priority` to be available, otherwise the priority will be
|
||||
/// ignored.
|
||||
pub fn new_with_config_and_priority(
|
||||
display: &EGLDisplay,
|
||||
attributes: GlAttributes,
|
||||
reqs: PixelFormatRequirements,
|
||||
priority: ContextPriority,
|
||||
) -> Result<EGLContext, Error> {
|
||||
Self::new_internal(display, None, Some((attributes, reqs)), Some(priority))
|
||||
}
|
||||
|
||||
/// Create a new configless `EGLContext` from a given `EGLDisplay` sharing resources with another context
|
||||
pub fn new_shared(display: &EGLDisplay, share: &EGLContext) -> Result<EGLContext, Error> {
|
||||
Self::new_internal(display, Some(share), None, None)
|
||||
}
|
||||
|
||||
/// Create a new configless `EGLContext` with the specified priority from a given `EGLDisplay` sharing resources with another context
|
||||
///
|
||||
/// Note: The priority is a hint that might be ignored by the underlying platform.
|
||||
/// It also requires `EGL_IMG_context_priority` to be available, otherwise the priority will be
|
||||
/// ignored.
|
||||
pub fn new_shared_with_priority(
|
||||
display: &EGLDisplay,
|
||||
share: &EGLContext,
|
||||
priority: ContextPriority,
|
||||
) -> Result<EGLContext, Error> {
|
||||
Self::new_internal(display, Some(share), None, Some(priority))
|
||||
}
|
||||
|
||||
/// Create a new `EGLContext` from a given `EGLDisplay` and configuration requirements sharing resources with another context
|
||||
pub fn new_shared_with_config(
|
||||
display: &EGLDisplay,
|
||||
share: &EGLContext,
|
||||
attributes: GlAttributes,
|
||||
reqs: PixelFormatRequirements,
|
||||
) -> Result<EGLContext, Error> {
|
||||
Self::new_internal(display, Some(share), Some((attributes, reqs)), None)
|
||||
}
|
||||
|
||||
/// Create a new `EGLContext` with the specified priority from a given `EGLDisplay` and configuration requirements sharing resources with another context
|
||||
///
|
||||
/// Note: The priority is a hint that might be ignored by the underlying platform.
|
||||
/// It also requires `EGL_IMG_context_priority` to be available, otherwise the priority will be
|
||||
/// ignored.
|
||||
pub fn new_shared_with_config_and_priority(
|
||||
display: &EGLDisplay,
|
||||
share: &EGLContext,
|
||||
attributes: GlAttributes,
|
||||
reqs: PixelFormatRequirements,
|
||||
priority: ContextPriority,
|
||||
) -> Result<EGLContext, Error> {
|
||||
Self::new_internal(display, Some(share), Some((attributes, reqs)), Some(priority))
|
||||
}
|
||||
|
||||
fn new_internal(
|
||||
display: &EGLDisplay,
|
||||
shared: Option<&EGLContext>,
|
||||
config: Option<(GlAttributes, PixelFormatRequirements)>,
|
||||
priority: Option<ContextPriority>,
|
||||
) -> Result<EGLContext, Error> {
|
||||
let span = info_span!(parent: &display.span, "egl_context", ptr = tracing::field::Empty, shared = tracing::field::Empty);
|
||||
let _guard = span.enter();
|
||||
|
||||
if let Some(shared) = shared {
|
||||
span.record("shared", shared.context as usize);
|
||||
}
|
||||
|
||||
let (pixel_format, config_id) = match config {
|
||||
Some((attributes, reqs)) => {
|
||||
let (format, config_id) = display.choose_config(attributes, reqs)?;
|
||||
(Some(format), config_id)
|
||||
}
|
||||
None => {
|
||||
if !display
|
||||
.extensions()
|
||||
.iter()
|
||||
.any(|x| x == "EGL_KHR_no_config_context")
|
||||
&& !display
|
||||
.extensions()
|
||||
.iter()
|
||||
.any(|x| x == "EGL_MESA_configless_context")
|
||||
&& !display
|
||||
.extensions()
|
||||
.iter()
|
||||
.any(|x| x == "EGL_KHR_surfaceless_context")
|
||||
{
|
||||
return Err(Error::EglExtensionNotSupported(&[
|
||||
"EGL_KHR_no_config_context",
|
||||
"EGL_MESA_configless_context",
|
||||
"EGL_KHR_surfaceless_context",
|
||||
]));
|
||||
}
|
||||
(None, ffi::egl::NO_CONFIG_KHR)
|
||||
}
|
||||
};
|
||||
|
||||
let mut context_attributes = Vec::with_capacity(12);
|
||||
|
||||
if let Some((attributes, _)) = config {
|
||||
let version = attributes.version;
|
||||
|
||||
if display.get_egl_version() >= (1, 5)
|
||||
|| display.extensions().iter().any(|s| s == "EGL_KHR_create_context")
|
||||
{
|
||||
trace!("Setting CONTEXT_MAJOR_VERSION to {}", version.0);
|
||||
context_attributes.push(ffi::egl::CONTEXT_MAJOR_VERSION as i32);
|
||||
context_attributes.push(version.0 as i32);
|
||||
trace!("Setting CONTEXT_MINOR_VERSION to {}", version.1);
|
||||
context_attributes.push(ffi::egl::CONTEXT_MINOR_VERSION as i32);
|
||||
context_attributes.push(version.1 as i32);
|
||||
|
||||
if attributes.debug && display.get_egl_version() >= (1, 5) {
|
||||
trace!("Setting CONTEXT_OPENGL_DEBUG to TRUE");
|
||||
context_attributes.push(ffi::egl::CONTEXT_OPENGL_DEBUG as i32);
|
||||
context_attributes.push(ffi::egl::TRUE as i32);
|
||||
}
|
||||
|
||||
context_attributes.push(ffi::egl::CONTEXT_FLAGS_KHR as i32);
|
||||
context_attributes.push(0);
|
||||
} else if display.get_egl_version() >= (1, 3) {
|
||||
trace!("Setting CONTEXT_CLIENT_VERSION to {}", version.0);
|
||||
context_attributes.push(ffi::egl::CONTEXT_CLIENT_VERSION as i32);
|
||||
context_attributes.push(version.0 as i32);
|
||||
}
|
||||
} else {
|
||||
trace!("Setting CONTEXT_CLIENT_VERSION to 2");
|
||||
context_attributes.push(ffi::egl::CONTEXT_CLIENT_VERSION as i32);
|
||||
context_attributes.push(2);
|
||||
}
|
||||
|
||||
let has_context_priority = display
|
||||
.extensions()
|
||||
.iter()
|
||||
.any(|x| x == "EGL_IMG_context_priority");
|
||||
if let Some(priority) = priority {
|
||||
if !has_context_priority {
|
||||
warn!(
|
||||
?priority,
|
||||
"ignoring requested context priority, EGL_IMG_context_priority not supported"
|
||||
);
|
||||
} else {
|
||||
context_attributes.push(ffi::egl::CONTEXT_PRIORITY_LEVEL_IMG as i32);
|
||||
context_attributes.push(Into::<ffi::egl::types::EGLenum>::into(priority) as i32);
|
||||
}
|
||||
}
|
||||
|
||||
context_attributes.push(ffi::egl::NONE as i32);
|
||||
|
||||
trace!("Creating EGL context...");
|
||||
let context = wrap_egl_call_ptr(|| unsafe {
|
||||
ffi::egl::CreateContext(
|
||||
**display.get_display_handle(),
|
||||
config_id,
|
||||
shared
|
||||
.map(|context| context.context)
|
||||
.unwrap_or(ffi::egl::NO_CONTEXT),
|
||||
context_attributes.as_ptr(),
|
||||
)
|
||||
})
|
||||
.map_err(Error::CreationFailed)?;
|
||||
span.record("ptr", context as usize);
|
||||
|
||||
let context_priority = if has_context_priority {
|
||||
let mut context_priority = 0;
|
||||
let res = wrap_egl_call_bool(|| unsafe {
|
||||
ffi::egl::QueryContext(
|
||||
**display.get_display_handle(),
|
||||
context,
|
||||
ffi::egl::CONTEXT_PRIORITY_LEVEL_IMG as ffi::egl::types::EGLint,
|
||||
&mut context_priority,
|
||||
)
|
||||
});
|
||||
|
||||
if res.is_ok() {
|
||||
match ContextPriority::try_from(context_priority as ffi::egl::types::EGLenum) {
|
||||
Ok(context_priority) => Some(context_priority),
|
||||
Err(_) => {
|
||||
warn!(context_priority, "failed to parse context priority");
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
warn!("failed to query context priority");
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if priority.is_some() && has_context_priority && priority != context_priority {
|
||||
warn!(requested = ?priority, got = ?context_priority, "failed to set context priority");
|
||||
}
|
||||
|
||||
info!(priority = ?context_priority, "EGL context created");
|
||||
|
||||
drop(_guard);
|
||||
Ok(EGLContext {
|
||||
context,
|
||||
display: display.clone(),
|
||||
config_id,
|
||||
pixel_format,
|
||||
user_data: if let Some(shared) = shared {
|
||||
shared.user_data.clone()
|
||||
} else {
|
||||
Arc::new(UserDataMap::default())
|
||||
},
|
||||
externally_managed: false,
|
||||
span,
|
||||
})
|
||||
}
|
||||
|
||||
/// Makes the OpenGL context the current context in the current thread with no surface bound.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is marked unsafe, because the context cannot be made current on another thread without
|
||||
/// being unbound again (see [`EGLContext::unbind`]).
|
||||
#[instrument(level = "trace", skip_all, parent = &self.span, err)]
|
||||
#[profiling::function]
|
||||
pub unsafe fn make_current(&self) -> Result<(), MakeCurrentError> {
|
||||
wrap_egl_call_bool(|| unsafe {
|
||||
ffi::egl::MakeCurrent(
|
||||
**self.display.get_display_handle(),
|
||||
ffi::egl::NO_SURFACE,
|
||||
ffi::egl::NO_SURFACE,
|
||||
self.context,
|
||||
)
|
||||
})
|
||||
.map(|_| ())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Makes the OpenGL context the current context in the current thread with a surface to
|
||||
/// read/draw to.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is marked unsafe, because the context cannot be made current on another thread without
|
||||
/// being unbound again (see [`EGLContext::unbind`]).
|
||||
#[profiling::function]
|
||||
pub unsafe fn make_current_with_surface(&self, surface: &EGLSurface) -> Result<(), MakeCurrentError> {
|
||||
unsafe { self.make_current_with_draw_and_read_surface(surface, surface) }
|
||||
}
|
||||
|
||||
/// Makes the OpenGL context the current context in the current thread with surfaces to
|
||||
/// read/draw to.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function is marked unsafe, because the context cannot be made current on another thread without
|
||||
/// being unbound again (see [`EGLContext::unbind`]).
|
||||
#[instrument(level = "trace", skip_all, parent = &self.span, err)]
|
||||
#[profiling::function]
|
||||
pub unsafe fn make_current_with_draw_and_read_surface(
|
||||
&self,
|
||||
draw_surface: &EGLSurface,
|
||||
read_surface: &EGLSurface,
|
||||
) -> Result<(), MakeCurrentError> {
|
||||
let draw_surface_ptr = draw_surface.surface.load(Ordering::SeqCst);
|
||||
let read_surface_ptr = read_surface.surface.load(Ordering::SeqCst);
|
||||
wrap_egl_call_bool(|| unsafe {
|
||||
ffi::egl::MakeCurrent(
|
||||
**self.display.get_display_handle(),
|
||||
draw_surface_ptr,
|
||||
read_surface_ptr,
|
||||
self.context,
|
||||
)
|
||||
})
|
||||
.map(|_| ())
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Returns true if the OpenGL context is the current one in the thread.
|
||||
pub fn is_current(&self) -> bool {
|
||||
unsafe { std::ptr::eq(ffi::egl::GetCurrentContext(), self.context as *const _) }
|
||||
}
|
||||
|
||||
/// Returns true if the OpenGL context is (possibly) shared with another.
|
||||
///
|
||||
/// Externally managed contexts created with `EGLContext::from_raw`
|
||||
/// are always considered shared by this function.
|
||||
pub fn is_shared(&self) -> bool {
|
||||
self.externally_managed || Arc::strong_count(&self.user_data) > 1
|
||||
}
|
||||
|
||||
/// Returns the egl config for this context
|
||||
pub fn config_id(&self) -> ffi::egl::types::EGLConfig {
|
||||
self.config_id
|
||||
}
|
||||
|
||||
/// Returns the pixel format of the main framebuffer of the context.
|
||||
pub fn pixel_format(&self) -> Option<PixelFormat> {
|
||||
self.pixel_format
|
||||
}
|
||||
|
||||
/// Unbinds this context from the current thread, if set.
|
||||
///
|
||||
/// This does nothing if this context is not the current context.
|
||||
#[instrument(level = "trace", skip_all, parent = &self.span, err)]
|
||||
#[profiling::function]
|
||||
pub fn unbind(&self) -> Result<(), MakeCurrentError> {
|
||||
if self.is_current() {
|
||||
wrap_egl_call_bool(|| unsafe {
|
||||
ffi::egl::MakeCurrent(
|
||||
**self.display.get_display_handle(),
|
||||
ffi::egl::NO_SURFACE,
|
||||
ffi::egl::NO_SURFACE,
|
||||
ffi::egl::NO_CONTEXT,
|
||||
)
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the display which created this context.
|
||||
pub fn display(&self) -> &EGLDisplay {
|
||||
&self.display
|
||||
}
|
||||
|
||||
/// Returns a list of formats for dmabufs that can be rendered to.
|
||||
pub fn dmabuf_render_formats(&self) -> &FormatSet {
|
||||
self.display.dmabuf_render_formats()
|
||||
}
|
||||
|
||||
/// Returns a list of formats for dmabufs that can be used as textures.
|
||||
pub fn dmabuf_texture_formats(&self) -> &FormatSet {
|
||||
self.display.dmabuf_texture_formats()
|
||||
}
|
||||
|
||||
/// Retrieve user_data associated with this context
|
||||
///
|
||||
/// *Note:* UserData is shared between shared context, if constructed with
|
||||
/// [`new_shared`](EGLContext::new_shared) or [`new_shared_with_config`](EGLContext::new_shared_with_config).
|
||||
pub fn user_data(&self) -> &UserDataMap {
|
||||
&self.user_data
|
||||
}
|
||||
|
||||
/// Get a raw handle to the underlying context.
|
||||
///
|
||||
/// The pointer will become invalid, when this struct is destroyed.
|
||||
pub fn get_context_handle(&self) -> ffi::egl::types::EGLContext {
|
||||
self.context
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EGLContext {
|
||||
fn drop(&mut self) {
|
||||
if !self.externally_managed {
|
||||
let _guard = self.span.enter();
|
||||
unsafe {
|
||||
// We need to ensure the context is unbound, otherwise it egl stalls the destroy call
|
||||
// ignore failures at this point
|
||||
let _ = self.unbind();
|
||||
ffi::egl::DestroyContext(**self.display.get_display_handle(), self.context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attributes to use when creating an OpenGL context.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct GlAttributes {
|
||||
/// Describes the OpenGL API and version that are being requested when a context is created.
|
||||
///
|
||||
/// `(3, 0)` will request a OpenGL ES 3.0 context for example.
|
||||
/// `(2, 0)` is the minimum.
|
||||
pub version: (u8, u8),
|
||||
/// OpenGL profile to use.
|
||||
pub profile: Option<GlProfile>,
|
||||
/// Whether to enable the debug flag of the context.
|
||||
///
|
||||
/// Debug contexts are usually slower but give better error reporting.
|
||||
pub debug: bool,
|
||||
/// Whether to use vsync. If vsync is enabled, calling `swap_buffers` will block until the screen refreshes.
|
||||
/// This is typically used to prevent screen tearing.
|
||||
pub vsync: bool,
|
||||
}
|
||||
|
||||
/// Describes the requested OpenGL context profiles.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum GlProfile {
|
||||
/// Include all the immediate functions and definitions.
|
||||
Compatibility,
|
||||
/// Include all the future-compatible functions and definitions.
|
||||
Core,
|
||||
}
|
||||
|
||||
/// Describes how the backend should choose a pixel format.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct PixelFormatRequirements {
|
||||
/// If `true`, only hardware-accelerated formats will be considered. If `false`, only software renderers.
|
||||
/// `None` means "don't care". Default is `None`.
|
||||
pub hardware_accelerated: Option<bool>,
|
||||
/// Minimum number of bits for the color buffer, excluding alpha. `None` means "don't care". The default is `None`.
|
||||
pub color_bits: Option<u8>,
|
||||
/// If `true`, the color buffer must be in a floating point format. Default is `false`.
|
||||
///
|
||||
/// Using floating points allows you to write values outside the `[0.0, 1.0]` range.
|
||||
pub float_color_buffer: bool,
|
||||
/// Minimum number of bits for the alpha in the color buffer. `None` means "don't care". The default is `None`.
|
||||
pub alpha_bits: Option<u8>,
|
||||
/// Minimum number of bits for the depth buffer. `None` means "don't care". The default value is `None`.
|
||||
pub depth_bits: Option<u8>,
|
||||
/// Minimum number of bits for the depth buffer. `None` means "don't care". The default value is `None`.
|
||||
pub stencil_bits: Option<u8>,
|
||||
/// Contains the minimum number of samples per pixel in the color, depth and stencil buffers.
|
||||
/// `None` means "don't care". Default is `None`. A value of `Some(0)` indicates that multisampling must not be enabled.
|
||||
pub multisampling: Option<u16>,
|
||||
}
|
||||
|
||||
impl PixelFormatRequirements {
|
||||
/// Format selection to get a 8-bit color format with alpha, depth and stencil bits
|
||||
pub fn _8_bit() -> Self {
|
||||
PixelFormatRequirements {
|
||||
hardware_accelerated: Some(true),
|
||||
color_bits: Some(24),
|
||||
float_color_buffer: false,
|
||||
alpha_bits: Some(8),
|
||||
depth_bits: Some(24),
|
||||
stencil_bits: Some(8),
|
||||
multisampling: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Format selection to get a 10-bit color format with alpha, depth and stencil bits
|
||||
pub fn _10_bit() -> Self {
|
||||
PixelFormatRequirements {
|
||||
hardware_accelerated: Some(true),
|
||||
color_bits: Some(30),
|
||||
float_color_buffer: false,
|
||||
alpha_bits: Some(2),
|
||||
depth_bits: Some(24),
|
||||
stencil_bits: Some(8),
|
||||
multisampling: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Format selection to get a 10-bit color format based on floating point values with alpha, depth and stencil bits
|
||||
pub fn _10f_bit() -> Self {
|
||||
PixelFormatRequirements {
|
||||
hardware_accelerated: Some(true),
|
||||
color_bits: Some(48),
|
||||
float_color_buffer: true,
|
||||
alpha_bits: Some(16),
|
||||
depth_bits: Some(24),
|
||||
stencil_bits: Some(8),
|
||||
multisampling: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PixelFormatRequirements {
|
||||
/// Append the requirements to the given attribute list
|
||||
pub fn create_attributes(&self, out: &mut Vec<c_int>) {
|
||||
if let Some(hardware_accelerated) = self.hardware_accelerated {
|
||||
out.push(ffi::egl::CONFIG_CAVEAT as c_int);
|
||||
out.push(if hardware_accelerated {
|
||||
trace!("Setting CONFIG_CAVEAT to NONE");
|
||||
ffi::egl::NONE as c_int
|
||||
} else {
|
||||
trace!("Setting CONFIG_CAVEAT to SLOW_CONFIG");
|
||||
ffi::egl::SLOW_CONFIG as c_int
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(color) = self.color_bits {
|
||||
trace!("Setting RED_SIZE to {}", color / 3);
|
||||
out.push(ffi::egl::RED_SIZE as c_int);
|
||||
out.push((color / 3) as c_int);
|
||||
trace!("Setting GREEN_SIZE to {}", color / 3 + u8::from(color % 3 != 0));
|
||||
out.push(ffi::egl::GREEN_SIZE as c_int);
|
||||
out.push((color / 3 + u8::from(color % 3 != 0)) as c_int);
|
||||
trace!("Setting BLUE_SIZE to {}", color / 3 + u8::from(color % 3 == 2));
|
||||
out.push(ffi::egl::BLUE_SIZE as c_int);
|
||||
out.push((color / 3 + u8::from(color % 3 == 2)) as c_int);
|
||||
}
|
||||
|
||||
if self.float_color_buffer {
|
||||
out.push(ffi::egl::COLOR_COMPONENT_TYPE_EXT as c_int);
|
||||
out.push(ffi::egl::COLOR_COMPONENT_TYPE_FLOAT_EXT as c_int);
|
||||
}
|
||||
|
||||
if let Some(alpha) = self.alpha_bits {
|
||||
trace!("Setting ALPHA_SIZE to {}", alpha);
|
||||
out.push(ffi::egl::ALPHA_SIZE as c_int);
|
||||
out.push(alpha as c_int);
|
||||
}
|
||||
|
||||
if let Some(depth) = self.depth_bits {
|
||||
trace!("Setting DEPTH_SIZE to {}", depth);
|
||||
out.push(ffi::egl::DEPTH_SIZE as c_int);
|
||||
out.push(depth as c_int);
|
||||
}
|
||||
|
||||
if let Some(stencil) = self.stencil_bits {
|
||||
trace!("Setting STENCIL_SIZE to {}", stencil);
|
||||
out.push(ffi::egl::STENCIL_SIZE as c_int);
|
||||
out.push(stencil as c_int);
|
||||
}
|
||||
|
||||
if let Some(multisampling) = self.multisampling {
|
||||
trace!("Setting SAMPLES to {}", multisampling);
|
||||
out.push(ffi::egl::SAMPLES as c_int);
|
||||
out.push(multisampling as c_int);
|
||||
}
|
||||
}
|
||||
}
|
||||
+283
@@ -0,0 +1,283 @@
|
||||
use std::{ffi::CStr, os::raw::c_void, path::PathBuf, ptr};
|
||||
|
||||
use super::{
|
||||
EGLDisplay, EGLError, Error,
|
||||
ffi::{self, egl::types::EGLDeviceEXT},
|
||||
wrap_egl_call_bool, wrap_egl_call_ptr,
|
||||
};
|
||||
|
||||
#[cfg(feature = "backend_drm")]
|
||||
use crate::backend::drm::{DrmNode, NodeType};
|
||||
|
||||
/// safe EGLDevice wrapper
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EGLDevice {
|
||||
pub(super) inner: EGLDeviceEXT,
|
||||
device_extensions: Vec<String>,
|
||||
}
|
||||
|
||||
unsafe impl Send for EGLDevice {}
|
||||
|
||||
impl EGLDevice {
|
||||
/// Returns an iterator which enumerates over the available [`EGLDevices`](EGLDevice) on the system.
|
||||
///
|
||||
/// This function will return an error if the following extensions are not available:
|
||||
/// - [`EGL_EXT_device_base`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_base.txt)
|
||||
/// - [`EGL_EXT_device_enumeration`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_enumeration.txt)
|
||||
/// - [`EGL_EXT_device_query`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_query.txt)
|
||||
pub fn enumerate() -> Result<impl Iterator<Item = EGLDevice>, Error> {
|
||||
// Check the required extensions are present:
|
||||
let extensions = ffi::make_sure_egl_is_loaded()?;
|
||||
|
||||
if !extensions.iter().any(|s| s == "EGL_EXT_device_base") {
|
||||
return Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_base"]));
|
||||
}
|
||||
|
||||
if !extensions.iter().any(|s| s == "EGL_EXT_device_enumeration") {
|
||||
return Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_enumeration"]));
|
||||
}
|
||||
|
||||
if !extensions.iter().any(|s| s == "EGL_EXT_device_query") {
|
||||
return Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_query"]));
|
||||
}
|
||||
|
||||
// Yes, this is marked as `mut` even though the value is never mutated. EGL expects a mutable pointer
|
||||
// for num_devices and will not modify the value if we are asking for pointers to some EGLDeviceEXT.
|
||||
let mut device_amount = 0;
|
||||
wrap_egl_call_bool(|| unsafe {
|
||||
// Passing 0 for max devices and a null-pointer for devices is safe because we indicate we only
|
||||
// want the number of devices.
|
||||
ffi::egl::QueryDevicesEXT(0, ptr::null_mut(), &mut device_amount)
|
||||
})
|
||||
.map_err(Error::QueryDevices)?;
|
||||
|
||||
let mut devices = Vec::with_capacity(device_amount as usize);
|
||||
|
||||
wrap_egl_call_bool(|| unsafe {
|
||||
// SAFETY:
|
||||
// - Vector used as pointer is correct size.
|
||||
// - Device amount will accommodate all available devices because we have checked the size earlier.
|
||||
ffi::egl::QueryDevicesEXT(device_amount, devices.as_mut_ptr(), &mut device_amount)
|
||||
})
|
||||
.map_err(Error::QueryDevices)?;
|
||||
|
||||
// Set the length of the vec so that rust does not think it is still empty.
|
||||
|
||||
// SAFETY:
|
||||
// 1) the vector is pre-allocated to the same size as the amount of returned devices.
|
||||
// 2) EGL has initialized every value in the vector.
|
||||
unsafe { devices.set_len(device_amount as usize) };
|
||||
|
||||
Ok(devices
|
||||
.into_iter()
|
||||
.map(|device| {
|
||||
// SAFETY: We have queried that the extensions are valid and the device pointer is valid.
|
||||
let device_extensions = unsafe { device_extensions(device) }?;
|
||||
Ok(EGLDevice {
|
||||
inner: device,
|
||||
device_extensions,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, EGLError>>()
|
||||
.map_err(Error::QueryDevices)?
|
||||
.into_iter())
|
||||
}
|
||||
|
||||
/// Returns the [`EGLDevices`](EGLDevice) related to the given `EGLDisplay`.
|
||||
///
|
||||
/// This function will return an error if the following extensions are not available:
|
||||
/// - [`EGL_EXT_device_base`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_base.txt)
|
||||
/// - [`EGL_EXT_device_query`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_query.txt)
|
||||
pub fn device_for_display(display: &EGLDisplay) -> Result<EGLDevice, Error> {
|
||||
// Check the required extensions are present:
|
||||
let extensions = ffi::make_sure_egl_is_loaded()?;
|
||||
|
||||
if !extensions.iter().any(|s| s == "EGL_EXT_device_base") {
|
||||
return Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_base"]));
|
||||
}
|
||||
|
||||
if !extensions.iter().any(|s| s == "EGL_EXT_device_query") {
|
||||
return Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_query"]));
|
||||
}
|
||||
|
||||
let mut device: ffi::egl::types::EGLAttrib = 0;
|
||||
if unsafe {
|
||||
ffi::egl::QueryDisplayAttribEXT(
|
||||
display.get_display_handle().handle,
|
||||
ffi::egl::DEVICE_EXT as i32,
|
||||
&mut device as *mut _,
|
||||
)
|
||||
} != ffi::egl::TRUE
|
||||
{
|
||||
return Err(Error::DisplayNotSupported);
|
||||
}
|
||||
|
||||
let device = device as EGLDeviceEXT;
|
||||
|
||||
// Per the EGL specification:
|
||||
//
|
||||
// > Functions with a return type of EGLDeviceEXT will return this value on failure: EGL_NO_DEVICE_EXT
|
||||
if device == ffi::egl::NO_DEVICE_EXT {
|
||||
return Err(Error::DisplayNotSupported);
|
||||
}
|
||||
|
||||
// SAFETY: We have queried that the extensions are valid and the device pointer is valid.
|
||||
let device_extensions = unsafe { device_extensions(device) }.map_err(Error::QueryDevices)?;
|
||||
Ok(EGLDevice {
|
||||
inner: device,
|
||||
device_extensions,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a list of extensions the device supports.
|
||||
pub fn extensions(&self) -> Vec<String> {
|
||||
self.device_extensions.clone()
|
||||
}
|
||||
|
||||
/// Returns the path to the drm node of this EGLDevice.
|
||||
///
|
||||
/// This function will return an error if the following extensions are not available:
|
||||
/// - [`EGL_EXT_device_drm`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_drm.txt)
|
||||
pub fn drm_device_path(&self) -> Result<PathBuf, Error> {
|
||||
if !self.extensions().contains(&"EGL_EXT_device_drm".to_owned()) {
|
||||
Err(Error::EglExtensionNotSupported(&["EGL_EXT_device_drm"]))
|
||||
} else {
|
||||
let raw_path = wrap_egl_call_ptr(|| unsafe {
|
||||
ffi::egl::QueryDeviceStringEXT(
|
||||
self.inner,
|
||||
ffi::egl::DRM_DEVICE_FILE_EXT as ffi::egl::types::EGLint,
|
||||
)
|
||||
})
|
||||
.map_err(Error::QueryDeviceProperty)?;
|
||||
if raw_path.is_null() {
|
||||
return Err(Error::EmptyDeviceProperty);
|
||||
}
|
||||
|
||||
// This is safe because of the following:
|
||||
// 1) The string returned by `eglQueryDeviceStringEXT` is string which will exist as long
|
||||
// as the EGLDisplay is valid. Since the pointer is only used in this function, the
|
||||
// lifetime of the pointer will fulfil Rust's CStr requirements on lifetime.
|
||||
// 2) The string returned by EGL is null terminated.
|
||||
let device_path = unsafe { CStr::from_ptr(raw_path) }
|
||||
.to_str()
|
||||
// EGL ensures the string is valid UTF-8
|
||||
.expect("Non-UTF8 device path name");
|
||||
|
||||
Ok(PathBuf::from(device_path))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the path to the render node of this EGLDevice.
|
||||
///
|
||||
/// This function will return an error if the following extensions are not available:
|
||||
/// - [`EGL_EXT_device_drm_render_node`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_drm_render_node.txt)
|
||||
pub fn render_device_path(&self) -> Result<PathBuf, Error> {
|
||||
if !self
|
||||
.extensions()
|
||||
.contains(&"EGL_EXT_device_drm_render_node".to_owned())
|
||||
{
|
||||
Err(Error::EglExtensionNotSupported(&[
|
||||
"EGL_EXT_device_drm_render_node",
|
||||
]))
|
||||
} else {
|
||||
let raw_path = wrap_egl_call_ptr(|| unsafe {
|
||||
ffi::egl::QueryDeviceStringEXT(
|
||||
self.inner,
|
||||
ffi::egl::DRM_RENDER_NODE_FILE_EXT as ffi::egl::types::EGLint,
|
||||
)
|
||||
})
|
||||
.map_err(Error::QueryDeviceProperty)?;
|
||||
if raw_path.is_null() {
|
||||
return Err(Error::EmptyDeviceProperty);
|
||||
}
|
||||
|
||||
// This is safe because of the following:
|
||||
// 1) The string returned by `eglQueryDeviceStringEXT` is string which will exist as long
|
||||
// as the EGLDisplay is valid. Since the pointer is only used in this function, the
|
||||
// lifetime of the pointer will fulfil Rust's CStr requirements on lifetime.
|
||||
// 2) The string returned by EGL is null terminated.
|
||||
let device_path = unsafe { CStr::from_ptr(raw_path) }
|
||||
.to_str()
|
||||
// EGL ensures the string is valid UTF-8
|
||||
.expect("Non-UTF8 device path name");
|
||||
|
||||
Ok(PathBuf::from(device_path))
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the drm node belonging to this device.
|
||||
/// Tries to obtain a render_node first through `EGL_EXT_device_drm_render_node`
|
||||
/// (see also [`EGLDevice::render_device_path`]) and then falls back to
|
||||
/// get a render_node from `EGL_EXT_device_drm` (see also [`EGLDevice::drm_device_path`]).
|
||||
/// If both fail to produce a render node, whichever device returned by
|
||||
/// `EGL_EXT_device_drm` is returned.
|
||||
#[cfg(feature = "backend_drm")]
|
||||
pub fn try_get_render_node(&self) -> Result<Option<DrmNode>, Error> {
|
||||
// first lets try to get a render_node directly
|
||||
match self
|
||||
.render_device_path()
|
||||
.ok()
|
||||
.and_then(|path| DrmNode::from_path(path).ok())
|
||||
{
|
||||
Some(node) => Ok(Some(node)),
|
||||
// else we take a drm_path
|
||||
None => {
|
||||
let path = self.drm_device_path()?;
|
||||
let node = DrmNode::from_path(path).ok();
|
||||
// and try to convert it to a render_node
|
||||
Ok(node.map(|node| {
|
||||
node.node_with_type(NodeType::Render)
|
||||
.and_then(Result::ok)
|
||||
// and otherwise go with whatever we got initially
|
||||
.unwrap_or(node)
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Queries if device is not backed by any actual device node and simply renders into client memory.
|
||||
///
|
||||
/// Note: This simply tests presence of the [`EGL_MESA_device_software`](https://gitlab.freedesktop.org/mesa/mesa/-/blob/main/docs/_extra/specs/EGL_MESA_device_software.txt) extension.
|
||||
pub fn is_software(&self) -> bool {
|
||||
// Note: EGL_MESA_device_software requires EGL_EXT_device_query which we already test for initializing the device,
|
||||
// so there is no need to re-test it here again.
|
||||
self.extensions().contains(&"EGL_MESA_device_software".to_owned())
|
||||
}
|
||||
|
||||
/// Returns the pointer to the raw [`EGLDevice`].
|
||||
///
|
||||
/// The pointer will become invalid, when this struct is destroyed.
|
||||
pub fn get_device_handle(&self) -> *const c_void {
|
||||
self.inner
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all device extensions a device supports.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - The `device` must be a valid pointer to an `EGLDeviceEXT`.
|
||||
/// - The following extensions must be supported by the display which provides the device:
|
||||
/// - [`EGL_EXT_device_base`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_base.txt)
|
||||
/// - [`EGL_EXT_device_query`](https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_device_query.txt)
|
||||
unsafe fn device_extensions(device: EGLDeviceEXT) -> Result<Vec<String>, EGLError> {
|
||||
let raw_extensions = wrap_egl_call_ptr(|| unsafe {
|
||||
ffi::egl::QueryDeviceStringEXT(device, ffi::egl::EXTENSIONS as ffi::egl::types::EGLint)
|
||||
})?;
|
||||
|
||||
// SAFETY:
|
||||
// 1) The string returned by `eglQueryDeviceStringEXT` is string which will exist as long
|
||||
// as the EGLDisplay is valid. Safety requirements for the function ensure this.
|
||||
// 2) The string returned by EGL is null terminated.
|
||||
let c_extensions = unsafe { CStr::from_ptr(raw_extensions) };
|
||||
|
||||
Ok(c_extensions
|
||||
.to_str()
|
||||
// EGL ensures the string is valid UTF-8
|
||||
.expect("Non-UTF8 device extension name")
|
||||
// Each extension is space separated (0x20) in the pointer, so strlen cannot return an improper length.
|
||||
.split_whitespace()
|
||||
// Take an owned copy so we do not point to garbage if EGL somehow vanishes.
|
||||
.map(ToOwned::to_owned)
|
||||
.collect::<Vec<_>>())
|
||||
}
|
||||
Loaded 100 of 391 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user