Compare commits
84 commits
post-1.6-m
...
local/yoda
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f3a82beb4 | |||
| 11f60acc94 | |||
|
|
41497b42d9 | ||
|
|
3d55cba06c | ||
|
|
895ce91543 | ||
|
|
115cdac039 | ||
|
|
7596cf7f2f | ||
|
|
295eaa8f63 | ||
|
|
569cfa6442 | ||
|
|
4e72d2ce98 | ||
|
|
6e0aea0eb2 | ||
|
|
b975370968 | ||
|
|
af025856c7 | ||
|
|
d8e4683be8 | ||
|
|
ed7814e51e | ||
|
|
467d1fbc7f | ||
|
|
e7b65b1fc8 | ||
|
|
4d854ecb94 | ||
|
|
9f746ea3ff | ||
| 30d8868cca | |||
| 51c6cfedf6 | |||
|
|
e5010c23c9 | ||
|
|
f60d2c8436 | ||
|
|
58837e27b8 | ||
|
|
6645edff64 | ||
|
|
0fbd4574ef | ||
|
|
d36480c1b5 | ||
|
|
50c170c355 | ||
|
|
26daf75d93 | ||
|
|
cd881a4df2 | ||
|
|
651f701f84 | ||
|
|
a7349b72e3 | ||
|
|
f9e02f4321 | ||
| a103e7619b | |||
| a69f659983 | |||
|
|
a55785993e | ||
|
|
7d1daab7bc | ||
|
|
6026520bd3 | ||
|
|
8ad541dbe3 | ||
|
|
f61c0f9f76 | ||
|
|
0f56d214b9 | ||
|
|
057e7b72cc | ||
|
|
e8caf335b3 | ||
|
|
f8344126a1 | ||
|
|
bd0b834128 | ||
|
|
1c8a0070c0 | ||
|
|
e563dc85bf | ||
|
|
2a3b4dca5f | ||
|
|
0e15b14ab2 | ||
|
|
6bf75e1c10 | ||
|
|
bb95a57f5a | ||
|
|
87d73dbce3 | ||
|
|
f00ff834c9 | ||
|
|
b8824128a0 | ||
|
|
e65f6608a4 | ||
|
|
85564f0336 | ||
|
|
a66f629a4e | ||
|
|
67aa6a0c49 | ||
|
|
9125f8a2a7 | ||
|
|
6467f79025 | ||
|
|
ca9dd27350 | ||
|
|
8ec1ffb740 | ||
|
|
0d75928579 | ||
|
|
e62e35e5cc | ||
|
|
8691956644 | ||
|
|
9059873c84 | ||
|
|
e93ec67a11 | ||
|
|
a7d31a75d2 | ||
|
|
b407ed560a | ||
|
|
a830784f11 | ||
|
|
9fe22c86b6 | ||
|
|
94178034dd | ||
|
|
158e2b7f91 | ||
| af6d31f413 | |||
|
|
5c93094574 | ||
|
|
31827ed240 | ||
|
|
80b8eb47d7 | ||
|
|
01b79c3eae | ||
|
|
8304b18886 | ||
|
|
738e0b3124 | ||
| 8ac198de15 | |||
| be1fab51ea | |||
|
|
f20de50e41 | ||
|
|
04026259e0 |
72 changed files with 5110 additions and 1394 deletions
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -1,8 +1,10 @@
|
||||||
- [ ] I have disclosed use of any AI generated code in my commit messages.
|
Replace this text with a description of your changes, and why these changes are important. Read and acknowledge the list below. PRs without a completed checkbox will be closed.
|
||||||
- If you are using an LLM, and do not fully understand the changes it is making to the code base, do not create a PR.
|
|
||||||
- In our experience, AI generated code often results in overly complex code that lacks enough context for a proper fix or feature inclusion. This results in considerably longer code reviews. Due to this, AI authored or partially authored PRs may be closed without comment.
|
|
||||||
- [ ] I understand these changes in full and will be able to respond to review comments.
|
|
||||||
- [ ] My change is accurately described in the commit message.
|
|
||||||
- [ ] My contribution is tested and working as described.
|
|
||||||
- [ ] I have read the [Developer Certificate of Origin](https://developercertificate.org/) and certify my contribution under its conditions.
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
- I have not included any LLM (also known as AI) generated content in this PR, including code, comments, and descriptions.
|
||||||
|
- I understand these changes in full and will be able to respond to review comments.
|
||||||
|
- My change is accurately described in the commit message.
|
||||||
|
- My contribution is tested and working as described.
|
||||||
|
- I have read the [Developer Certificate of Origin](https://developercertificate.org/) and certify my contribution under its conditions.
|
||||||
|
- [ ] I have read and followed all of the above requirements. For more information, see our [contributor guide](https://github.com/pop-os/pop/blob/master/CONTRIBUTING.md).
|
||||||
|
|
|
||||||
55
.github/workflows/ci.yml
vendored
55
.github/workflows/ci.yml
vendored
|
|
@ -13,18 +13,12 @@ jobs:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
- name: Rust toolchain
|
- name: Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
with:
|
with:
|
||||||
components: rustfmt
|
components: rustfmt
|
||||||
- name: Cargo cache
|
rustflags: ""
|
||||||
uses: actions/cache@v5
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cargo/registry
|
|
||||||
~/.cargo/git
|
|
||||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
|
|
||||||
- name: Format
|
- name: Format
|
||||||
run: cargo +stable fmt --all -- --check
|
run: cargo fmt --all -- --check
|
||||||
|
|
||||||
clippy-check:
|
clippy-check:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
@ -32,20 +26,14 @@ jobs:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
- name: Rust toolchain
|
- name: Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
with:
|
with:
|
||||||
components: clippy
|
components: clippy
|
||||||
- name: Cargo cache
|
rustflags: ""
|
||||||
uses: actions/cache@v5
|
|
||||||
with:
|
|
||||||
path: |
|
|
||||||
~/.cargo/registry
|
|
||||||
~/.cargo/git
|
|
||||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
|
|
||||||
- name: System dependencies
|
- name: System dependencies
|
||||||
run: sudo apt-get update; sudo apt-get install libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev libdisplay-info-dev
|
run: sudo apt-get update; sudo apt-get install 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
|
- name: Clippy
|
||||||
run: cargo +stable clippy --all-features -- -D warnings
|
run: cargo clippy --all-features -- -D warnings
|
||||||
|
|
||||||
check-features:
|
check-features:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
@ -53,20 +41,15 @@ jobs:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
- name: Rust toolchain
|
- name: Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
- name: Cargo cache
|
|
||||||
uses: actions/cache@v5
|
|
||||||
with:
|
with:
|
||||||
path: |
|
rustflags: ""
|
||||||
~/.cargo/registry
|
|
||||||
~/.cargo/git
|
|
||||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
|
|
||||||
- name: System dependencies
|
- name: System dependencies
|
||||||
run: sudo apt-get update; sudo apt-get install libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev libdisplay-info-dev
|
run: sudo apt-get update; sudo apt-get install libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libseat-dev libdisplay-info-dev
|
||||||
- run: cargo +stable check --no-default-features
|
- run: cargo check --no-default-features
|
||||||
- run: cargo +stable check --features debug
|
- run: cargo check --features debug
|
||||||
- run: cargo +stable check --features profile-with-tracy
|
- run: cargo check --features profile-with-tracy
|
||||||
- run: cargo +stable check --features profile-with-tracy-gpu
|
- run: cargo check --features profile-with-tracy-gpu
|
||||||
|
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
@ -74,21 +57,11 @@ jobs:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
- name: Rust toolchain
|
- name: Rust toolchain
|
||||||
uses: dtolnay/rust-toolchain@stable
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
- name: Cargo cache
|
|
||||||
uses: actions/cache@v5
|
|
||||||
with:
|
with:
|
||||||
path: |
|
rustflags: ""
|
||||||
~/.cargo/registry
|
|
||||||
~/.cargo/git
|
|
||||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
|
|
||||||
- name: System dependencies
|
- name: System dependencies
|
||||||
run: sudo apt-get update; sudo apt-get install libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libdisplay-info-dev libpixman-1-dev meson ninja-build
|
run: sudo apt-get update; sudo apt-get install libudev-dev libgbm-dev libxkbcommon-dev libegl1-mesa-dev libwayland-dev libinput-dev libdbus-1-dev libsystemd-dev libdisplay-info-dev libpixman-1-dev meson ninja-build
|
||||||
- name: Build cache
|
|
||||||
uses: actions/cache@v5
|
|
||||||
with:
|
|
||||||
path: target
|
|
||||||
key: ${{ runner.os }}-build-${{ hashFiles('**/Cargo.toml') }}
|
|
||||||
- name: Build and install Libseat
|
- name: Build and install Libseat
|
||||||
run: |
|
run: |
|
||||||
wget https://git.sr.ht/~kennylevinsen/seatd/archive/0.9.1.tar.gz -O libseat-source.tar.gz
|
wget https://git.sr.ht/~kennylevinsen/seatd/archive/0.9.1.tar.gz -O libseat-source.tar.gz
|
||||||
|
|
|
||||||
14
.github/workflows/require-checklist.yml
vendored
Normal file
14
.github/workflows/require-checklist.yml
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# From https://github.com/mheap/require-checklist-action, license MIT
|
||||||
|
name: Require Checklist
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, edited, synchronize]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
job1:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: mheap/require-checklist-action@v2
|
||||||
|
with:
|
||||||
|
requireChecklist: true
|
||||||
596
Cargo.lock
generated
596
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
10
Cargo.toml
10
Cargo.toml
|
|
@ -44,7 +44,6 @@ iced_graphics = { path = "../libcosmic/iced/graphics", features = ["geometry"] }
|
||||||
indexmap = "2.13"
|
indexmap = "2.13"
|
||||||
keyframe = "1.1.1"
|
keyframe = "1.1.1"
|
||||||
cosmic = { package = "libcosmic-yoda", path = "../libcosmic", default-features = false, features = [
|
cosmic = { package = "libcosmic-yoda", path = "../libcosmic", default-features = false, features = [
|
||||||
"tokio",
|
|
||||||
"wayland",
|
"wayland",
|
||||||
"multi-window",
|
"multi-window",
|
||||||
] }
|
] }
|
||||||
|
|
@ -53,6 +52,7 @@ log-panics = { version = "2", features = ["with-backtrace"] }
|
||||||
ordered-float = "5.1"
|
ordered-float = "5.1"
|
||||||
png = "0.18"
|
png = "0.18"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
|
resvg = "0.45"
|
||||||
ron = "0.12"
|
ron = "0.12"
|
||||||
rust-embed = { version = "8.11", features = ["debug-embed"] }
|
rust-embed = { version = "8.11", features = ["debug-embed"] }
|
||||||
sanitize-filename = "0.6.0"
|
sanitize-filename = "0.6.0"
|
||||||
|
|
@ -65,7 +65,7 @@ tracing = { version = "0.1.44", features = [
|
||||||
"max_level_debug",
|
"max_level_debug",
|
||||||
"release_max_level_info",
|
"release_max_level_info",
|
||||||
] }
|
] }
|
||||||
tracing-journald = "0.3.2"
|
tracing-journald = { version = "0.3.2", optional = true }
|
||||||
tracing-subscriber = { version = "0.3.22", features = [
|
tracing-subscriber = { version = "0.3.22", features = [
|
||||||
"env-filter",
|
"env-filter",
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
|
|
@ -78,7 +78,6 @@ xdg = "^3.0"
|
||||||
xdg-user = "0.2.1"
|
xdg-user = "0.2.1"
|
||||||
xkbcommon = "0.9"
|
xkbcommon = "0.9"
|
||||||
zbus = "5.14.0"
|
zbus = "5.14.0"
|
||||||
tokio = { version = "1", features = ["rt-multi-thread"] }
|
|
||||||
profiling = { version = "1.0" }
|
profiling = { version = "1.0" }
|
||||||
rustix = { version = "1.1.4", features = ["process"] }
|
rustix = { version = "1.1.4", features = ["process"] }
|
||||||
rand = "0.10"
|
rand = "0.10"
|
||||||
|
|
@ -126,7 +125,7 @@ optional = true
|
||||||
debug = ["egui", "egui_plot", "smithay-egui", "anyhow/backtrace"]
|
debug = ["egui", "egui_plot", "smithay-egui", "anyhow/backtrace"]
|
||||||
default = ["systemd"]
|
default = ["systemd"]
|
||||||
logind = ["logind-zbus"]
|
logind = ["logind-zbus"]
|
||||||
systemd = ["libsystemd", "logind"]
|
systemd = ["libsystemd", "logind", "tracing-journald"]
|
||||||
profile-with-tracy = ["profiling/profile-with-tracy", "tracy-client/default"]
|
profile-with-tracy = ["profiling/profile-with-tracy", "tracy-client/default"]
|
||||||
profile-with-tracy-gpu = ["profile-with-tracy", "smithay/tracy_gpu_profiling"]
|
profile-with-tracy-gpu = ["profile-with-tracy", "smithay/tracy_gpu_profiling"]
|
||||||
|
|
||||||
|
|
@ -178,4 +177,5 @@ dnd = { path = "../window_clipboard/dnd" }
|
||||||
mime = { path = "../window_clipboard/mime" }
|
mime = { path = "../window_clipboard/mime" }
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
smithay = { git = "https://github.com/smithay/smithay.git", rev = "5fb12b8" }
|
smithay = { git = "https://github.com/smithay/smithay.git", rev = "e3d461a" }
|
||||||
|
smithay-egui = { git = "https://github.com/Smithay/smithay-egui.git", rev = "6511552" }
|
||||||
|
|
|
||||||
67
Makefile
67
Makefile
|
|
@ -1,67 +0,0 @@
|
||||||
export prefix ?= /usr
|
|
||||||
sysconfdir ?= /etc
|
|
||||||
bindir = $(prefix)/bin
|
|
||||||
libdir = $(prefix)/lib
|
|
||||||
sharedir = $(prefix)/share
|
|
||||||
|
|
||||||
BINARY = cosmic-comp
|
|
||||||
CARGO_TARGET_DIR ?= target
|
|
||||||
TARGET = debug
|
|
||||||
DEBUG ?= 0
|
|
||||||
|
|
||||||
.PHONY = all clean install uninstall vendor
|
|
||||||
|
|
||||||
ifeq ($(DEBUG),0)
|
|
||||||
TARGET = release
|
|
||||||
ARGS += --release
|
|
||||||
endif
|
|
||||||
|
|
||||||
VENDOR ?= 0
|
|
||||||
ifneq ($(VENDOR),0)
|
|
||||||
ARGS += --offline --locked
|
|
||||||
endif
|
|
||||||
|
|
||||||
TARGET_BIN="$(DESTDIR)$(bindir)/$(BINARY)"
|
|
||||||
|
|
||||||
KEYBINDINGS_CONF="$(DESTDIR)$(sharedir)/cosmic/com.system76.CosmicSettings.Shortcuts/v1/defaults"
|
|
||||||
TILING_EXCEPTIONS_CONF="$(DESTDIR)$(sharedir)/cosmic/com.system76.CosmicSettings.WindowRules/v1/tiling_exception_defaults"
|
|
||||||
|
|
||||||
all: extract-vendor
|
|
||||||
cargo build $(ARGS)
|
|
||||||
|
|
||||||
clean:
|
|
||||||
cargo clean
|
|
||||||
|
|
||||||
distclean:
|
|
||||||
rm -rf .cargo vendor vendor.tar target
|
|
||||||
|
|
||||||
vendor:
|
|
||||||
mkdir -p .cargo
|
|
||||||
cargo vendor | head -n -1 > .cargo/config
|
|
||||||
echo 'directory = "vendor"' >> .cargo/config
|
|
||||||
[ -n "$(SOURCE_GIT_HASH)" ] && printf '\n[env]\nGIT_HASH = "%s"\n' "$(SOURCE_GIT_HASH)" >> .cargo/config || true
|
|
||||||
tar pcf vendor.tar vendor
|
|
||||||
rm -rf vendor
|
|
||||||
|
|
||||||
extract-vendor:
|
|
||||||
ifeq ($(VENDOR),1)
|
|
||||||
rm -rf vendor; tar pxf vendor.tar
|
|
||||||
endif
|
|
||||||
|
|
||||||
install:
|
|
||||||
install -Dm0755 "$(CARGO_TARGET_DIR)/$(TARGET)/$(BINARY)" "$(TARGET_BIN)"
|
|
||||||
install -Dm0644 "data/keybindings.ron" "$(KEYBINDINGS_CONF)"
|
|
||||||
install -Dm0644 "data/tiling-exceptions.ron" "$(TILING_EXCEPTIONS_CONF)"
|
|
||||||
|
|
||||||
install-bare-session: install
|
|
||||||
install -Dm0644 "data/cosmic.desktop" "$(DESTDIR)$(sharedir)/wayland-sessions/cosmic.desktop"
|
|
||||||
install -Dm0644 "data/cosmic-session.target" "$(DESTDIR)$(libdir)/systemd/user/cosmic-session.target"
|
|
||||||
install -Dm0644 "data/cosmic-session-pre.target" "$(DESTDIR)$(libdir)/systemd/user/cosmic-session-pre.target"
|
|
||||||
install -Dm0644 "data/cosmic-comp.service" "$(DESTDIR)$(libdir)/systemd/user/cosmic-comp.service"
|
|
||||||
install -Dm0755 "data/cosmic-service" "$(DESTDIR)/$(bindir)/cosmic-service"
|
|
||||||
|
|
||||||
uninstall:
|
|
||||||
rm "$(TARGET_BIN)" "$(KEYBINDINGS_CONF)"
|
|
||||||
|
|
||||||
uninstall-bare-session:
|
|
||||||
rm "$(DESTDIR)$(sharedir)/wayland-sessions/cosmic.desktop"
|
|
||||||
31
README.md
Normal file
31
README.md
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
# cosmic-comp
|
||||||
|
Compositor for the COSMIC desktop environment
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
This project uses [just](https://github.com/casey/just) as a command runner.
|
||||||
|
|
||||||
|
### Available Commands
|
||||||
|
|
||||||
|
#### Building
|
||||||
|
- `just build-debug` - Compile with debug profile
|
||||||
|
- `just build-release` - Compile with release profile (default)
|
||||||
|
- `just build-vendored` - Compile release profile with vendored dependencies
|
||||||
|
- Requires vendoring first, which can be done with `just vendor`
|
||||||
|
|
||||||
|
#### Code Quality
|
||||||
|
- `just check` - Run clippy linter with pedantic warnings
|
||||||
|
|
||||||
|
#### Installation
|
||||||
|
- `just install` - Install binary and configuration files
|
||||||
|
- `just install-bare-session` - Install with bare session files (wayland session, systemd units)
|
||||||
|
- `just uninstall` - Remove installed files
|
||||||
|
- `just uninstall-bare-session` - Remove installed files including bare session files
|
||||||
|
|
||||||
|
#### Cleanup
|
||||||
|
- `just clean` - Run `cargo clean`
|
||||||
|
- `just clean-dist` - Run `cargo clean` and remove vendored dependencies
|
||||||
|
|
||||||
|
#### Vendoring
|
||||||
|
- `just vendor` - Vendor dependencies locally and create vendor.tar
|
||||||
|
- `just vendor-extract` - Extract vendored dependencies from vendor.tar
|
||||||
53
cargo.just
Normal file
53
cargo.just
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
cargo-target-dir := env('CARGO_TARGET_DIR', 'target')
|
||||||
|
|
||||||
|
# Compile with debug profile
|
||||||
|
build-debug *args:
|
||||||
|
cargo build {{ args }}
|
||||||
|
|
||||||
|
# Compile with release profile
|
||||||
|
build-release *args: (build-debug '--release' args)
|
||||||
|
|
||||||
|
# Compile with a vendored tarball
|
||||||
|
build-vendored *args: vendor-extract (build-release '--frozen --offline' args)
|
||||||
|
|
||||||
|
# Check for errors and linter warnings
|
||||||
|
check *args:
|
||||||
|
cargo clippy --all-features {{ args }} -- -W clippy::pedantic
|
||||||
|
|
||||||
|
# Remove Cargo build artifacts
|
||||||
|
[no-cd]
|
||||||
|
clean:
|
||||||
|
cargo clean
|
||||||
|
|
||||||
|
# Also remove .cargo and vendored dependencies
|
||||||
|
[no-cd]
|
||||||
|
clean-dist: clean
|
||||||
|
rm -rf .cargo vendor vendor.tar
|
||||||
|
|
||||||
|
# Vendor Cargo dependencies locally
|
||||||
|
[no-cd]
|
||||||
|
vendor:
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
mkdir -p .cargo
|
||||||
|
cargo vendor --sync Cargo.toml | head -n -1 > .cargo/config.toml
|
||||||
|
echo 'directory = "vendor"' >> .cargo/config.toml
|
||||||
|
echo >> .cargo/config.toml
|
||||||
|
echo '[env]' >> .cargo/config.toml
|
||||||
|
if [ -n "${SOURCE_DATE_EPOCH}" ]
|
||||||
|
then
|
||||||
|
source_date="$(date -d "@${SOURCE_DATE_EPOCH}" "+%Y-%m-%d")"
|
||||||
|
echo "VERGEN_GIT_COMMIT_DATE = \"${source_date}\"" >> .cargo/config.toml
|
||||||
|
fi
|
||||||
|
if [ -n "${SOURCE_GIT_HASH}" ]
|
||||||
|
then
|
||||||
|
echo "GIT_HASH = \"${SOURCE_GIT_HASH}\"" >> .cargo/config.toml
|
||||||
|
fi
|
||||||
|
tar pcf vendor.tar .cargo vendor
|
||||||
|
rm -rf .cargo vendor
|
||||||
|
|
||||||
|
# Extracts vendored dependencies
|
||||||
|
[no-cd]
|
||||||
|
[private]
|
||||||
|
vendor-extract:
|
||||||
|
rm -rf vendor
|
||||||
|
tar pxf vendor.tar
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
use cosmic_config::{CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry};
|
use cosmic_config::{CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::input::TouchpadOverride;
|
use crate::input::TouchpadOverride;
|
||||||
|
|
||||||
|
|
@ -66,6 +67,13 @@ impl Default for AppearanceConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub enum DecorationPreference {
|
||||||
|
#[default]
|
||||||
|
ClientSide,
|
||||||
|
ServerSide,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, CosmicConfigEntry)]
|
#[derive(Clone, Debug, PartialEq, CosmicConfigEntry)]
|
||||||
#[version = 1]
|
#[version = 1]
|
||||||
pub struct CosmicCompConfig {
|
pub struct CosmicCompConfig {
|
||||||
|
|
@ -99,9 +107,12 @@ pub struct CosmicCompConfig {
|
||||||
pub edge_snap_threshold: u32,
|
pub edge_snap_threshold: u32,
|
||||||
pub accessibility_zoom: ZoomConfig,
|
pub accessibility_zoom: ZoomConfig,
|
||||||
pub appearance_settings: AppearanceConfig,
|
pub appearance_settings: AppearanceConfig,
|
||||||
/// Hide the cursor after this many seconds of pointer inactivity (None disables)
|
/// When the cursor hides itself: idle, fullscreen idle, typing, touch
|
||||||
pub cursor_hide_timeout: Option<u32>,
|
pub cursor_hide: CursorHideConfig,
|
||||||
|
/// Briefly magnify the cursor when the pointer is shaken, to help locate it
|
||||||
|
pub cursor_shake_to_find: bool,
|
||||||
pub activation_policy: ActivationPolicy,
|
pub activation_policy: ActivationPolicy,
|
||||||
|
pub decoration_preference: DecorationPreference,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CosmicCompConfig {
|
impl Default for CosmicCompConfig {
|
||||||
|
|
@ -138,8 +149,10 @@ impl Default for CosmicCompConfig {
|
||||||
edge_snap_threshold: 0,
|
edge_snap_threshold: 0,
|
||||||
accessibility_zoom: ZoomConfig::default(),
|
accessibility_zoom: ZoomConfig::default(),
|
||||||
appearance_settings: AppearanceConfig::default(),
|
appearance_settings: AppearanceConfig::default(),
|
||||||
cursor_hide_timeout: None,
|
cursor_hide: CursorHideConfig::default(),
|
||||||
|
cursor_shake_to_find: true,
|
||||||
activation_policy: ActivationPolicy::default(),
|
activation_policy: ActivationPolicy::default(),
|
||||||
|
decoration_preference: DecorationPreference::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -186,6 +199,81 @@ fn default_repeat_delay() -> u32 {
|
||||||
600
|
600
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What the cursor idle timer should do when it fires.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum HideDecision {
|
||||||
|
/// Nothing applies in this context; drop the timer until the next activity.
|
||||||
|
Drop,
|
||||||
|
/// Hide the cursor now.
|
||||||
|
Hide,
|
||||||
|
/// Not yet — re-arm for this long.
|
||||||
|
RearmAfter(Duration),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// When the cursor hides itself. Every trigger is revealed by pointer input, so
|
||||||
|
/// these differ only in what arms the hide and after how long.
|
||||||
|
// `default`: a trigger added later must not make every existing file fail to
|
||||||
|
// deserialize, which `get_entry` would swallow as "reset everyone to defaults".
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct CursorHideConfig {
|
||||||
|
/// Seconds of pointer inactivity before hiding, anywhere. `None` disables.
|
||||||
|
pub idle_timeout: Option<u32>,
|
||||||
|
/// Seconds of pointer inactivity before hiding over a fullscreen window.
|
||||||
|
/// Shortens `idle_timeout` in fullscreen; it never lengthens it.
|
||||||
|
pub fullscreen_idle_timeout: Option<u32>,
|
||||||
|
/// Hide as soon as a key is pressed.
|
||||||
|
pub while_typing: bool,
|
||||||
|
/// Hide on touch input, until the pointer next moves.
|
||||||
|
pub after_touch: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CursorHideConfig {
|
||||||
|
/// The shortest timeout that applies right now, if any.
|
||||||
|
pub fn effective_timeout(&self, fullscreen: bool) -> Option<Duration> {
|
||||||
|
let secs = if fullscreen {
|
||||||
|
match (self.idle_timeout, self.fullscreen_idle_timeout) {
|
||||||
|
(Some(a), Some(b)) => Some(a.min(b)),
|
||||||
|
(a, b) => a.or(b),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.idle_timeout
|
||||||
|
}?;
|
||||||
|
Some(Duration::from_secs(secs as u64))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What to do when the idle timer fires after `elapsed` without pointer input.
|
||||||
|
pub fn resolve(&self, elapsed: Duration, fullscreen: bool) -> HideDecision {
|
||||||
|
match self.effective_timeout(fullscreen) {
|
||||||
|
None => HideDecision::Drop,
|
||||||
|
Some(timeout) if elapsed >= timeout => HideDecision::Hide,
|
||||||
|
Some(timeout) => HideDecision::RearmAfter(timeout - elapsed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How long to wait before the idle timer should next fire, given how long
|
||||||
|
/// it has been since the last pointer event. Pessimistic: fullscreen only
|
||||||
|
/// ever shortens a timeout, so arming as if fullscreen cannot fire too
|
||||||
|
/// late, and `resolve` corrects an early fire once the context is known.
|
||||||
|
pub fn arm_delay(&self, since_pointer_activity: Duration) -> Option<Duration> {
|
||||||
|
Some(
|
||||||
|
self.effective_timeout(true)?
|
||||||
|
.saturating_sub(since_pointer_activity),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CursorHideConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
CursorHideConfig {
|
||||||
|
idle_timeout: None,
|
||||||
|
fullscreen_idle_timeout: Some(3),
|
||||||
|
while_typing: false,
|
||||||
|
after_touch: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||||
pub struct ZoomConfig {
|
pub struct ZoomConfig {
|
||||||
pub start_on_login: bool,
|
pub start_on_login: bool,
|
||||||
|
|
@ -195,6 +283,10 @@ pub struct ZoomConfig {
|
||||||
pub enable_mouse_zoom_shortcuts: bool,
|
pub enable_mouse_zoom_shortcuts: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ZoomConfig {
|
||||||
|
pub const ZOOM_INCREMENT_PRESETS: &[u32] = &[10, 25, 50, 75, 100, 150, 200];
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for ZoomConfig {
|
impl Default for ZoomConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
ZoomConfig {
|
ZoomConfig {
|
||||||
|
|
@ -247,3 +339,109 @@ pub enum XwaylandDescaling {
|
||||||
#[default]
|
#[default]
|
||||||
Fractional,
|
Fractional,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::{CursorHideConfig, HideDecision};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
const OFF: CursorHideConfig = CursorHideConfig {
|
||||||
|
idle_timeout: None,
|
||||||
|
fullscreen_idle_timeout: None,
|
||||||
|
while_typing: false,
|
||||||
|
after_touch: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn secs(n: u64) -> Duration {
|
||||||
|
Duration::from_secs(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_timeout_never_hides() {
|
||||||
|
assert_eq!(OFF.resolve(secs(600), false), HideDecision::Drop);
|
||||||
|
assert_eq!(OFF.resolve(secs(600), true), HideDecision::Drop);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn global_timeout_applies_everywhere() {
|
||||||
|
let cfg = CursorHideConfig {
|
||||||
|
idle_timeout: Some(10),
|
||||||
|
..OFF
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
cfg.resolve(secs(4), false),
|
||||||
|
HideDecision::RearmAfter(secs(6))
|
||||||
|
);
|
||||||
|
assert_eq!(cfg.resolve(secs(10), false), HideDecision::Hide);
|
||||||
|
// A fullscreen window must not suppress an enabled global timeout.
|
||||||
|
assert_eq!(cfg.resolve(secs(10), true), HideDecision::Hide);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fullscreen_timeout_only_applies_in_fullscreen() {
|
||||||
|
let cfg = CursorHideConfig {
|
||||||
|
fullscreen_idle_timeout: Some(3),
|
||||||
|
..OFF
|
||||||
|
};
|
||||||
|
assert_eq!(cfg.resolve(secs(3), true), HideDecision::Hide);
|
||||||
|
assert_eq!(cfg.resolve(secs(600), false), HideDecision::Drop);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn fullscreen_shortens_but_never_lengthens() {
|
||||||
|
let short_fs = CursorHideConfig {
|
||||||
|
idle_timeout: Some(10),
|
||||||
|
fullscreen_idle_timeout: Some(3),
|
||||||
|
..OFF
|
||||||
|
};
|
||||||
|
assert_eq!(short_fs.resolve(secs(3), true), HideDecision::Hide);
|
||||||
|
assert_eq!(
|
||||||
|
short_fs.resolve(secs(3), false),
|
||||||
|
HideDecision::RearmAfter(secs(7))
|
||||||
|
);
|
||||||
|
|
||||||
|
let long_fs = CursorHideConfig {
|
||||||
|
idle_timeout: Some(3),
|
||||||
|
fullscreen_idle_timeout: Some(30),
|
||||||
|
..OFF
|
||||||
|
};
|
||||||
|
assert_eq!(long_fs.resolve(secs(3), true), HideDecision::Hide);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn boundary_is_inclusive() {
|
||||||
|
let cfg = CursorHideConfig {
|
||||||
|
idle_timeout: Some(5),
|
||||||
|
..OFF
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
cfg.resolve(Duration::from_millis(4999), false),
|
||||||
|
HideDecision::RearmAfter(Duration::from_millis(1))
|
||||||
|
);
|
||||||
|
assert_eq!(cfg.resolve(secs(5), false), HideDecision::Hide);
|
||||||
|
assert_eq!(cfg.resolve(secs(6), false), HideDecision::Hide);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arm_delay_measures_from_the_last_pointer_event() {
|
||||||
|
let cfg = CursorHideConfig {
|
||||||
|
idle_timeout: Some(10),
|
||||||
|
..OFF
|
||||||
|
};
|
||||||
|
assert_eq!(cfg.arm_delay(Duration::ZERO), Some(secs(10)));
|
||||||
|
assert_eq!(cfg.arm_delay(secs(4)), Some(secs(6)));
|
||||||
|
// Exactly due, and past due: fire immediately rather than deferring.
|
||||||
|
assert_eq!(cfg.arm_delay(secs(10)), Some(Duration::ZERO));
|
||||||
|
assert_eq!(cfg.arm_delay(secs(600)), Some(Duration::ZERO));
|
||||||
|
|
||||||
|
// Armed against the shortest timeout that any context could want.
|
||||||
|
let fs = CursorHideConfig {
|
||||||
|
idle_timeout: Some(10),
|
||||||
|
fullscreen_idle_timeout: Some(3),
|
||||||
|
..OFF
|
||||||
|
};
|
||||||
|
assert_eq!(fs.arm_delay(secs(1)), Some(secs(2)));
|
||||||
|
|
||||||
|
assert_eq!(OFF.arm_delay(Duration::ZERO), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
1
debian/control
vendored
1
debian/control
vendored
|
|
@ -6,6 +6,7 @@ Build-Depends:
|
||||||
cargo,
|
cargo,
|
||||||
cmake,
|
cmake,
|
||||||
debhelper-compat (=10),
|
debhelper-compat (=10),
|
||||||
|
just,
|
||||||
libegl1-mesa-dev,
|
libegl1-mesa-dev,
|
||||||
libfontconfig-dev,
|
libfontconfig-dev,
|
||||||
libgbm-dev,
|
libgbm-dev,
|
||||||
|
|
|
||||||
12
debian/rules
vendored
12
debian/rules
vendored
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/make -f
|
#!/usr/bin/make -f
|
||||||
|
|
||||||
export sysconfdir = "/usr/share"
|
DESTDIR = debian/cosmic-comp
|
||||||
export VENDOR ?= 1
|
export VENDOR ?= 1
|
||||||
CLEAN ?= 1
|
CLEAN ?= 1
|
||||||
|
|
||||||
|
|
@ -9,12 +9,18 @@ CLEAN ?= 1
|
||||||
|
|
||||||
override_dh_auto_clean:
|
override_dh_auto_clean:
|
||||||
ifeq ($(CLEAN),1)
|
ifeq ($(CLEAN),1)
|
||||||
ischroot && make clean || make distclean
|
ischroot && just clean || just clean-dist
|
||||||
endif
|
endif
|
||||||
ifeq ($(VENDOR),1)
|
ifeq ($(VENDOR),1)
|
||||||
ischroot || make vendor
|
ischroot || just vendor
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
override_dh_auto_build:
|
||||||
|
just build-vendored
|
||||||
|
|
||||||
|
override_dh_auto_install:
|
||||||
|
just rootdir=$(DESTDIR) install
|
||||||
|
|
||||||
override_dh_installinit:
|
override_dh_installinit:
|
||||||
dh_installinit -r
|
dh_installinit -r
|
||||||
|
|
||||||
|
|
|
||||||
64
justfile
Normal file
64
justfile
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
name := 'cosmic-comp'
|
||||||
|
|
||||||
|
rootdir := ''
|
||||||
|
prefix := '/usr'
|
||||||
|
sysconfdir := '/etc'
|
||||||
|
|
||||||
|
base-dir := absolute_path(clean(rootdir / prefix))
|
||||||
|
sysconfdir-dir := absolute_path(clean(rootdir / sysconfdir))
|
||||||
|
|
||||||
|
bin-dir := base-dir / 'bin'
|
||||||
|
lib-dir := base-dir / 'lib'
|
||||||
|
share-dir := base-dir / 'share'
|
||||||
|
|
||||||
|
bin-src := cargo-target-dir / 'release' / name
|
||||||
|
bin-dst := bin-dir / name
|
||||||
|
|
||||||
|
keybindings-src := 'data' / 'keybindings.ron'
|
||||||
|
keybindings-dst := share-dir / 'cosmic' / 'com.system76.CosmicSettings.Shortcuts' / 'v1' / 'defaults'
|
||||||
|
|
||||||
|
tiling-exceptions-src := 'data' / 'tiling-exceptions.ron'
|
||||||
|
tiling-exceptions-dst := share-dir / 'cosmic' / 'com.system76.CosmicSettings.WindowRules' / 'v1' / 'tiling_exception_defaults'
|
||||||
|
|
||||||
|
session-desktop-src := 'data' / 'cosmic.desktop'
|
||||||
|
session-desktop-dst := share-dir / 'wayland-sessions' / 'cosmic.desktop'
|
||||||
|
|
||||||
|
systemd-session-target-src := 'data' / 'cosmic-session.target'
|
||||||
|
systemd-session-target-dst := lib-dir / 'systemd' / 'user' / 'cosmic-session.target'
|
||||||
|
|
||||||
|
systemd-session-pre-target-src := 'data' / 'cosmic-session-pre.target'
|
||||||
|
systemd-session-pre-target-dst := lib-dir / 'systemd' / 'user' / 'cosmic-session-pre.target'
|
||||||
|
|
||||||
|
systemd-comp-service-src := 'data' / 'cosmic-comp.service'
|
||||||
|
systemd-comp-service-dst := lib-dir / 'systemd' / 'user' / 'cosmic-comp.service'
|
||||||
|
|
||||||
|
cosmic-service-src := 'data' / 'cosmic-service'
|
||||||
|
cosmic-service-dst := bin-dir / 'cosmic-service'
|
||||||
|
|
||||||
|
import 'cargo.just'
|
||||||
|
|
||||||
|
# Default recipe which runs `just build-release`
|
||||||
|
[private]
|
||||||
|
default: build-release
|
||||||
|
|
||||||
|
# Installs files
|
||||||
|
install:
|
||||||
|
install -Dm0755 {{bin-src}} {{bin-dst}}
|
||||||
|
install -Dm0644 {{keybindings-src}} {{keybindings-dst}}
|
||||||
|
install -Dm0644 {{tiling-exceptions-src}} {{tiling-exceptions-dst}}
|
||||||
|
|
||||||
|
# Installs files including bare session files
|
||||||
|
install-bare-session: install
|
||||||
|
install -Dm0644 {{session-desktop-src}} {{session-desktop-dst}}
|
||||||
|
install -Dm0644 {{systemd-session-target-src}} {{systemd-session-target-dst}}
|
||||||
|
install -Dm0644 {{systemd-session-pre-target-src}} {{systemd-session-pre-target-dst}}
|
||||||
|
install -Dm0644 {{systemd-comp-service-src}} {{systemd-comp-service-dst}}
|
||||||
|
install -Dm0755 {{cosmic-service-src}} {{cosmic-service-dst}}
|
||||||
|
|
||||||
|
# Uninstalls installed files
|
||||||
|
uninstall:
|
||||||
|
rm {{bin-dst}} {{keybindings-dst}} {{tiling-exceptions-dst}}
|
||||||
|
|
||||||
|
# Uninstalls installed files including bare session files
|
||||||
|
uninstall-bare-session: uninstall
|
||||||
|
rm {{session-desktop-dst}} {{systemd-session-target-dst}} {{systemd-session-pre-target-dst}} {{systemd-comp-service-dst}} {{cosmic-service-dst}}
|
||||||
0
resources/i18n/fil/cosmic_comp.ftl
Normal file
0
resources/i18n/fil/cosmic_comp.ftl
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
a11y-zoom-move-continuously = La vista bolega de contunh amb lo ponchador
|
||||||
|
a11y-zoom-move-onedge = La lista bolega quand lo ponchador atenh un caire
|
||||||
|
a11y-zoom-move-centered = La vista bolega per gardar lo ponchador al centre
|
||||||
|
a11y-zoom-settings = Paramètres de la lópia...
|
||||||
|
unknown-keybinding = <unset>
|
||||||
|
window-menu-tiled = Fenèstras bandejanta
|
||||||
|
window-menu-screenshot = Prendre una captura d’ecran
|
||||||
|
window-menu-resize = Redimensionar
|
||||||
|
window-menu-move-prev-workspace = Desplaçar a l’espaci de trabalh precedent
|
||||||
|
window-menu-move-next-workspace = Desplaçar a l'espaci de trabalh seguent
|
||||||
|
window-menu-stack = Crear una pila de fenèstras
|
||||||
|
window-menu-unstack-all = Desapilar totas las fenèstras
|
||||||
|
window-menu-unstack = Desapilar la fenèstra
|
||||||
|
window-menu-sticky = Fenèstra adesiva
|
||||||
|
window-menu-close = Tampar
|
||||||
|
window-menu-close-all = Tampar totas las fenèstras
|
||||||
|
window-menu-resize-edge-top = Amont
|
||||||
|
window-menu-resize-edge-left = Esquèrra
|
||||||
|
window-menu-resize-edge-right = Drecha
|
||||||
|
window-menu-resize-edge-bottom = Aval
|
||||||
|
|
@ -693,7 +693,7 @@ impl Device {
|
||||||
)
|
)
|
||||||
.with_context(|| {
|
.with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"Failed to optain file descriptor for drm device: {}",
|
"Failed to obtain file descriptor for drm device: {}",
|
||||||
path.display()
|
path.display()
|
||||||
)
|
)
|
||||||
})?,
|
})?,
|
||||||
|
|
@ -1241,26 +1241,15 @@ fn populate_modes(
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let conn_info = drm.get_connector(conn, false)?;
|
let conn_info = drm.get_connector(conn, false)?;
|
||||||
let max_bpc = drm_helpers::get_max_bpc(drm, conn)?.map(|(_val, range)| range.end.min(16));
|
let max_bpc = drm_helpers::get_max_bpc(drm, conn)?.map(|(_val, range)| range.end.min(16));
|
||||||
let preferred = conn_info
|
let Some(mode) = conn_info
|
||||||
.modes()
|
.modes()
|
||||||
.iter()
|
.iter()
|
||||||
.find(|mode| mode.mode_type().contains(ModeTypeFlags::PREFERRED))
|
.find(|mode| mode.mode_type().contains(ModeTypeFlags::PREFERRED))
|
||||||
.copied()
|
.copied()
|
||||||
.or(conn_info.modes().first().copied());
|
.or(conn_info.modes().first().copied())
|
||||||
let Some(preferred) = preferred else {
|
else {
|
||||||
anyhow::bail!("No mode found");
|
anyhow::bail!("No mode found");
|
||||||
};
|
};
|
||||||
// Prefer the highest refresh rate at the native resolution so that
|
|
||||||
// high-refresh panels (120 Hz, 144 Hz, …) are used out-of-the-box
|
|
||||||
// without a pre-existing outputs.ron config.
|
|
||||||
let preferred_size = preferred.size();
|
|
||||||
let mode = conn_info
|
|
||||||
.modes()
|
|
||||||
.iter()
|
|
||||||
.filter(|m| m.size() == preferred_size)
|
|
||||||
.max_by_key(|m| drm_helpers::calculate_refresh_rate(**m))
|
|
||||||
.copied()
|
|
||||||
.unwrap_or(preferred);
|
|
||||||
|
|
||||||
let refresh_rate = drm_helpers::calculate_refresh_rate(mode);
|
let refresh_rate = drm_helpers::calculate_refresh_rate(mode);
|
||||||
let output_mode = OutputMode {
|
let output_mode = OutputMode {
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,11 @@ use smithay::{
|
||||||
calloop::{Dispatcher, EventLoop, LoopHandle},
|
calloop::{Dispatcher, EventLoop, LoopHandle},
|
||||||
drm::{
|
drm::{
|
||||||
Device as _,
|
Device as _,
|
||||||
control::{Device as _, connector::Interface, crtc},
|
control::{
|
||||||
|
Device as _,
|
||||||
|
connector::{Interface, State as ConnectorState},
|
||||||
|
crtc,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
input::{self, Libinput},
|
input::{self, Libinput},
|
||||||
wayland_protocols::wp::linux_dmabuf::zv1::server::zwp_linux_dmabuf_feedback_v1::TrancheFlags,
|
wayland_protocols::wp::linux_dmabuf::zv1::server::zwp_linux_dmabuf_feedback_v1::TrancheFlags,
|
||||||
|
|
@ -250,13 +254,18 @@ fn determine_primary_gpu(
|
||||||
|
|
||||||
// try to find builtin display
|
// try to find builtin display
|
||||||
for dev in drm_devices.values() {
|
for dev in drm_devices.values() {
|
||||||
if dev.inner.surfaces.values().any(|s| {
|
let drm = dev.drm.device();
|
||||||
if let Ok(conn_info) = dev.drm.device().get_connector(s.connector, false) {
|
let res_handles = drm.resource_handles()?;
|
||||||
let i = conn_info.interface();
|
let connectors = res_handles.connectors();
|
||||||
i == Interface::EmbeddedDisplayPort || i == Interface::LVDS || i == Interface::DSI
|
if connectors.iter().any(|conn| {
|
||||||
} else {
|
let Ok(conn_info) = drm.get_connector(*conn, false) else {
|
||||||
false
|
return false;
|
||||||
}
|
};
|
||||||
|
let i = conn_info.interface();
|
||||||
|
conn_info.state() == ConnectorState::Connected
|
||||||
|
&& (i == Interface::EmbeddedDisplayPort
|
||||||
|
|| i == Interface::LVDS
|
||||||
|
|| i == Interface::DSI)
|
||||||
}) {
|
}) {
|
||||||
return Ok(Some(dev.inner.render_node));
|
return Ok(Some(dev.inner.render_node));
|
||||||
}
|
}
|
||||||
|
|
@ -606,7 +615,13 @@ impl State {
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
let mut feedback =
|
let mut feedback =
|
||||||
DmabufFeedbackBuilder::new(primary_node.dev_id(), primary_formats.clone());
|
DmabufFeedbackBuilder::new(primary_node.dev_id(), primary_formats.clone())
|
||||||
|
.add_preference_tranche(
|
||||||
|
primary_node.dev_id(),
|
||||||
|
TrancheFlags::Sampling,
|
||||||
|
primary_formats.iter().cloned(),
|
||||||
|
3..=6,
|
||||||
|
);
|
||||||
for dev in kms
|
for dev in kms
|
||||||
.drm_devices
|
.drm_devices
|
||||||
.values()
|
.values()
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,14 @@ use crate::{
|
||||||
element::AsGlowRenderer,
|
element::AsGlowRenderer,
|
||||||
wayland::{SurfaceRenderElement, push_render_elements_from_surface_tree},
|
wayland::{SurfaceRenderElement, push_render_elements_from_surface_tree},
|
||||||
},
|
},
|
||||||
|
shell::focus::target::KeyboardFocusTarget,
|
||||||
utils::prelude::*,
|
utils::prelude::*,
|
||||||
wayland::handlers::compositor::FRAME_TIME_FILTER,
|
wayland::handlers::compositor::FRAME_TIME_FILTER,
|
||||||
};
|
};
|
||||||
|
use cosmic_comp_config::HideDecision;
|
||||||
|
use keyframe::{ease, functions::EaseInOutCubic};
|
||||||
|
use resvg::{tiny_skia, usvg};
|
||||||
|
use serde::Deserialize;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
allocator::Fourcc,
|
allocator::Fourcc,
|
||||||
|
|
@ -38,7 +43,7 @@ use smithay::{
|
||||||
wayland::compositor::{get_role, with_states},
|
wayland::compositor::{get_role, with_states},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::{HashMap, VecDeque},
|
||||||
io::Read,
|
io::Read,
|
||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
|
|
@ -51,14 +56,43 @@ use xcursor::{
|
||||||
|
|
||||||
static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../../../resources/cursor.rgba");
|
static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../../../resources/cursor.rgba");
|
||||||
|
|
||||||
|
/// A single frame of a scalable SVG cursor: the parsed SVG plus its metadata.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct SvgFrame {
|
||||||
|
tree: usvg::Tree,
|
||||||
|
/// The nominal (logical) size the SVG is authored for.
|
||||||
|
nominal_size: f32,
|
||||||
|
/// Hotspot coordinates, in the SVG's own (nominal) coordinate space.
|
||||||
|
hotspot_x: f32,
|
||||||
|
hotspot_y: f32,
|
||||||
|
/// Delay to the next frame in milliseconds (0 for static cursors).
|
||||||
|
delay: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum CursorKind {
|
||||||
|
/// Legacy raster XCursor frames.
|
||||||
|
Xcursor(Vec<Image>),
|
||||||
|
/// Scalable SVG frames, rasterized on demand.
|
||||||
|
Svg(Vec<SvgFrame>),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Cursor {
|
pub struct Cursor {
|
||||||
icons: Vec<Image>,
|
kind: CursorKind,
|
||||||
size: u32,
|
size: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cursor {
|
impl Cursor {
|
||||||
pub fn load(theme: &CursorTheme, shape: CursorIcon, size: u32) -> Cursor {
|
pub fn load(theme: &CursorTheme, shape: CursorIcon, size: u32) -> Cursor {
|
||||||
|
// Prefer a scalable SVG cursor when the theme provides one.
|
||||||
|
if let Some(frames) = load_svg_icon(theme, shape) {
|
||||||
|
return Cursor {
|
||||||
|
kind: CursorKind::Svg(frames),
|
||||||
|
size,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let icons = load_icon(theme, shape)
|
let icons = load_icon(theme, shape)
|
||||||
.map_err(|err| warn!(?err, "Unable to load xcursor, using fallback cursor"))
|
.map_err(|err| warn!(?err, "Unable to load xcursor, using fallback cursor"))
|
||||||
.or_else(|_| load_icon(theme, CursorIcon::Default))
|
.or_else(|_| load_icon(theme, CursorIcon::Default))
|
||||||
|
|
@ -75,44 +109,134 @@ impl Cursor {
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
|
||||||
Cursor { icons, size }
|
Cursor {
|
||||||
|
kind: CursorKind::Xcursor(icons),
|
||||||
|
size,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_image(&self, scale: u32, millis: u32) -> Image {
|
pub fn get_image(&self, scale: u32, millis: u32) -> Image {
|
||||||
let size = self.size * scale;
|
let size = self.size * scale;
|
||||||
frame(millis, size, &self.icons)
|
let idx = self.frame_index(size, millis);
|
||||||
|
self.render_frame(size, idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Selects the index of the frame to display at nominal size `size` (in px)
|
||||||
|
/// and elapsed `millis`.
|
||||||
|
fn frame_index(&self, size: u32, millis: u32) -> usize {
|
||||||
|
match &self.kind {
|
||||||
|
CursorKind::Xcursor(images) => xcursor_frame_index(millis, size, images),
|
||||||
|
CursorKind::Svg(frames) => svg_frame_index(millis, frames),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Produces the RGBA image for frame `idx`, rasterizing SVG cursors at the
|
||||||
|
/// requested nominal size `size` (in px).
|
||||||
|
fn render_frame(&self, size: u32, idx: usize) -> Image {
|
||||||
|
match &self.kind {
|
||||||
|
CursorKind::Xcursor(images) => images[idx].clone(),
|
||||||
|
CursorKind::Svg(frames) => rasterize_svg_frame(&frames[idx], size),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nearest_images(size: u32, images: &[Image]) -> impl Iterator<Item = &Image> {
|
/// Rasterize a scalable cursor frame at nominal pixel size `size`. Per the KDE
|
||||||
// Follow the nominal size of the cursor to choose the nearest
|
/// SVG cursor format, the SVG canvas and hotspot are scaled by
|
||||||
|
/// `size / nominal_size`. The returned [`Image`] uses the same premultiplied
|
||||||
|
/// byte order (BGRA / little-endian ARGB) as xcursor images.
|
||||||
|
fn rasterize_svg_frame(frame: &SvgFrame, size: u32) -> Image {
|
||||||
|
let factor = size as f32 / frame.nominal_size;
|
||||||
|
let svg_size = frame.tree.size();
|
||||||
|
let width = ((svg_size.width() * factor).floor() as u32).max(1);
|
||||||
|
let height = ((svg_size.height() * factor).floor() as u32).max(1);
|
||||||
|
|
||||||
|
let mut image = Image {
|
||||||
|
size,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
xhot: (frame.hotspot_x * factor).floor() as u32,
|
||||||
|
yhot: (frame.hotspot_y * factor).floor() as u32,
|
||||||
|
delay: frame.delay,
|
||||||
|
pixels_rgba: Vec::new(),
|
||||||
|
pixels_argb: Vec::new(), // unused
|
||||||
|
};
|
||||||
|
|
||||||
|
match tiny_skia::Pixmap::new(width, height) {
|
||||||
|
Some(mut pixmap) => {
|
||||||
|
resvg::render(
|
||||||
|
&frame.tree,
|
||||||
|
tiny_skia::Transform::from_scale(factor, factor),
|
||||||
|
&mut pixmap.as_mut(),
|
||||||
|
);
|
||||||
|
// tiny-skia produces premultiplied RGBA; xcursor images consume the
|
||||||
|
// raw little-endian ARGB byte order (premultiplied BGRA). Swap R<->B.
|
||||||
|
let mut pixels = pixmap.take();
|
||||||
|
for px in pixels.as_chunks_mut::<4>().0 {
|
||||||
|
px.swap(0, 2);
|
||||||
|
}
|
||||||
|
image.pixels_rgba = pixels;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
warn!(width, height, "Failed to allocate cursor pixmap");
|
||||||
|
image.pixels_rgba = vec![0; (width as usize) * (height as usize) * 4];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
image
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Indices (into `images`) of all frames sharing the resolution nearest to `size`.
|
||||||
|
fn nearest_image_indices(size: u32, images: &[Image]) -> Vec<usize> {
|
||||||
|
// Follow the nominal size of the cursor to choose the nearest.
|
||||||
let nearest_image = images
|
let nearest_image = images
|
||||||
.iter()
|
.iter()
|
||||||
.min_by_key(|image| u32::abs_diff(size, image.size))
|
.min_by_key(|image| u32::abs_diff(size, image.size))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let (width, height) = (nearest_image.width, nearest_image.height);
|
||||||
|
|
||||||
images.iter().filter(move |image| {
|
images
|
||||||
image.width == nearest_image.width && image.height == nearest_image.height
|
.iter()
|
||||||
})
|
.enumerate()
|
||||||
|
.filter(|(_, image)| image.width == width && image.height == height)
|
||||||
|
.map(|(i, _)| i)
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn frame(mut millis: u32, size: u32, images: &[Image]) -> Image {
|
fn xcursor_frame_index(mut millis: u32, size: u32, images: &[Image]) -> usize {
|
||||||
let total = nearest_images(size, images).fold(0, |acc, image| acc + image.delay);
|
let indices = nearest_image_indices(size, images);
|
||||||
|
let total: u32 = indices.iter().map(|&i| images[i].delay).sum();
|
||||||
|
|
||||||
if total == 0 {
|
if total == 0 {
|
||||||
millis = 0;
|
return indices[0];
|
||||||
} else {
|
|
||||||
millis %= total;
|
|
||||||
}
|
}
|
||||||
|
millis %= total;
|
||||||
|
|
||||||
for img in nearest_images(size, images) {
|
for &i in &indices {
|
||||||
if millis <= img.delay {
|
if millis <= images[i].delay {
|
||||||
return img.clone();
|
return i;
|
||||||
}
|
}
|
||||||
millis -= img.delay;
|
millis -= images[i].delay;
|
||||||
}
|
}
|
||||||
|
|
||||||
unreachable!()
|
*indices.last().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn svg_frame_index(mut millis: u32, frames: &[SvgFrame]) -> usize {
|
||||||
|
let total: u32 = frames.iter().map(|frame| frame.delay).sum();
|
||||||
|
|
||||||
|
if total == 0 {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
millis %= total;
|
||||||
|
|
||||||
|
for (i, frame) in frames.iter().enumerate() {
|
||||||
|
if millis <= frame.delay {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
millis -= frame.delay;
|
||||||
|
}
|
||||||
|
|
||||||
|
frames.len() - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
|
@ -174,6 +298,79 @@ fn load_icon(theme: &CursorTheme, shape: CursorIcon) -> Result<Vec<Image>, Error
|
||||||
Err(Error::NoDefaultCursor)
|
Err(Error::NoDefaultCursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A frame entry in a `cursors_scalable/<shape>/metadata.json` file, per the
|
||||||
|
/// KDE SVG cursor format specification.
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
struct SvgCursorMeta {
|
||||||
|
filename: String,
|
||||||
|
nominal_size: f32,
|
||||||
|
hotspot_x: f32,
|
||||||
|
hotspot_y: f32,
|
||||||
|
/// Only present for animated cursors; defaults to 0 for static ones.
|
||||||
|
#[serde(default)]
|
||||||
|
delay: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves a scalable (SVG) cursor for `shape`
|
||||||
|
fn load_svg_icon(theme: &CursorTheme, shape: CursorIcon) -> Option<Vec<SvgFrame>> {
|
||||||
|
let shape_name = shape.to_string();
|
||||||
|
let options = usvg::Options::default();
|
||||||
|
for name in cursor_aliases(&shape_name)
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.chain(std::iter::once(shape_name.as_str()))
|
||||||
|
{
|
||||||
|
if let Some(dir) = theme.load_scalable(name)
|
||||||
|
&& let Some(frames) = parse_svg_dir(&dir, &options)
|
||||||
|
{
|
||||||
|
return Some(frames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a `cursors_scalable/<shape>` directory (its `metadata.json` and the
|
||||||
|
/// referenced SVG files) into a list of parsed frames.
|
||||||
|
fn parse_svg_dir(dir: &std::path::Path, options: &usvg::Options) -> Option<Vec<SvgFrame>> {
|
||||||
|
let metadata = std::fs::read(dir.join("metadata.json")).ok()?;
|
||||||
|
let metas: Vec<SvgCursorMeta> = match serde_json::from_slice(&metadata) {
|
||||||
|
Ok(metas) => metas,
|
||||||
|
Err(err) => {
|
||||||
|
warn!(?dir, ?err, "Malformed SVG cursor metadata");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut frames = Vec::with_capacity(metas.len());
|
||||||
|
for meta in metas {
|
||||||
|
let svg_path = dir.join(&meta.filename);
|
||||||
|
let svg_data = match std::fs::read(&svg_path) {
|
||||||
|
Ok(data) => data,
|
||||||
|
Err(err) => {
|
||||||
|
warn!(?svg_path, ?err, "Unable to read SVG cursor");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let tree = match usvg::Tree::from_data(&svg_data, options) {
|
||||||
|
Ok(tree) => tree,
|
||||||
|
Err(err) => {
|
||||||
|
warn!(?svg_path, ?err, "Unable to parse SVG cursor");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
frames.push(SvgFrame {
|
||||||
|
tree,
|
||||||
|
nominal_size: meta.nominal_size,
|
||||||
|
hotspot_x: meta.hotspot_x,
|
||||||
|
hotspot_y: meta.hotspot_y,
|
||||||
|
delay: meta.delay,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
(!frames.is_empty()).then_some(frames)
|
||||||
|
}
|
||||||
|
|
||||||
render_elements! {
|
render_elements! {
|
||||||
pub CursorRenderElement<R> where R: ImportAll + ImportMem + AsGlowRenderer, R::TextureId: Send;
|
pub CursorRenderElement<R> where R: ImportAll + ImportMem + AsGlowRenderer, R::TextureId: Send;
|
||||||
Static=MemoryRenderBufferRenderElement<R>,
|
Static=MemoryRenderBufferRenderElement<R>,
|
||||||
|
|
@ -256,6 +453,32 @@ pub fn draw_dnd_icon<R>(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Why the cursor is currently hidden. Determines what brings it back.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum HideReason {
|
||||||
|
/// No pointer activity for the configured timeout.
|
||||||
|
Idle,
|
||||||
|
/// A key was pressed while `while_typing` is enabled.
|
||||||
|
Typing,
|
||||||
|
/// Touch input arrived while `after_touch` is enabled.
|
||||||
|
Touch,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether a pointer event carries movement. A touch-hidden cursor comes back
|
||||||
|
/// only on real movement, so the distinction has to reach the call site.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum PointerEventKind {
|
||||||
|
Motion,
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HideReason {
|
||||||
|
/// Whether an event of this kind should reveal the cursor.
|
||||||
|
fn revealed_by(self, kind: PointerEventKind) -> bool {
|
||||||
|
matches!(kind, PointerEventKind::Motion) || !matches!(self, HideReason::Touch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type CursorState = Mutex<CursorStateInner>;
|
pub type CursorState = Mutex<CursorStateInner>;
|
||||||
pub struct CursorStateInner {
|
pub struct CursorStateInner {
|
||||||
current_cursor: Option<CursorIcon>,
|
current_cursor: Option<CursorIcon>,
|
||||||
|
|
@ -265,11 +488,63 @@ pub struct CursorStateInner {
|
||||||
|
|
||||||
cursors: HashMap<CursorIcon, Cursor>,
|
cursors: HashMap<CursorIcon, Cursor>,
|
||||||
current_image: Option<Image>,
|
current_image: Option<Image>,
|
||||||
image_cache: Vec<(Image, MemoryRenderBuffer)>,
|
image_cache: Vec<CachedFrame>,
|
||||||
|
|
||||||
hidden: bool,
|
hidden: Option<HideReason>,
|
||||||
idle_timer: Option<RegistrationToken>,
|
idle_timer: Option<RegistrationToken>,
|
||||||
last_armed: Option<Instant>,
|
last_armed: Option<Instant>,
|
||||||
|
last_pointer_activity: Instant,
|
||||||
|
|
||||||
|
// shake-to-find
|
||||||
|
shake_path: VecDeque<PathSample>,
|
||||||
|
shake_path_position: Point<f64, Logical>,
|
||||||
|
magnify_until: Option<Instant>,
|
||||||
|
magnify_target: f32,
|
||||||
|
magnification: f32,
|
||||||
|
anim_from: f32,
|
||||||
|
anim_start: Option<Instant>,
|
||||||
|
rest_started: Option<Instant>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A rasterized cursor frame, keyed by `(shape, pixel size, frame index)`.
|
||||||
|
struct CachedFrame {
|
||||||
|
key: (CursorIcon, u32, usize),
|
||||||
|
image: Image,
|
||||||
|
buffer: MemoryRenderBuffer,
|
||||||
|
unmagnified: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// One sampled pointer position on the recent motion path.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct PathSample {
|
||||||
|
position: Point<f64, Logical>,
|
||||||
|
time: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How long everything must stay unmagnified before the enlarged frames go.
|
||||||
|
const MAGNIFIED_FRAME_GRACE: Duration = Duration::from_secs(10);
|
||||||
|
|
||||||
|
/// How far back the motion path is considered when looking for a shake.
|
||||||
|
const SHAKE_INTERVAL: Duration = Duration::from_millis(1000);
|
||||||
|
/// Path-length / bounding-box-diagonal ratio required to count as a shake.
|
||||||
|
const SHAKE_SENSITIVITY: f64 = 4.0;
|
||||||
|
/// Minimum bounding-box diagonal (logical px) before a shake is considered.
|
||||||
|
const SHAKE_DIAGONAL_MIN: f64 = 100.0;
|
||||||
|
/// Two deltas count as "the same direction" if both lie within this tolerance.
|
||||||
|
const SHAKE_SAME_SIGN_TOLERANCE: f64 = 1.0;
|
||||||
|
/// Keep the cursor enlarged for this long after the last detected shake.
|
||||||
|
const SHAKE_HOLD: Duration = Duration::from_millis(2000);
|
||||||
|
/// Extra magnification added by each shake, growing from the normal cursor size.
|
||||||
|
const OVER_MAGNIFICATION: f32 = 1.0;
|
||||||
|
/// Upper bound on the nominal size (in px) a cursor frame is rasterized at.
|
||||||
|
const MAX_RASTER_SIZE: u32 = 512;
|
||||||
|
/// Duration of the grow/shrink animation.
|
||||||
|
const MAGNIFICATION_ANIM: Duration = Duration::from_millis(200);
|
||||||
|
|
||||||
|
/// small movement is ignored and direction stays the same
|
||||||
|
fn same_direction(a: f64, b: f64) -> bool {
|
||||||
|
(a >= -SHAKE_SAME_SIGN_TOLERANCE && b >= -SHAKE_SAME_SIGN_TOLERANCE)
|
||||||
|
|| (a <= SHAKE_SAME_SIGN_TOLERANCE && b <= SHAKE_SAME_SIGN_TOLERANCE)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CursorStateInner {
|
impl CursorStateInner {
|
||||||
|
|
@ -282,14 +557,141 @@ impl CursorStateInner {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_named_cursor(&mut self, shape: CursorIcon) -> &Cursor {
|
pub fn get_named_cursor(&mut self, shape: CursorIcon) -> &Cursor {
|
||||||
|
let cursor_theme = &self.cursor_theme;
|
||||||
|
let cursor_size = self.cursor_size;
|
||||||
self.cursors
|
self.cursors
|
||||||
.entry(shape)
|
.entry(shape)
|
||||||
.or_insert_with(|| Cursor::load(&self.cursor_theme, shape, self.cursor_size))
|
.or_insert_with(|| Cursor::load(cursor_theme, shape, cursor_size))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size(&self) -> u32 {
|
pub fn size(&self) -> u32 {
|
||||||
self.cursor_size
|
self.cursor_size
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Drop the rasterizations only a magnified cursor needed, once nothing has
|
||||||
|
/// magnified it for [`MAGNIFIED_FRAME_GRACE`].
|
||||||
|
pub fn drop_magnified_frames(&mut self, now: Instant, zoomed: bool) {
|
||||||
|
if zoomed || self.is_magnifying() {
|
||||||
|
self.rest_started = None;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let rest_started = *self.rest_started.get_or_insert(now);
|
||||||
|
if now.duration_since(rest_started) < MAGNIFIED_FRAME_GRACE {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.image_cache.retain(|frame| frame.unmagnified);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Feed one relative-motion event into the shake detector.
|
||||||
|
pub fn detect_shake(&mut self, delta: Point<f64, Logical>, now: Instant) {
|
||||||
|
// Drop samples that have aged out of the time window.
|
||||||
|
while let Some(oldest) = self.shake_path.front() {
|
||||||
|
if now.duration_since(oldest.time) >= SHAKE_INTERVAL {
|
||||||
|
self.shake_path.pop_front();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if delta.x != 0.0 || delta.y != 0.0 {
|
||||||
|
self.shake_path_position += delta;
|
||||||
|
let sample = PathSample {
|
||||||
|
position: self.shake_path_position,
|
||||||
|
time: now,
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.shake_path.len() >= 2 {
|
||||||
|
let last = self.shake_path[self.shake_path.len() - 1].position;
|
||||||
|
let prev = self.shake_path[self.shake_path.len() - 2].position;
|
||||||
|
let last_delta = last - prev;
|
||||||
|
if same_direction(last_delta.x, delta.x) && same_direction(last_delta.y, delta.y) {
|
||||||
|
*self.shake_path.back_mut().unwrap() = sample;
|
||||||
|
} else {
|
||||||
|
self.shake_path.push_back(sample);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.shake_path.push_back(sample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.shake_path.len() < 2 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let first = self.shake_path[0].position;
|
||||||
|
let (mut left, mut top, mut right, mut bottom) = (first.x, first.y, first.x, first.y);
|
||||||
|
let mut path_length = 0.0;
|
||||||
|
for i in 1..self.shake_path.len() {
|
||||||
|
let p = self.shake_path[i].position;
|
||||||
|
left = left.min(p.x);
|
||||||
|
top = top.min(p.y);
|
||||||
|
right = right.max(p.x);
|
||||||
|
bottom = bottom.max(p.y);
|
||||||
|
|
||||||
|
let step = p - self.shake_path[i - 1].position;
|
||||||
|
path_length += step.x.hypot(step.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
let diagonal = (right - left).hypot(bottom - top);
|
||||||
|
if diagonal < SHAKE_DIAGONAL_MIN {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path noticeably longer than the diagonal => a shake gesture.
|
||||||
|
if path_length / diagonal > SHAKE_SENSITIVITY {
|
||||||
|
self.grow(now);
|
||||||
|
self.shake_path.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// grow the cursor by one more increment (unbounded)
|
||||||
|
fn grow(&mut self, now: Instant) {
|
||||||
|
self.animate_to(self.magnify_target + OVER_MAGNIFICATION, now);
|
||||||
|
self.magnify_until = Some(now + SHAKE_HOLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start a 200ms `InOutCubic` tween from the current size to `target`.
|
||||||
|
fn animate_to(&mut self, target: f32, now: Instant) {
|
||||||
|
if (target - self.magnify_target).abs() < f32::EPSILON {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.anim_from = self.magnification;
|
||||||
|
self.anim_start = Some(now);
|
||||||
|
self.magnify_target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advance the magnification animation and return the current factor.
|
||||||
|
pub fn animated_magnification(&mut self, now: Instant) -> f32 {
|
||||||
|
// Begin shrinking back once the hold window elapses.
|
||||||
|
if let Some(until) = self.magnify_until
|
||||||
|
&& now >= until
|
||||||
|
{
|
||||||
|
self.magnify_until = None;
|
||||||
|
self.animate_to(1.0, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.magnification = match self.anim_start {
|
||||||
|
Some(start) => {
|
||||||
|
// `ease` clamps the time to `0.0..=1.0` for us.
|
||||||
|
let t = now.duration_since(start).as_secs_f32() / MAGNIFICATION_ANIM.as_secs_f32();
|
||||||
|
if t >= 1.0 {
|
||||||
|
self.anim_start = None;
|
||||||
|
}
|
||||||
|
ease(EaseInOutCubic, self.anim_from, self.magnify_target, t)
|
||||||
|
}
|
||||||
|
None => self.magnify_target,
|
||||||
|
};
|
||||||
|
self.magnification
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether the cursor is currently magnified or pending; drives continued redraws.
|
||||||
|
pub fn is_magnifying(&self) -> bool {
|
||||||
|
self.magnify_until.is_some()
|
||||||
|
|| self.anim_start.is_some()
|
||||||
|
|| self.magnification > 1.001
|
||||||
|
|| self.magnify_target > 1.001
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load_cursor_env() -> (String, u32) {
|
pub fn load_cursor_env() -> (String, u32) {
|
||||||
|
|
@ -321,13 +723,38 @@ impl Default for CursorStateInner {
|
||||||
current_image: None,
|
current_image: None,
|
||||||
image_cache: Vec::new(),
|
image_cache: Vec::new(),
|
||||||
|
|
||||||
hidden: false,
|
hidden: None,
|
||||||
|
last_pointer_activity: Instant::now(),
|
||||||
idle_timer: None,
|
idle_timer: None,
|
||||||
last_armed: None,
|
last_armed: None,
|
||||||
|
|
||||||
|
shake_path: VecDeque::new(),
|
||||||
|
shake_path_position: Point::from((0.0, 0.0)),
|
||||||
|
magnify_until: None,
|
||||||
|
magnify_target: 1.0,
|
||||||
|
magnification: 1.0,
|
||||||
|
anim_from: 1.0,
|
||||||
|
anim_start: None,
|
||||||
|
rest_started: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pick the size a cursor frame is rasterized at, given the size the output wants
|
||||||
|
/// (`needed`) and the size it would want unmagnified (`base`).
|
||||||
|
///
|
||||||
|
/// Rasterizations are restricted to `base * 2^n`, rounded up, and clamped to
|
||||||
|
/// [`MAX_RASTER_SIZE`].
|
||||||
|
fn raster_size(needed: u32, base: u32) -> u32 {
|
||||||
|
let base = base.max(1);
|
||||||
|
let cap = MAX_RASTER_SIZE.max(base);
|
||||||
|
let mut rung = base;
|
||||||
|
while rung < needed && rung.saturating_mul(2) <= cap {
|
||||||
|
rung *= 2;
|
||||||
|
}
|
||||||
|
rung
|
||||||
|
}
|
||||||
|
|
||||||
#[profiling::function]
|
#[profiling::function]
|
||||||
pub fn draw_cursor<R>(
|
pub fn draw_cursor<R>(
|
||||||
renderer: &mut R,
|
renderer: &mut R,
|
||||||
|
|
@ -350,7 +777,7 @@ pub fn draw_cursor<R>(
|
||||||
let mut state_ref = seat_userdata.get::<CursorState>().unwrap().lock().unwrap();
|
let mut state_ref = seat_userdata.get::<CursorState>().unwrap().lock().unwrap();
|
||||||
let state = &mut *state_ref;
|
let state = &mut *state_ref;
|
||||||
|
|
||||||
if state.hidden {
|
if state.hidden.is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -363,32 +790,53 @@ pub fn draw_cursor<R>(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let integer_scale = (scale.x.max(scale.y) * buffer_scale).ceil() as u32;
|
let output_scale = scale.x.max(scale.y);
|
||||||
let frame = state
|
let integer_scale = (output_scale * buffer_scale).ceil() as u32;
|
||||||
.get_named_cursor(current_cursor)
|
let unmagnified_px = state.size() * (output_scale.ceil() as u32);
|
||||||
.get_image(integer_scale, time.as_millis());
|
let size_px = raster_size(state.size() * integer_scale, unmagnified_px);
|
||||||
let actual_scale = (frame.size / state.size()).max(1);
|
|
||||||
|
|
||||||
let pointer_images = &mut state.image_cache;
|
// Pick the frame to display without rasterizing, so a cache hit avoids
|
||||||
let maybe_image = pointer_images
|
// any SVG rendering. The `&Cursor` borrow is scoped to this block.
|
||||||
.iter()
|
let frame_idx = {
|
||||||
.find_map(|(image, texture)| if image == &frame { Some(texture) } else { None });
|
let cursor = state.get_named_cursor(current_cursor);
|
||||||
let pointer_image = match maybe_image {
|
cursor.frame_index(size_px, time.as_millis())
|
||||||
Some(image) => image,
|
};
|
||||||
|
let key = (current_cursor, size_px, frame_idx);
|
||||||
|
|
||||||
|
// Rasterize and upload this (shape, size, frame) only if not cached.
|
||||||
|
let index = match state.image_cache.iter().position(|frame| frame.key == key) {
|
||||||
|
Some(index) => index,
|
||||||
None => {
|
None => {
|
||||||
|
let image = {
|
||||||
|
let cursor = state.get_named_cursor(current_cursor);
|
||||||
|
cursor.render_frame(size_px, frame_idx)
|
||||||
|
};
|
||||||
|
let actual_scale = (image.size / state.size()).max(1);
|
||||||
let buffer = MemoryRenderBuffer::from_slice(
|
let buffer = MemoryRenderBuffer::from_slice(
|
||||||
&frame.pixels_rgba,
|
&image.pixels_rgba,
|
||||||
Fourcc::Argb8888,
|
Fourcc::Argb8888,
|
||||||
(frame.width as i32, frame.height as i32),
|
(image.width as i32, image.height as i32),
|
||||||
actual_scale as i32,
|
actual_scale as i32,
|
||||||
Transform::Normal,
|
Transform::Normal,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
pointer_images.push((frame.clone(), buffer));
|
state.image_cache.push(CachedFrame {
|
||||||
pointer_images.last().map(|(_, i)| i).unwrap()
|
key,
|
||||||
|
image,
|
||||||
|
buffer,
|
||||||
|
unmagnified: size_px == unmagnified_px,
|
||||||
|
});
|
||||||
|
state.image_cache.len() - 1
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let (frame, pointer_image) = {
|
||||||
|
let entry = &mut state.image_cache[index];
|
||||||
|
entry.unmagnified |= size_px == unmagnified_px;
|
||||||
|
(entry.image.clone(), entry.buffer.clone())
|
||||||
|
};
|
||||||
|
let actual_scale = (frame.size / state.size()).max(1);
|
||||||
|
|
||||||
let hotspot = Point::<i32, BufferCoords>::from((frame.xhot as i32, frame.yhot as i32))
|
let hotspot = Point::<i32, BufferCoords>::from((frame.xhot as i32, frame.yhot as i32))
|
||||||
.to_logical(
|
.to_logical(
|
||||||
actual_scale as i32,
|
actual_scale as i32,
|
||||||
|
|
@ -402,7 +850,7 @@ pub fn draw_cursor<R>(
|
||||||
MemoryRenderBufferRenderElement::from_buffer(
|
MemoryRenderBufferRenderElement::from_buffer(
|
||||||
renderer,
|
renderer,
|
||||||
location.to_physical(scale),
|
location.to_physical(scale),
|
||||||
pointer_image,
|
&pointer_image,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
|
|
@ -419,68 +867,175 @@ pub fn draw_cursor<R>(
|
||||||
|
|
||||||
const ACTIVITY_THROTTLE: Duration = Duration::from_millis(100);
|
const ACTIVITY_THROTTLE: Duration = Duration::from_millis(100);
|
||||||
|
|
||||||
/// Reveal the cursor and (re)arm the idle-hide timer; returns true if it was previously hidden
|
/// Reveal the cursor if this event should, and (re)arm the idle-hide timer.
|
||||||
pub fn notify_cursor_activity(state: &State, seat: &Seat<State>) -> bool {
|
/// Returns true if the cursor was previously hidden and is now visible.
|
||||||
let timeout = state.common.config.cosmic_conf.cursor_hide_timeout;
|
pub fn notify_cursor_activity(state: &State, seat: &Seat<State>, kind: PointerEventKind) -> bool {
|
||||||
|
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
|
||||||
|
|
||||||
|
let revealed = {
|
||||||
|
let mut inner = cursor_state.lock().unwrap();
|
||||||
|
inner.last_pointer_activity = Instant::now();
|
||||||
|
match inner.hidden {
|
||||||
|
Some(reason) if reason.revealed_by(kind) => {
|
||||||
|
inner.hidden = None;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
// Still hidden on purpose: leave the timer alone.
|
||||||
|
Some(_) => return false,
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
refresh_idle_timer(state, seat);
|
||||||
|
revealed
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (Re)arm the idle-hide timer without counting as pointer activity.
|
||||||
|
///
|
||||||
|
/// The deadline is measured from the last *pointer* event, not from now, so a
|
||||||
|
/// caller that does not stamp activity — a keystroke, a config reload — cannot
|
||||||
|
/// postpone the hide by re-arming.
|
||||||
|
///
|
||||||
|
/// Armed with the shortest timeout any context could want: the input that arms
|
||||||
|
/// the timer is usually what goes on to make a window fullscreen, so the
|
||||||
|
/// context is not yet knowable here. `on_idle_timer` evaluates it on fire and
|
||||||
|
/// re-arms for the remainder if nothing applied yet.
|
||||||
|
pub fn refresh_idle_timer(state: &State, seat: &Seat<State>) {
|
||||||
|
let config = state.common.config.cosmic_conf.cursor_hide;
|
||||||
let loop_handle = &state.common.event_loop_handle;
|
let loop_handle = &state.common.event_loop_handle;
|
||||||
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
|
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
|
||||||
let (was_hidden, old_token) = {
|
let (old_token, since_activity) = {
|
||||||
let mut inner = cursor_state.lock().unwrap();
|
let mut inner = cursor_state.lock().unwrap();
|
||||||
let was_hidden = inner.hidden;
|
if inner.hidden.is_some() {
|
||||||
inner.hidden = false;
|
return;
|
||||||
|
}
|
||||||
let throttled = timeout.is_some()
|
// Coalesce bursts from high-frequency pointers.
|
||||||
&& !was_hidden
|
let throttled = inner.idle_timer.is_some()
|
||||||
&& inner.idle_timer.is_some()
|
|
||||||
&& inner
|
&& inner
|
||||||
.last_armed
|
.last_armed
|
||||||
.is_some_and(|t| now.duration_since(t) < ACTIVITY_THROTTLE);
|
.is_some_and(|t| now.duration_since(t) < ACTIVITY_THROTTLE);
|
||||||
if throttled {
|
if throttled {
|
||||||
return was_hidden;
|
return;
|
||||||
}
|
}
|
||||||
|
let since_activity = now.duration_since(inner.last_pointer_activity);
|
||||||
let old_token = inner.idle_timer.take();
|
|
||||||
inner.last_armed = None;
|
inner.last_armed = None;
|
||||||
(was_hidden, old_token)
|
(inner.idle_timer.take(), since_activity)
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(token) = old_token {
|
if let Some(token) = old_token {
|
||||||
loop_handle.remove(token);
|
loop_handle.remove(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(secs) = timeout {
|
let Some(delay) = config.arm_delay(since_activity) else {
|
||||||
let timer = Timer::from_duration(Duration::from_secs(secs as u64));
|
return;
|
||||||
let seat = seat.clone();
|
};
|
||||||
if let Ok(token) = loop_handle.insert_source(timer, move |_, _, state| {
|
|
||||||
hide_cursor(state, &seat);
|
let timer = Timer::from_duration(delay);
|
||||||
TimeoutAction::Drop
|
let timer_seat = seat.clone();
|
||||||
}) {
|
match loop_handle.insert_source(timer, move |_, _, state| on_idle_timer(state, &timer_seat)) {
|
||||||
|
Ok(token) => {
|
||||||
let mut inner = cursor_state.lock().unwrap();
|
let mut inner = cursor_state.lock().unwrap();
|
||||||
inner.idle_timer = Some(token);
|
inner.idle_timer = Some(token);
|
||||||
inner.last_armed = Some(now);
|
inner.last_armed = Some(now);
|
||||||
}
|
}
|
||||||
|
// `idle_timer` stays `None`, so the next activity is unthrottled and retries.
|
||||||
|
Err(err) => warn!(?err, "Failed to arm the cursor idle-hide timer"),
|
||||||
}
|
}
|
||||||
|
|
||||||
was_hidden
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hide_cursor(state: &mut State, seat: &Seat<State>) {
|
/// Whether the seat is focusing a fullscreen surface. A fullscreen window that
|
||||||
if let Some(ptr) = seat.get_pointer()
|
/// is not focused can be stacked behind others, so its presence on the output
|
||||||
&& ptr.is_grabbed()
|
/// says nothing about what the user is looking at.
|
||||||
{
|
fn fullscreen_focused(seat: &Seat<State>) -> bool {
|
||||||
return;
|
matches!(
|
||||||
|
seat.get_keyboard().and_then(|kbd| kbd.current_focus()),
|
||||||
|
Some(KeyboardFocusTarget::Fullscreen(_))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_idle_timer(state: &mut State, seat: &Seat<State>) -> TimeoutAction {
|
||||||
|
let config = state.common.config.cosmic_conf.cursor_hide;
|
||||||
|
let fullscreen = fullscreen_focused(seat);
|
||||||
|
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
|
||||||
|
|
||||||
|
let elapsed = {
|
||||||
|
let inner = cursor_state.lock().unwrap();
|
||||||
|
Instant::now().duration_since(inner.last_pointer_activity)
|
||||||
|
};
|
||||||
|
|
||||||
|
match config.resolve(elapsed, fullscreen) {
|
||||||
|
HideDecision::Hide => {
|
||||||
|
hide_cursor(state, seat, HideReason::Idle);
|
||||||
|
TimeoutAction::Drop
|
||||||
|
}
|
||||||
|
HideDecision::Drop => {
|
||||||
|
let mut inner = cursor_state.lock().unwrap();
|
||||||
|
inner.idle_timer = None;
|
||||||
|
inner.last_armed = None;
|
||||||
|
TimeoutAction::Drop
|
||||||
|
}
|
||||||
|
HideDecision::RearmAfter(delay) => {
|
||||||
|
cursor_state.lock().unwrap().last_armed = Some(Instant::now());
|
||||||
|
TimeoutAction::ToDuration(delay)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hide the cursor immediately, for a trigger that does not wait on the timer.
|
||||||
|
pub fn hide_cursor_now(state: &mut State, seat: &Seat<State>, reason: HideReason) {
|
||||||
|
let loop_handle = state.common.event_loop_handle.clone();
|
||||||
|
let token = {
|
||||||
|
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
|
||||||
|
let mut inner = cursor_state.lock().unwrap();
|
||||||
|
inner.idle_timer.take()
|
||||||
|
};
|
||||||
|
if let Some(token) = token {
|
||||||
|
loop_handle.remove(token);
|
||||||
|
}
|
||||||
|
hide_cursor(state, seat, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hide_cursor(state: &mut State, seat: &Seat<State>, reason: HideReason) {
|
||||||
|
let grabbed = seat.get_pointer().is_some_and(|ptr| ptr.is_grabbed());
|
||||||
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
|
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
|
||||||
{
|
{
|
||||||
let mut inner = cursor_state.lock().unwrap();
|
let mut inner = cursor_state.lock().unwrap();
|
||||||
inner.hidden = true;
|
// Every caller has already disposed of the timer source, so clear the
|
||||||
|
// bookkeeping before the grab check rather than only on success.
|
||||||
inner.idle_timer = None;
|
inner.idle_timer = None;
|
||||||
inner.last_armed = None;
|
inner.last_armed = None;
|
||||||
|
// The reason only ever tightens: `Touch` waits for real movement, so a
|
||||||
|
// later trigger must not relax it back to click-or-scroll.
|
||||||
|
if grabbed || inner.hidden == Some(reason) || inner.hidden == Some(HideReason::Touch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
inner.hidden = Some(reason);
|
||||||
}
|
}
|
||||||
let outputs: Vec<_> = state.common.shell.read().outputs().cloned().collect();
|
let outputs: Vec<_> = state.common.shell.read().outputs().cloned().collect();
|
||||||
for output in outputs {
|
for output in outputs {
|
||||||
state.backend.schedule_render(&output);
|
state.backend.schedule_render(&output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::{HideReason, PointerEventKind};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn touch_hidden_cursor_waits_for_movement() {
|
||||||
|
assert!(HideReason::Touch.revealed_by(PointerEventKind::Motion));
|
||||||
|
assert!(!HideReason::Touch.revealed_by(PointerEventKind::Other));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn idle_and_typing_hidden_cursors_are_revealed_by_any_pointer_event() {
|
||||||
|
for reason in [HideReason::Idle, HideReason::Typing] {
|
||||||
|
assert!(reason.revealed_by(PointerEventKind::Motion));
|
||||||
|
// A click or a scroll must rescue the cursor rather than forcing a
|
||||||
|
// blind click.
|
||||||
|
assert!(reason.revealed_by(PointerEventKind::Other));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,9 @@ where
|
||||||
Workspace(
|
Workspace(
|
||||||
RelocateRenderElement<CropRenderElement<RescaleRenderElement<WorkspaceRenderElement<R>>>>,
|
RelocateRenderElement<CropRenderElement<RescaleRenderElement<WorkspaceRenderElement<R>>>>,
|
||||||
),
|
),
|
||||||
Cursor(RescaleRenderElement<RelocateRenderElement<CursorRenderElement<R>>>),
|
Cursor(
|
||||||
|
RescaleRenderElement<RescaleRenderElement<RelocateRenderElement<CursorRenderElement<R>>>>,
|
||||||
|
),
|
||||||
Dnd(SurfaceRenderElement<R>),
|
Dnd(SurfaceRenderElement<R>),
|
||||||
MoveGrab(RescaleRenderElement<CosmicMappedRenderElement<R>>),
|
MoveGrab(RescaleRenderElement<CosmicMappedRenderElement<R>>),
|
||||||
Postprocess(
|
Postprocess(
|
||||||
|
|
|
||||||
|
|
@ -286,10 +286,10 @@ impl IndicatorShader {
|
||||||
Uniform::new(
|
Uniform::new(
|
||||||
"radius",
|
"radius",
|
||||||
[
|
[
|
||||||
outer_radius[3] as f32,
|
|
||||||
outer_radius[1] as f32,
|
|
||||||
outer_radius[0] as f32,
|
outer_radius[0] as f32,
|
||||||
|
outer_radius[1] as f32,
|
||||||
outer_radius[2] as f32,
|
outer_radius[2] as f32,
|
||||||
|
outer_radius[3] as f32,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Uniform::new("scale", scale as f32),
|
Uniform::new("scale", scale as f32),
|
||||||
|
|
@ -510,22 +510,35 @@ pub fn cursor_elements<'a, 'frame, R>(
|
||||||
};
|
};
|
||||||
let location = pointer.current_location() - output.current_location().to_f64();
|
let location = pointer.current_location() - output.current_location().to_f64();
|
||||||
|
|
||||||
|
// Shake-to-find magnification, applied around the pointer tip.
|
||||||
|
let cursor_magnification = seat
|
||||||
|
.user_data()
|
||||||
|
.get::<cursor::CursorState>()
|
||||||
|
.map_or(1.0, |s| {
|
||||||
|
s.lock().unwrap().animated_magnification(Instant::now())
|
||||||
|
});
|
||||||
|
let cursor_center = location.to_physical(scale).to_i32_round();
|
||||||
|
|
||||||
if mode != CursorMode::None {
|
if mode != CursorMode::None {
|
||||||
cursor::draw_cursor(
|
cursor::draw_cursor(
|
||||||
renderer,
|
renderer,
|
||||||
seat,
|
seat,
|
||||||
location,
|
location,
|
||||||
scale.into(),
|
scale.into(),
|
||||||
zoom_scale,
|
zoom_scale * cursor_magnification as f64,
|
||||||
now,
|
now,
|
||||||
blur_strength,
|
blur_strength,
|
||||||
mode != CursorMode::NotDefault,
|
mode != CursorMode::NotDefault,
|
||||||
&mut |elem, hotspot| {
|
&mut |elem, hotspot| {
|
||||||
push(CosmicElement::Cursor(RescaleRenderElement::from_element(
|
push(CosmicElement::Cursor(RescaleRenderElement::from_element(
|
||||||
RelocateRenderElement::from_element(
|
RescaleRenderElement::from_element(
|
||||||
elem,
|
RelocateRenderElement::from_element(
|
||||||
Point::from((-hotspot.x, -hotspot.y)),
|
elem,
|
||||||
Relocate::Relative,
|
Point::from((-hotspot.x, -hotspot.y)),
|
||||||
|
Relocate::Relative,
|
||||||
|
),
|
||||||
|
cursor_center,
|
||||||
|
cursor_magnification as f64,
|
||||||
),
|
),
|
||||||
focal_point
|
focal_point
|
||||||
.as_logical()
|
.as_logical()
|
||||||
|
|
@ -886,8 +899,7 @@ where
|
||||||
let geometry = geometry.to_local(output).as_logical();
|
let geometry = geometry.to_local(output).as_logical();
|
||||||
|
|
||||||
let padded = with_states(layer.wl_surface(), |states| {
|
let padded = with_states(layer.wl_surface(), |states| {
|
||||||
surface_padding(states, geometry.size)
|
surface_padding(states).and_then(|padding| pad_rect(geometry, &padding))
|
||||||
.and_then(|padding| pad_rect(geometry, &padding))
|
|
||||||
})
|
})
|
||||||
.unwrap_or(geometry);
|
.unwrap_or(geometry);
|
||||||
let radii = with_states(layer.wl_surface(), |states| {
|
let radii = with_states(layer.wl_surface(), |states| {
|
||||||
|
|
|
||||||
|
|
@ -86,12 +86,6 @@ impl ShadowShader {
|
||||||
let offset = [0., 5.];
|
let offset = [0., 5.];
|
||||||
let color = [0., 0., 0., if dark_mode { 0.45 } else { 0.35 }];
|
let color = [0., 0., 0., if dark_mode { 0.45 } else { 0.35 }];
|
||||||
let radius = radius.map(|r| ceil(r as f64));
|
let radius = radius.map(|r| ceil(r as f64));
|
||||||
let radius = [
|
|
||||||
radius[3], // top_left
|
|
||||||
radius[1], // top_right
|
|
||||||
radius[0], // bottom_right
|
|
||||||
radius[2], // bottom_left
|
|
||||||
];
|
|
||||||
|
|
||||||
let width = softness;
|
let width = softness;
|
||||||
let sigma = width / 2.;
|
let sigma = width / 2.;
|
||||||
|
|
|
||||||
|
|
@ -247,10 +247,10 @@ impl BlurElement {
|
||||||
Uniform::new(
|
Uniform::new(
|
||||||
"corner_radius",
|
"corner_radius",
|
||||||
[
|
[
|
||||||
radii[3] as f32,
|
|
||||||
radii[1] as f32,
|
|
||||||
radii[0] as f32,
|
radii[0] as f32,
|
||||||
|
radii[1] as f32,
|
||||||
radii[2] as f32,
|
radii[2] as f32,
|
||||||
|
radii[3] as f32,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Uniform::new(
|
Uniform::new(
|
||||||
|
|
|
||||||
|
|
@ -99,10 +99,10 @@ where
|
||||||
Uniform::new(
|
Uniform::new(
|
||||||
"corner_radius",
|
"corner_radius",
|
||||||
[
|
[
|
||||||
radius[3] as f32,
|
|
||||||
radius[1] as f32,
|
|
||||||
radius[0] as f32,
|
radius[0] as f32,
|
||||||
|
radius[1] as f32,
|
||||||
radius[2] as f32,
|
radius[2] as f32,
|
||||||
|
radius[3] as f32,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Uniform::new(
|
Uniform::new(
|
||||||
|
|
@ -145,10 +145,10 @@ where
|
||||||
geo: Rectangle<f64, Logical>,
|
geo: Rectangle<f64, Logical>,
|
||||||
radius: [u8; 4],
|
radius: [u8; 4],
|
||||||
) -> [Rectangle<f64, Logical>; 4] {
|
) -> [Rectangle<f64, Logical>; 4] {
|
||||||
let top_left = radius[3] as f64;
|
let top_left = radius[0] as f64;
|
||||||
let top_right = radius[1] as f64;
|
let top_right = radius[1] as f64;
|
||||||
let bottom_right = radius[0] as f64;
|
let bottom_right = radius[2] as f64;
|
||||||
let bottom_left = radius[2] as f64;
|
let bottom_left = radius[3] as f64;
|
||||||
|
|
||||||
[
|
[
|
||||||
Rectangle::new(geo.loc, Size::from((top_left, top_left))),
|
Rectangle::new(geo.loc, Size::from((top_left, top_left))),
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,15 @@ use crate::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use cosmic_config::{ConfigGet, CosmicConfigEntry};
|
use cosmic_config::{ConfigGet, ConfigSet, CosmicConfigEntry};
|
||||||
use cosmic_settings_config::window_rules::ApplicationException;
|
use cosmic_settings_config::window_rules::ApplicationException;
|
||||||
use cosmic_settings_config::{Shortcuts, shortcuts, window_rules};
|
use cosmic_settings_config::{Shortcuts, shortcuts, window_rules};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use smithay::utils::{Clock, Monotonic};
|
|
||||||
use smithay::wayland::xdg_activation::XdgActivationState;
|
use smithay::wayland::xdg_activation::XdgActivationState;
|
||||||
|
use smithay::{
|
||||||
|
backend::input::InputTime,
|
||||||
|
utils::{Clock, Monotonic},
|
||||||
|
};
|
||||||
pub use smithay::{
|
pub use smithay::{
|
||||||
backend::input::{self as smithay_input, KeyState},
|
backend::input::{self as smithay_input, KeyState},
|
||||||
input::keyboard::{Keysym, ModifiersState, keysyms as KeySyms},
|
input::keyboard::{Keysym, ModifiersState, keysyms as KeySyms},
|
||||||
|
|
@ -46,8 +49,8 @@ mod types;
|
||||||
use cosmic::config::CosmicTk;
|
use cosmic::config::CosmicTk;
|
||||||
pub use cosmic_comp_config::EdidProduct;
|
pub use cosmic_comp_config::EdidProduct;
|
||||||
use cosmic_comp_config::{
|
use cosmic_comp_config::{
|
||||||
ActivationPolicy, AppearanceConfig, CosmicCompConfig, KeyboardConfig, TileBehavior, XkbConfig,
|
ActivationPolicy, AppearanceConfig, CosmicCompConfig, CursorHideConfig, DecorationPreference,
|
||||||
XwaylandDescaling, XwaylandEavesdropping, ZoomConfig,
|
KeyboardConfig, TileBehavior, XkbConfig, XwaylandDescaling, XwaylandEavesdropping, ZoomConfig,
|
||||||
input::{DeviceState as InputDeviceState, InputConfig, TouchpadOverride},
|
input::{DeviceState as InputDeviceState, InputConfig, TouchpadOverride},
|
||||||
output::comp::{
|
output::comp::{
|
||||||
OutputConfig, OutputInfo, OutputState, OutputsConfig, TransformDef, load_outputs,
|
OutputConfig, OutputInfo, OutputState, OutputsConfig, TransformDef, load_outputs,
|
||||||
|
|
@ -169,7 +172,7 @@ pub enum ColorFilter {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn load(loop_handle: &LoopHandle<'_, State>) -> Config {
|
pub fn load(loop_handle: &LoopHandle<'_, State>, kiosk_mode: bool) -> Config {
|
||||||
let config = cosmic_config::Config::new("com.system76.CosmicComp", 1).unwrap();
|
let config = cosmic_config::Config::new("com.system76.CosmicComp", 1).unwrap();
|
||||||
let source = cosmic_config::calloop::ConfigWatchSource::new(&config).unwrap();
|
let source = cosmic_config::calloop::ConfigWatchSource::new(&config).unwrap();
|
||||||
loop_handle
|
loop_handle
|
||||||
|
|
@ -179,7 +182,7 @@ impl Config {
|
||||||
.expect("Failed to add cosmic-config to the event loop");
|
.expect("Failed to add cosmic-config to the event loop");
|
||||||
let xdg = xdg::BaseDirectories::new();
|
let xdg = xdg::BaseDirectories::new();
|
||||||
|
|
||||||
let cosmic_comp_config =
|
let mut cosmic_comp_config =
|
||||||
CosmicCompConfig::get_entry(&config).unwrap_or_else(|(errs, c)| {
|
CosmicCompConfig::get_entry(&config).unwrap_or_else(|(errs, c)| {
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
for err in errs {
|
for err in errs {
|
||||||
|
|
@ -189,6 +192,18 @@ impl Config {
|
||||||
c
|
c
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// `cursor_hide_timeout` was replaced by the grouped `cursor_hide` key.
|
||||||
|
// Seed the new key once so configs hand-edited before the rename keep
|
||||||
|
// working; the old file is left in place so a downgrade still reads it.
|
||||||
|
if config.get::<CursorHideConfig>("cursor_hide").is_err()
|
||||||
|
&& let Ok(legacy) = config.get::<Option<u32>>("cursor_hide_timeout")
|
||||||
|
{
|
||||||
|
cosmic_comp_config.cursor_hide.idle_timeout = legacy;
|
||||||
|
if let Err(err) = config.set("cursor_hide", cosmic_comp_config.cursor_hide) {
|
||||||
|
warn!(?err, "Failed to migrate cursor_hide_timeout to cursor_hide");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Listen for updates to the toolkit config
|
// Listen for updates to the toolkit config
|
||||||
if let Ok(tk_config) = cosmic_config::Config::new("com.system76.CosmicTk", 1) {
|
if let Ok(tk_config) = cosmic_config::Config::new("com.system76.CosmicTk", 1) {
|
||||||
fn handle_new_toolkit_config(config: CosmicTk, state: &mut State) {
|
fn handle_new_toolkit_config(config: CosmicTk, state: &mut State) {
|
||||||
|
|
@ -245,40 +260,48 @@ impl Config {
|
||||||
|
|
||||||
// Source key bindings from com.system76.CosmicSettings.Shortcuts
|
// Source key bindings from com.system76.CosmicSettings.Shortcuts
|
||||||
let settings_context = shortcuts::context().expect("Failed to load shortcuts config");
|
let settings_context = shortcuts::context().expect("Failed to load shortcuts config");
|
||||||
let system_actions = shortcuts::system_actions(&settings_context);
|
let mut system_actions = Default::default();
|
||||||
let shortcuts = shortcuts::shortcuts(&settings_context);
|
let mut shortcuts = Default::default();
|
||||||
|
// Kiosk mode disables shortcuts
|
||||||
|
if !kiosk_mode {
|
||||||
|
system_actions = shortcuts::system_actions(&settings_context);
|
||||||
|
shortcuts = shortcuts::shortcuts(&settings_context);
|
||||||
|
|
||||||
// Listen for updates to the keybindings config.
|
// Listen for updates to the keybindings config.
|
||||||
match cosmic_config::calloop::ConfigWatchSource::new(&settings_context) {
|
match cosmic_config::calloop::ConfigWatchSource::new(&settings_context) {
|
||||||
Ok(source) => {
|
Ok(source) => {
|
||||||
if let Err(err) = loop_handle.insert_source(source, |(config, keys), (), state| {
|
if let Err(err) =
|
||||||
for key in keys {
|
loop_handle.insert_source(source, |(config, keys), (), state| {
|
||||||
match key.as_str() {
|
for key in keys {
|
||||||
// Reload the keyboard shortcuts config.
|
match key.as_str() {
|
||||||
"custom" | "defaults" => {
|
// Reload the keyboard shortcuts config.
|
||||||
state.common.config.shortcuts = shortcuts::shortcuts(&config);
|
"custom" | "defaults" => {
|
||||||
|
state.common.config.shortcuts =
|
||||||
|
shortcuts::shortcuts(&config);
|
||||||
|
}
|
||||||
|
|
||||||
|
"system_actions" => {
|
||||||
|
state.common.config.system_actions =
|
||||||
|
shortcuts::system_actions(&config);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
"system_actions" => {
|
{
|
||||||
state.common.config.system_actions =
|
warn!(
|
||||||
shortcuts::system_actions(&config);
|
?err,
|
||||||
}
|
"Failed to watch com.system76.CosmicSettings.Shortcuts config"
|
||||||
|
);
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}) {
|
|
||||||
warn!(
|
|
||||||
?err,
|
|
||||||
"Failed to watch com.system76.CosmicSettings.Shortcuts config"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
Err(err) => warn!(
|
||||||
Err(err) => warn!(
|
?err,
|
||||||
?err,
|
"failed to create config watch source for com.system76.CosmicSettings.Shortcuts"
|
||||||
"failed to create config watch source for com.system76.CosmicSettings.Shortcuts"
|
),
|
||||||
),
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
let window_rules_context =
|
let window_rules_context =
|
||||||
window_rules::context().expect("Failed to load window rules config");
|
window_rules::context().expect("Failed to load window rules config");
|
||||||
|
|
@ -538,7 +561,13 @@ impl Config {
|
||||||
primary.config_mut().xwayland_primary = true;
|
primary.config_mut().xwayland_primary = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for output in outputs.iter().filter(|o| o.mirroring().is_none()) {
|
// sort by connector name for a deterministic layout independent of hotplug order
|
||||||
|
let mut sorted_outputs = outputs
|
||||||
|
.iter()
|
||||||
|
.filter(|o| o.mirroring().is_none())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
sorted_outputs.sort_by_key(|o| o.name());
|
||||||
|
for output in sorted_outputs {
|
||||||
{
|
{
|
||||||
let mut config = output.config_mut();
|
let mut config = output.config_mut();
|
||||||
config.position = (w, 0);
|
config.position = (w, 0);
|
||||||
|
|
@ -770,13 +799,12 @@ pub fn change_modifier_state(
|
||||||
const X11_KEYCODE_OFFSET: u32 = 8;
|
const X11_KEYCODE_OFFSET: u32 = 8;
|
||||||
|
|
||||||
let mut input = |key_state, scan_code| {
|
let mut input = |key_state, scan_code| {
|
||||||
let time = state.common.clock.now().as_millis();
|
|
||||||
let _ = keyboard.input(
|
let _ = keyboard.input(
|
||||||
state,
|
state,
|
||||||
smithay_input::Keycode::new(scan_code + X11_KEYCODE_OFFSET),
|
smithay_input::Keycode::new(scan_code + X11_KEYCODE_OFFSET),
|
||||||
key_state,
|
key_state,
|
||||||
SERIAL_COUNTER.next_serial(),
|
SERIAL_COUNTER.next_serial(),
|
||||||
time,
|
InputTime::now(),
|
||||||
|_, _, _| smithay::input::keyboard::FilterResult::<()>::Forward,
|
|_, _, _| smithay::input::keyboard::FilterResult::<()>::Forward,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -840,7 +868,9 @@ fn config_changed(config: cosmic_config::Config, keys: Vec<String>, state: &mut
|
||||||
}
|
}
|
||||||
if !state.common.ei_seats.is_empty() {
|
if !state.common.ei_seats.is_empty() {
|
||||||
let seat = state.common.shell.read().seats.last_active().clone();
|
let seat = state.common.shell.read().seats.last_active().clone();
|
||||||
state.broadcast_ei_keyboard_modifiers(&seat);
|
if let Some(keyboard) = seat.get_keyboard() {
|
||||||
|
state.broadcast_ei_keyboard_modifiers(&keyboard);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
state.common.config.cosmic_conf.xkb_config = value;
|
state.common.config.cosmic_conf.xkb_config = value;
|
||||||
}
|
}
|
||||||
|
|
@ -969,15 +999,25 @@ fn config_changed(config: cosmic_config::Config, keys: Vec<String>, state: &mut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"cursor_hide_timeout" => {
|
"cursor_shake_to_find" => {
|
||||||
let new = get_config::<Option<u32>>(&config, "cursor_hide_timeout");
|
let new = get_config::<bool>(&config, "cursor_shake_to_find");
|
||||||
if new != state.common.config.cosmic_conf.cursor_hide_timeout {
|
state.common.config.cosmic_conf.cursor_shake_to_find = new;
|
||||||
state.common.config.cosmic_conf.cursor_hide_timeout = new;
|
}
|
||||||
|
"cursor_hide" => {
|
||||||
|
let new = get_config::<CursorHideConfig>(&config, "cursor_hide");
|
||||||
|
if new != state.common.config.cosmic_conf.cursor_hide {
|
||||||
|
state.common.config.cosmic_conf.cursor_hide = new;
|
||||||
|
// Reveal on every change: a visible cursor is the safe state,
|
||||||
|
// and it avoids stranding a hidden cursor when the trigger
|
||||||
|
// that hid it is switched off.
|
||||||
let seats: Vec<_> = state.common.shell.read().seats.iter().cloned().collect();
|
let seats: Vec<_> = state.common.shell.read().seats.iter().cloned().collect();
|
||||||
let mut needs_render = false;
|
let mut needs_render = false;
|
||||||
for seat in seats {
|
for seat in seats {
|
||||||
needs_render |=
|
needs_render |= crate::backend::render::cursor::notify_cursor_activity(
|
||||||
crate::backend::render::cursor::notify_cursor_activity(state, &seat);
|
state,
|
||||||
|
&seat,
|
||||||
|
crate::backend::render::cursor::PointerEventKind::Motion,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if needs_render {
|
if needs_render {
|
||||||
let outputs: Vec<_> =
|
let outputs: Vec<_> =
|
||||||
|
|
@ -994,6 +1034,13 @@ fn config_changed(config: cosmic_config::Config, keys: Vec<String>, state: &mut
|
||||||
state.common.config.cosmic_conf.activation_policy = new;
|
state.common.config.cosmic_conf.activation_policy = new;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"decoration_preference" => {
|
||||||
|
let new = get_config::<DecorationPreference>(&config, "decoration_preference");
|
||||||
|
if new != state.common.config.cosmic_conf.decoration_preference {
|
||||||
|
state.common.config.cosmic_conf.decoration_preference = new;
|
||||||
|
state.update_decorations();
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ use cosmic_config::ConfigSet;
|
||||||
use cosmic_settings_config::shortcuts;
|
use cosmic_settings_config::shortcuts;
|
||||||
use cosmic_settings_config::shortcuts::action::{Direction, FocusDirection};
|
use cosmic_settings_config::shortcuts::action::{Direction, FocusDirection};
|
||||||
use smithay::{
|
use smithay::{
|
||||||
|
backend::input::InputTime,
|
||||||
input::{Seat, pointer::MotionEvent},
|
input::{Seat, pointer::MotionEvent},
|
||||||
utils::{Point, Serial},
|
utils::{Point, Serial},
|
||||||
};
|
};
|
||||||
|
|
@ -29,6 +30,8 @@ use std::{os::unix::process::CommandExt, thread};
|
||||||
|
|
||||||
use super::gestures;
|
use super::gestures;
|
||||||
|
|
||||||
|
const MAX_ZOOM: f64 = 256.0;
|
||||||
|
|
||||||
fn propagate_by_default(action: &shortcuts::Action) -> bool {
|
fn propagate_by_default(action: &shortcuts::Action) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
action,
|
action,
|
||||||
|
|
@ -43,7 +46,7 @@ impl State {
|
||||||
backend_id: &InputBackendId,
|
backend_id: &InputBackendId,
|
||||||
seat: &Seat<State>,
|
seat: &Seat<State>,
|
||||||
serial: Serial,
|
serial: Serial,
|
||||||
time: u32,
|
time: InputTime,
|
||||||
pattern: shortcuts::Binding,
|
pattern: shortcuts::Binding,
|
||||||
direction: Option<Direction>,
|
direction: Option<Direction>,
|
||||||
) {
|
) {
|
||||||
|
|
@ -148,7 +151,7 @@ impl State {
|
||||||
backend_id: &InputBackendId,
|
backend_id: &InputBackendId,
|
||||||
seat: &Seat<State>,
|
seat: &Seat<State>,
|
||||||
serial: Serial,
|
serial: Serial,
|
||||||
time: u32,
|
time: InputTime,
|
||||||
pattern: shortcuts::Binding,
|
pattern: shortcuts::Binding,
|
||||||
direction: Option<Direction>,
|
direction: Option<Direction>,
|
||||||
propagate: bool,
|
propagate: bool,
|
||||||
|
|
@ -1116,7 +1119,14 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
if zoom_seat == *seat {
|
if zoom_seat == *seat {
|
||||||
let new_level = (current_level + change).max(1.0);
|
let factor = 1.0 + change.abs();
|
||||||
|
let new_level = if change < 0. {
|
||||||
|
current_level / factor
|
||||||
|
} else {
|
||||||
|
current_level * factor
|
||||||
|
}
|
||||||
|
.clamp(1.0, MAX_ZOOM);
|
||||||
|
let new_level = if new_level < 1.01 { 1.0 } else { new_level };
|
||||||
shell.trigger_zoom(
|
shell.trigger_zoom(
|
||||||
seat,
|
seat,
|
||||||
Some(&output),
|
Some(&output),
|
||||||
|
|
|
||||||
451
src/input/mod.rs
451
src/input/mod.rs
|
|
@ -1,7 +1,10 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::render::{ElementFilter, cursor::notify_cursor_activity},
|
backend::render::{
|
||||||
|
ElementFilter,
|
||||||
|
cursor::{PointerEventKind, notify_cursor_activity},
|
||||||
|
},
|
||||||
config::{
|
config::{
|
||||||
Action, Config, PrivateAction,
|
Action, Config, PrivateAction,
|
||||||
key_bindings::{
|
key_bindings::{
|
||||||
|
|
@ -9,14 +12,17 @@ use crate::{
|
||||||
cosmic_modifiers_from_smithay,
|
cosmic_modifiers_from_smithay,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
input::gestures::{GestureState, SwipeAction},
|
input::{
|
||||||
|
gestures::{GestureState, SwipeAction},
|
||||||
|
tablet_emu::PointerEmulationGrab,
|
||||||
|
},
|
||||||
shell::{
|
shell::{
|
||||||
SeatExt, Trigger,
|
SeatExt, Trigger,
|
||||||
focus::{
|
focus::{
|
||||||
Stage, render_input_order,
|
Stage, render_input_order,
|
||||||
target::{KeyboardFocusTarget, PointerFocusTarget},
|
target::{KeyboardFocusTarget, PointerFocusTarget},
|
||||||
},
|
},
|
||||||
grabs::{ReleaseMode, ResizeEdge},
|
grabs::{MenuGrab, MoveGrab, ReleaseMode, ResizeEdge, ResizeGrab},
|
||||||
layout::{
|
layout::{
|
||||||
floating::ResizeGrabMarker,
|
floating::ResizeGrabMarker,
|
||||||
tiling::{NodeDesc, SwapWindowGrab, TilingLayout},
|
tiling::{NodeDesc, SwapWindowGrab, TilingLayout},
|
||||||
|
|
@ -42,19 +48,21 @@ use smithay::{
|
||||||
backend::input::{
|
backend::input::{
|
||||||
AbsolutePositionEvent, Axis, AxisRelativeDirection, AxisSource, Device, DeviceCapability,
|
AbsolutePositionEvent, Axis, AxisRelativeDirection, AxisSource, Device, DeviceCapability,
|
||||||
GestureBeginEvent, GestureEndEvent, GesturePinchUpdateEvent as _,
|
GestureBeginEvent, GestureEndEvent, GesturePinchUpdateEvent as _,
|
||||||
GestureSwipeUpdateEvent as _, InputBackend, InputEvent, KeyState, PointerAxisEvent,
|
GestureSwipeUpdateEvent as _, InputBackend, InputEvent, InputTime, KeyState,
|
||||||
ProximityState, TabletToolButtonEvent, TabletToolEvent, TabletToolProximityEvent,
|
PointerAxisEvent, ProximityState, TabletToolButtonEvent, TabletToolEvent,
|
||||||
TabletToolTipEvent, TabletToolTipState, TouchEvent,
|
TabletToolProximityEvent, TabletToolTipEvent, TabletToolTipState, TouchEvent,
|
||||||
},
|
},
|
||||||
desktop::{PopupKeyboardGrab, WindowSurfaceType, utils::under_from_surface_tree},
|
desktop::{WindowSurfaceType, utils::under_from_surface_tree},
|
||||||
input::{
|
input::{
|
||||||
Seat,
|
Seat,
|
||||||
|
keyboard::KeyboardHandle,
|
||||||
keyboard::{FilterResult, KeyboardSource, KeysymHandle, ModifiersState},
|
keyboard::{FilterResult, KeyboardSource, KeysymHandle, ModifiersState},
|
||||||
pointer::{
|
pointer::{
|
||||||
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent,
|
AxisFrame, ButtonEvent as PointerButtonEvent, Focus, GestureHoldBeginEvent,
|
||||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent,
|
||||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent,
|
GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent,
|
||||||
PointerGrab, PointerHandle, RelativeMotionEvent,
|
GestureSwipeUpdateEvent, MotionEvent as PointerMotionEvent, PointerGrab, PointerHandle,
|
||||||
|
RelativeMotionEvent,
|
||||||
},
|
},
|
||||||
tablet::{TabletDescriptor, TabletSeatTrait, tool},
|
tablet::{TabletDescriptor, TabletSeatTrait, tool},
|
||||||
touch::{DownEvent, MotionEvent as TouchMotionEvent, UpEvent},
|
touch::{DownEvent, MotionEvent as TouchMotionEvent, UpEvent},
|
||||||
|
|
@ -87,6 +95,7 @@ use std::{
|
||||||
|
|
||||||
pub mod actions;
|
pub mod actions;
|
||||||
pub mod gestures;
|
pub mod gestures;
|
||||||
|
pub mod tablet_emu;
|
||||||
|
|
||||||
/// Identifies the input backend instance an event came from, used to disambiguate device ids
|
/// Identifies the input backend instance an event came from, used to disambiguate device ids
|
||||||
/// (which are only unique within a single backend instance, see
|
/// (which are only unique within a single backend instance, see
|
||||||
|
|
@ -214,7 +223,16 @@ impl State {
|
||||||
) where
|
) where
|
||||||
<B as InputBackend>::Device: 'static,
|
<B as InputBackend>::Device: 'static,
|
||||||
{
|
{
|
||||||
crate::wayland::handlers::output_power::set_all_surfaces_dpms_on(self);
|
// An input device going away is not user activity, e.g. a wireless keyboard
|
||||||
|
// dropping its link when it sleeps, so it must not power displays back on.
|
||||||
|
// Device additions still wake: after a system resume the re-enumerated
|
||||||
|
// devices may be the only input seen, and the wake resets idle anyway.
|
||||||
|
if !matches!(
|
||||||
|
&event,
|
||||||
|
InputEvent::DeviceRemoved { .. } | InputEvent::Special(_)
|
||||||
|
) {
|
||||||
|
crate::wayland::handlers::output_power::set_all_surfaces_dpms_on(self);
|
||||||
|
}
|
||||||
|
|
||||||
use smithay::backend::input::Event;
|
use smithay::backend::input::Event;
|
||||||
match event {
|
match event {
|
||||||
|
|
@ -271,7 +289,7 @@ impl State {
|
||||||
trace!(?keycode, ?state, "key");
|
trace!(?keycode, ?state, "key");
|
||||||
|
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
let time = Event::time_msec(&event);
|
let time = Event::time(&event);
|
||||||
let keyboard = seat.get_keyboard().unwrap();
|
let keyboard = seat.get_keyboard().unwrap();
|
||||||
let previous_modifiers = keyboard.modifier_state();
|
let previous_modifiers = keyboard.modifier_state();
|
||||||
if let Some((action, pattern)) = keyboard
|
if let Some((action, pattern)) = keyboard
|
||||||
|
|
@ -321,11 +339,31 @@ impl State {
|
||||||
keyboard.modifier_state().num_lock;
|
keyboard.modifier_state().num_lock;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A bare modifier press changes the modifier state; a real
|
||||||
|
// keystroke does not. Super+drag moves windows, so hiding on
|
||||||
|
// Super-down would take the cursor away exactly as the user
|
||||||
|
// reaches for it. Super+1 still counts as typing.
|
||||||
|
let bare_modifier = previous_modifiers != keyboard.modifier_state();
|
||||||
|
if self.common.config.cosmic_conf.cursor_hide.while_typing
|
||||||
|
&& state == KeyState::Pressed
|
||||||
|
&& !bare_modifier
|
||||||
|
{
|
||||||
|
crate::backend::render::cursor::hide_cursor_now(
|
||||||
|
self,
|
||||||
|
&seat,
|
||||||
|
crate::backend::render::cursor::HideReason::Typing,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Still refresh: this is how entering fullscreen by
|
||||||
|
// keyboard arms the fullscreen timeout.
|
||||||
|
crate::backend::render::cursor::refresh_idle_timer(self, &seat);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InputEvent::PointerMotion { event, .. } => {
|
InputEvent::PointerMotion { event, .. } => {
|
||||||
use smithay::backend::input::PointerMotionEvent;
|
use smithay::backend::input::PointerMotionEvent as _;
|
||||||
|
|
||||||
let shell = self.common.shell.write();
|
let shell = self.common.shell.write();
|
||||||
if let Some(seat) = shell
|
if let Some(seat) = shell
|
||||||
|
|
@ -334,9 +372,27 @@ impl State {
|
||||||
.cloned()
|
.cloned()
|
||||||
{
|
{
|
||||||
self.common.idle_notifier_state.notify_activity(&seat);
|
self.common.idle_notifier_state.notify_activity(&seat);
|
||||||
notify_cursor_activity(self, &seat);
|
notify_cursor_activity(self, &seat, PointerEventKind::Motion);
|
||||||
let current_output = seat.active_output();
|
let current_output = seat.active_output();
|
||||||
|
|
||||||
|
if self.common.config.cosmic_conf.cursor_shake_to_find
|
||||||
|
&& seat
|
||||||
|
.get_pointer()
|
||||||
|
.is_some_and(|pointer| !pointer.is_grabbed())
|
||||||
|
&& let Some(cursor_state) =
|
||||||
|
seat.user_data()
|
||||||
|
.get::<crate::backend::render::cursor::CursorState>()
|
||||||
|
{
|
||||||
|
let active = {
|
||||||
|
let mut cursor = cursor_state.lock().unwrap();
|
||||||
|
cursor.detect_shake(event.delta(), std::time::Instant::now());
|
||||||
|
cursor.is_magnifying()
|
||||||
|
};
|
||||||
|
if active {
|
||||||
|
self.backend.schedule_render(¤t_output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut position = seat.get_pointer().unwrap().current_location().as_global();
|
let mut position = seat.get_pointer().unwrap().current_location().as_global();
|
||||||
|
|
||||||
let under = State::surface_under(position, ¤t_output, &shell)
|
let under = State::surface_under(position, ¤t_output, &shell)
|
||||||
|
|
@ -382,7 +438,7 @@ impl State {
|
||||||
&RelativeMotionEvent {
|
&RelativeMotionEvent {
|
||||||
delta: event.delta(),
|
delta: event.delta(),
|
||||||
delta_unaccel: event.delta_unaccel(),
|
delta_unaccel: event.delta_unaccel(),
|
||||||
utime: event.time(),
|
time: event.time(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -620,10 +676,10 @@ impl State {
|
||||||
ptr.motion(
|
ptr.motion(
|
||||||
self,
|
self,
|
||||||
under,
|
under,
|
||||||
&MotionEvent {
|
&PointerMotionEvent {
|
||||||
location: position.as_logical(),
|
location: position.as_logical(),
|
||||||
serial,
|
serial,
|
||||||
time: event.time_msec(),
|
time: event.time(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
ptr.frame(self);
|
ptr.frame(self);
|
||||||
|
|
@ -690,7 +746,7 @@ impl State {
|
||||||
.cloned();
|
.cloned();
|
||||||
if let Some(seat) = maybe_seat {
|
if let Some(seat) = maybe_seat {
|
||||||
self.common.idle_notifier_state.notify_activity(&seat);
|
self.common.idle_notifier_state.notify_activity(&seat);
|
||||||
notify_cursor_activity(self, &seat);
|
notify_cursor_activity(self, &seat, PointerEventKind::Motion);
|
||||||
let (output, position) = if matches!(&backend_id, InputBackendId::Ei(_)) {
|
let (output, position) = if matches!(&backend_id, InputBackendId::Ei(_)) {
|
||||||
// EI absolute coordinates are in the compositor's *global*
|
// EI absolute coordinates are in the compositor's *global*
|
||||||
// logical space: each advertised region carries its output's
|
// logical space: each advertised region carries its output's
|
||||||
|
|
@ -735,23 +791,33 @@ impl State {
|
||||||
ptr.motion(
|
ptr.motion(
|
||||||
self,
|
self,
|
||||||
under,
|
under,
|
||||||
&MotionEvent {
|
&PointerMotionEvent {
|
||||||
location: position.as_logical(),
|
location: position.as_logical(),
|
||||||
serial,
|
serial,
|
||||||
time: event.time_msec(),
|
time: event.time(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
ptr.frame(self);
|
ptr.frame(self);
|
||||||
|
|
||||||
|
let mut shell = self.common.shell.write();
|
||||||
// Keep the seat's active output following the pointer. Click-to-
|
// Keep the seat's active output following the pointer. Click-to-
|
||||||
// focus (PointerButton) resolves its target via
|
// focus (PointerButton) resolves its target via
|
||||||
// `seat.active_output()`
|
// `seat.active_output()`
|
||||||
let previous_output = seat.active_output();
|
let previous_output = seat.active_output();
|
||||||
if previous_output != output {
|
if previous_output != output {
|
||||||
|
for session in cursor_sessions_for_output(&shell, &previous_output) {
|
||||||
|
session.set_cursor_pos(None);
|
||||||
|
}
|
||||||
seat.set_active_output(&output);
|
seat.set_active_output(&output);
|
||||||
}
|
}
|
||||||
|
|
||||||
let shell = self.common.shell.read();
|
shell.update_pointer_position(position.to_local(&output), &output);
|
||||||
|
shell.update_focal_point(
|
||||||
|
&seat,
|
||||||
|
position,
|
||||||
|
self.common.config.cosmic_conf.accessibility_zoom.view_moves,
|
||||||
|
);
|
||||||
|
|
||||||
update_output_image_copy_cursor_position(
|
update_output_image_copy_cursor_position(
|
||||||
&shell,
|
&shell,
|
||||||
&self.common.clock,
|
&self.common.clock,
|
||||||
|
|
@ -762,9 +828,8 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InputEvent::PointerButton { event, .. } => {
|
InputEvent::PointerButton { event, .. } => {
|
||||||
use smithay::backend::input::{ButtonState, PointerButtonEvent};
|
use smithay::backend::input::{ButtonState, PointerButtonEvent as _};
|
||||||
|
|
||||||
//
|
|
||||||
let Some(seat) = self
|
let Some(seat) = self
|
||||||
.common
|
.common
|
||||||
.shell
|
.shell
|
||||||
|
|
@ -776,7 +841,7 @@ impl State {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
self.common.idle_notifier_state.notify_activity(&seat);
|
self.common.idle_notifier_state.notify_activity(&seat);
|
||||||
notify_cursor_activity(self, &seat);
|
notify_cursor_activity(self, &seat, PointerEventKind::Other);
|
||||||
|
|
||||||
let current_focus = seat.get_keyboard().unwrap().current_focus();
|
let current_focus = seat.get_keyboard().unwrap().current_focus();
|
||||||
let shortcuts_inhibited = current_focus.as_ref().is_some_and(|f| {
|
let shortcuts_inhibited = current_focus.as_ref().is_some_and(|f| {
|
||||||
|
|
@ -838,7 +903,7 @@ impl State {
|
||||||
&& !shortcuts_inhibited
|
&& !shortcuts_inhibited
|
||||||
{
|
{
|
||||||
let seat_clone = seat.clone();
|
let seat_clone = seat.clone();
|
||||||
let mouse_button = PointerButtonEvent::button(&event);
|
let mouse_button = event.button();
|
||||||
|
|
||||||
let mut supress_button = || {
|
let mut supress_button = || {
|
||||||
// If the logo is held then the pointer event is
|
// If the logo is held then the pointer event is
|
||||||
|
|
@ -990,7 +1055,7 @@ impl State {
|
||||||
button,
|
button,
|
||||||
event.state(),
|
event.state(),
|
||||||
serial,
|
serial,
|
||||||
event.time_msec(),
|
event.time(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -998,16 +1063,16 @@ impl State {
|
||||||
if pass_event {
|
if pass_event {
|
||||||
ptr.button(
|
ptr.button(
|
||||||
self,
|
self,
|
||||||
&ButtonEvent {
|
&PointerButtonEvent {
|
||||||
button,
|
button,
|
||||||
state: event.state(),
|
state: event.state(),
|
||||||
serial,
|
serial,
|
||||||
time: event.time_msec(),
|
time: event.time(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
ptr.frame(self);
|
ptr.frame(self);
|
||||||
} else if event.state() == ButtonState::Released {
|
} else if event.state() == ButtonState::Released {
|
||||||
ptr.unset_grab(self, serial, event.time_msec())
|
ptr.unset_grab(self, serial, event.time())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InputEvent::PointerAxis { event, .. } => {
|
InputEvent::PointerAxis { event, .. } => {
|
||||||
|
|
@ -1027,7 +1092,7 @@ impl State {
|
||||||
.cloned();
|
.cloned();
|
||||||
if let Some(seat) = maybe_seat {
|
if let Some(seat) = maybe_seat {
|
||||||
self.common.idle_notifier_state.notify_activity(&seat);
|
self.common.idle_notifier_state.notify_activity(&seat);
|
||||||
notify_cursor_activity(self, &seat);
|
notify_cursor_activity(self, &seat, PointerEventKind::Other);
|
||||||
|
|
||||||
if self.source_modifiers(&backend_id, &seat).logo
|
if self.source_modifiers(&backend_id, &seat).logo
|
||||||
&& self
|
&& self
|
||||||
|
|
@ -1058,7 +1123,7 @@ impl State {
|
||||||
self.update_zoom(&seat, change, event.source() == AxisSource::Wheel);
|
self.update_zoom(&seat, change, event.source() == AxisSource::Wheel);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut frame = AxisFrame::new(event.time_msec()).source(event.source());
|
let mut frame = AxisFrame::new(event.time()).source(event.source());
|
||||||
let horizontal_amount = event
|
let horizontal_amount = event
|
||||||
.amount(Axis::Horizontal)
|
.amount(Axis::Horizontal)
|
||||||
.or_else(|| Some(event.amount_v120(Axis::Horizontal)? * 15.0 / 120.));
|
.or_else(|| Some(event.amount_v120(Axis::Horizontal)? * 15.0 / 120.));
|
||||||
|
|
@ -1127,7 +1192,7 @@ impl State {
|
||||||
self,
|
self,
|
||||||
&GestureSwipeBeginEvent {
|
&GestureSwipeBeginEvent {
|
||||||
serial,
|
serial,
|
||||||
time: event.time_msec(),
|
time: event.time(),
|
||||||
fingers: event.fingers(),
|
fingers: event.fingers(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -1148,7 +1213,7 @@ impl State {
|
||||||
if let Some(ref mut gesture_state) = self.common.gesture_state {
|
if let Some(ref mut gesture_state) = self.common.gesture_state {
|
||||||
let first_update = gesture_state.update(
|
let first_update = gesture_state.update(
|
||||||
event.delta(),
|
event.delta(),
|
||||||
Duration::from_millis(event.time_msec() as u64),
|
Duration::from_millis(event.time().millis() as u64),
|
||||||
);
|
);
|
||||||
// Decide on action if first update
|
// Decide on action if first update
|
||||||
if first_update {
|
if first_update {
|
||||||
|
|
@ -1224,7 +1289,7 @@ impl State {
|
||||||
pointer.gesture_swipe_update(
|
pointer.gesture_swipe_update(
|
||||||
self,
|
self,
|
||||||
&GestureSwipeUpdateEvent {
|
&GestureSwipeUpdateEvent {
|
||||||
time: event.time_msec(),
|
time: event.time(),
|
||||||
delta: event.delta(),
|
delta: event.delta(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -1273,7 +1338,7 @@ impl State {
|
||||||
self,
|
self,
|
||||||
&GestureSwipeEndEvent {
|
&GestureSwipeEndEvent {
|
||||||
serial,
|
serial,
|
||||||
time: event.time_msec(),
|
time: event.time(),
|
||||||
cancelled: event.cancelled(),
|
cancelled: event.cancelled(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -1296,7 +1361,7 @@ impl State {
|
||||||
self,
|
self,
|
||||||
&GesturePinchBeginEvent {
|
&GesturePinchBeginEvent {
|
||||||
serial,
|
serial,
|
||||||
time: event.time_msec(),
|
time: event.time(),
|
||||||
fingers: event.fingers(),
|
fingers: event.fingers(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -1316,7 +1381,7 @@ impl State {
|
||||||
pointer.gesture_pinch_update(
|
pointer.gesture_pinch_update(
|
||||||
self,
|
self,
|
||||||
&GesturePinchUpdateEvent {
|
&GesturePinchUpdateEvent {
|
||||||
time: event.time_msec(),
|
time: event.time(),
|
||||||
delta: event.delta(),
|
delta: event.delta(),
|
||||||
scale: event.scale(),
|
scale: event.scale(),
|
||||||
rotation: event.rotation(),
|
rotation: event.rotation(),
|
||||||
|
|
@ -1340,7 +1405,7 @@ impl State {
|
||||||
self,
|
self,
|
||||||
&GesturePinchEndEvent {
|
&GesturePinchEndEvent {
|
||||||
serial,
|
serial,
|
||||||
time: event.time_msec(),
|
time: event.time(),
|
||||||
cancelled: event.cancelled(),
|
cancelled: event.cancelled(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -1362,7 +1427,7 @@ impl State {
|
||||||
self,
|
self,
|
||||||
&GestureHoldBeginEvent {
|
&GestureHoldBeginEvent {
|
||||||
serial,
|
serial,
|
||||||
time: event.time_msec(),
|
time: event.time(),
|
||||||
fingers: event.fingers(),
|
fingers: event.fingers(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -1384,7 +1449,7 @@ impl State {
|
||||||
self,
|
self,
|
||||||
&GestureHoldEndEvent {
|
&GestureHoldEndEvent {
|
||||||
serial,
|
serial,
|
||||||
time: event.time_msec(),
|
time: event.time(),
|
||||||
cancelled: event.cancelled(),
|
cancelled: event.cancelled(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -1429,11 +1494,18 @@ impl State {
|
||||||
};
|
};
|
||||||
let under = State::surface_under(position, &output, &shell)
|
let under = State::surface_under(position, &output, &shell)
|
||||||
.map(|(target, pos)| (target, pos.as_logical()));
|
.map(|(target, pos)| (target, pos.as_logical()));
|
||||||
|
let focus_target = State::element_under(position, &output, &shell, &seat);
|
||||||
|
|
||||||
std::mem::drop(shell);
|
std::mem::drop(shell);
|
||||||
|
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
let touch = seat.get_touch().unwrap();
|
let touch = seat.get_touch().unwrap();
|
||||||
|
// change the keyboard focus unless the touch is grabbed, like pointer buttons
|
||||||
|
if !touch.is_grabbed()
|
||||||
|
&& let Some(target) = focus_target.as_ref()
|
||||||
|
{
|
||||||
|
Shell::set_focus(self, Some(target), &seat, Some(serial), false);
|
||||||
|
}
|
||||||
touch.down(
|
touch.down(
|
||||||
self,
|
self,
|
||||||
under,
|
under,
|
||||||
|
|
@ -1441,9 +1513,17 @@ impl State {
|
||||||
slot: event.slot(),
|
slot: event.slot(),
|
||||||
location: position.as_logical(),
|
location: position.as_logical(),
|
||||||
serial,
|
serial,
|
||||||
time: event.time_msec(),
|
time: event.time(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if self.common.config.cosmic_conf.cursor_hide.after_touch {
|
||||||
|
crate::backend::render::cursor::hide_cursor_now(
|
||||||
|
self,
|
||||||
|
&seat,
|
||||||
|
crate::backend::render::cursor::HideReason::Touch,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InputEvent::TouchMotion { event, .. } => {
|
InputEvent::TouchMotion { event, .. } => {
|
||||||
|
|
@ -1490,7 +1570,7 @@ impl State {
|
||||||
&TouchMotionEvent {
|
&TouchMotionEvent {
|
||||||
slot: event.slot(),
|
slot: event.slot(),
|
||||||
location: position.as_logical(),
|
location: position.as_logical(),
|
||||||
time: event.time_msec(),
|
time: event.time(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1516,7 +1596,7 @@ impl State {
|
||||||
self,
|
self,
|
||||||
&UpEvent {
|
&UpEvent {
|
||||||
slot: event.slot(),
|
slot: event.slot(),
|
||||||
time: event.time_msec(),
|
time: event.time(),
|
||||||
serial,
|
serial,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
@ -1559,7 +1639,7 @@ impl State {
|
||||||
.cloned()
|
.cloned()
|
||||||
{
|
{
|
||||||
self.common.idle_notifier_state.notify_activity(&seat);
|
self.common.idle_notifier_state.notify_activity(&seat);
|
||||||
notify_cursor_activity(self, &seat);
|
notify_cursor_activity(self, &seat, PointerEventKind::Motion);
|
||||||
let Some(output) =
|
let Some(output) =
|
||||||
mapped_output_for_device(&self.common.config, &shell, &event.device())
|
mapped_output_for_device(&self.common.config, &shell, &event.device())
|
||||||
.cloned()
|
.cloned()
|
||||||
|
|
@ -1567,6 +1647,7 @@ impl State {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let current_output = seat.active_output();
|
||||||
let position =
|
let position =
|
||||||
transform_output_mapped_position(&output, &event, shell.zoom_state());
|
transform_output_mapped_position(&output, &event, shell.zoom_state());
|
||||||
let under = State::surface_under(position, &output, &shell)
|
let under = State::surface_under(position, &output, &shell)
|
||||||
|
|
@ -1575,21 +1656,33 @@ impl State {
|
||||||
std::mem::drop(shell);
|
std::mem::drop(shell);
|
||||||
|
|
||||||
let pointer = seat.get_pointer().unwrap();
|
let pointer = seat.get_pointer().unwrap();
|
||||||
pointer.motion(
|
pointer.set_location(position.as_logical());
|
||||||
self,
|
|
||||||
under.clone(),
|
|
||||||
&MotionEvent {
|
|
||||||
location: position.as_logical(),
|
|
||||||
serial: SERIAL_COUNTER.next_serial(),
|
|
||||||
time: self.common.clock.now().as_millis(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let tablet_seat = seat.tablet_seat();
|
let tablet_seat = seat.tablet_seat();
|
||||||
|
|
||||||
let tool = tablet_seat.get_tool(&event.tool());
|
let tool = tablet_seat.get_tool(&event.tool());
|
||||||
|
|
||||||
if let Some(tool) = tool {
|
if let Some(tool) = tool {
|
||||||
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
|
if !tool.is_grabbed()
|
||||||
|
&& under
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|(target, _)| !target.supports_tool(&tool))
|
||||||
|
{
|
||||||
|
let start_data = tool::GrabStartData {
|
||||||
|
focus: under.clone(),
|
||||||
|
trigger: tool::GrabTrigger::Proximity,
|
||||||
|
location: position.as_logical(),
|
||||||
|
};
|
||||||
|
tool.set_grab(
|
||||||
|
self,
|
||||||
|
PointerEmulationGrab::new(start_data, seat.clone()),
|
||||||
|
event.time(),
|
||||||
|
serial,
|
||||||
|
Focus::Keep,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let frame = tool::AxisFrame {
|
let frame = tool::AxisFrame {
|
||||||
pressure: event.pressure_has_changed().then(|| event.pressure()),
|
pressure: event.pressure_has_changed().then(|| event.pressure()),
|
||||||
distance: event.distance_has_changed().then(|| event.distance()),
|
distance: event.distance_has_changed().then(|| event.distance()),
|
||||||
|
|
@ -1607,17 +1700,39 @@ impl State {
|
||||||
|
|
||||||
tool.motion(
|
tool.motion(
|
||||||
self,
|
self,
|
||||||
under
|
under,
|
||||||
.and_then(|(f, loc)| f.wl_surface().map(|s| (s.into_owned(), loc))),
|
|
||||||
&tool::MotionEvent {
|
&tool::MotionEvent {
|
||||||
location: position.as_logical(),
|
location: position.as_logical(),
|
||||||
serial: SERIAL_COUNTER.next_serial(),
|
serial,
|
||||||
time: event.time_msec(),
|
time: event.time(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
tool.frame(self, event.time_msec());
|
tool.frame(self, event.time());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut shell = self.common.shell.write();
|
||||||
|
shell.update_pointer_position(position.to_local(&output), &output);
|
||||||
|
shell.update_focal_point(
|
||||||
|
&seat,
|
||||||
|
position,
|
||||||
|
self.common.config.cosmic_conf.accessibility_zoom.view_moves,
|
||||||
|
);
|
||||||
|
|
||||||
|
if output != current_output {
|
||||||
|
for session in cursor_sessions_for_output(&shell, ¤t_output) {
|
||||||
|
session.set_cursor_pos(None);
|
||||||
|
}
|
||||||
|
seat.set_active_output(&output);
|
||||||
|
}
|
||||||
|
|
||||||
|
update_output_image_copy_cursor_position(
|
||||||
|
&shell,
|
||||||
|
&self.common.clock,
|
||||||
|
&output,
|
||||||
|
&seat,
|
||||||
|
position,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InputEvent::TabletToolProximity { event, .. } => {
|
InputEvent::TabletToolProximity { event, .. } => {
|
||||||
|
|
@ -1628,7 +1743,7 @@ impl State {
|
||||||
.cloned()
|
.cloned()
|
||||||
{
|
{
|
||||||
self.common.idle_notifier_state.notify_activity(&seat);
|
self.common.idle_notifier_state.notify_activity(&seat);
|
||||||
notify_cursor_activity(self, &seat);
|
notify_cursor_activity(self, &seat, PointerEventKind::Motion);
|
||||||
let Some(output) =
|
let Some(output) =
|
||||||
mapped_output_for_device(&self.common.config, &shell, &event.device())
|
mapped_output_for_device(&self.common.config, &shell, &event.device())
|
||||||
.cloned()
|
.cloned()
|
||||||
|
|
@ -1636,6 +1751,7 @@ impl State {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let current_output = seat.active_output();
|
||||||
let position =
|
let position =
|
||||||
transform_output_mapped_position(&output, &event, shell.zoom_state());
|
transform_output_mapped_position(&output, &event, shell.zoom_state());
|
||||||
let under = State::surface_under(position, &output, &shell)
|
let under = State::surface_under(position, &output, &shell)
|
||||||
|
|
@ -1644,15 +1760,7 @@ impl State {
|
||||||
std::mem::drop(shell);
|
std::mem::drop(shell);
|
||||||
|
|
||||||
let pointer = seat.get_pointer().unwrap();
|
let pointer = seat.get_pointer().unwrap();
|
||||||
pointer.motion(
|
pointer.set_location(position.as_logical());
|
||||||
self,
|
|
||||||
under.clone(),
|
|
||||||
&MotionEvent {
|
|
||||||
location: position.as_logical(),
|
|
||||||
serial: SERIAL_COUNTER.next_serial(),
|
|
||||||
time: self.common.clock.now().as_millis(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
let tablet_seat = seat.tablet_seat();
|
let tablet_seat = seat.tablet_seat();
|
||||||
|
|
||||||
|
|
@ -1665,6 +1773,25 @@ impl State {
|
||||||
if let Some(tablet) = tablet {
|
if let Some(tablet) = tablet {
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
|
|
||||||
|
if !tool.is_grabbed()
|
||||||
|
&& under
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|(target, _)| !target.supports_tool(&tool))
|
||||||
|
{
|
||||||
|
let start_data = tool::GrabStartData {
|
||||||
|
focus: under.clone(),
|
||||||
|
trigger: tool::GrabTrigger::Proximity,
|
||||||
|
location: position.as_logical(),
|
||||||
|
};
|
||||||
|
tool.set_grab(
|
||||||
|
self,
|
||||||
|
PointerEmulationGrab::new(start_data, seat.clone()),
|
||||||
|
event.time(),
|
||||||
|
serial,
|
||||||
|
Focus::Keep,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let frame = tool::AxisFrame {
|
let frame = tool::AxisFrame {
|
||||||
pressure: event.pressure_has_changed().then(|| event.pressure()),
|
pressure: event.pressure_has_changed().then(|| event.pressure()),
|
||||||
distance: event.distance_has_changed().then(|| event.distance()),
|
distance: event.distance_has_changed().then(|| event.distance()),
|
||||||
|
|
@ -1680,9 +1807,6 @@ impl State {
|
||||||
|
|
||||||
match event.state() {
|
match event.state() {
|
||||||
ProximityState::In => {
|
ProximityState::In => {
|
||||||
let under = under.and_then(|(f, loc)| {
|
|
||||||
f.wl_surface().map(|s| (s.into_owned(), loc))
|
|
||||||
});
|
|
||||||
tool.proximity_in(
|
tool.proximity_in(
|
||||||
self,
|
self,
|
||||||
under,
|
under,
|
||||||
|
|
@ -1690,25 +1814,75 @@ impl State {
|
||||||
&tool::ProximityInEvent {
|
&tool::ProximityInEvent {
|
||||||
location: position.as_logical(),
|
location: position.as_logical(),
|
||||||
axis: Some(frame),
|
axis: Some(frame),
|
||||||
serial: SERIAL_COUNTER.next_serial(),
|
serial,
|
||||||
time: event.time_msec(),
|
time: event.time(),
|
||||||
},
|
},
|
||||||
)
|
);
|
||||||
|
}
|
||||||
|
ProximityState::Out => {
|
||||||
|
tool.proximity_out(
|
||||||
|
self,
|
||||||
|
&tool::ProximityOutEvent {
|
||||||
|
serial,
|
||||||
|
time: event.time(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if let Some(pointer) = seat.get_pointer() {
|
||||||
|
pointer.motion(
|
||||||
|
self,
|
||||||
|
None,
|
||||||
|
&PointerMotionEvent {
|
||||||
|
location: position.as_logical(),
|
||||||
|
serial,
|
||||||
|
time: event.time(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ProximityState::Out => tool.proximity_out(
|
|
||||||
self,
|
|
||||||
&tool::ProximityOutEvent {
|
|
||||||
serial,
|
|
||||||
time: event.time_msec(),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tool.frame(self, event.time_msec());
|
tool.frame(self, event.time());
|
||||||
|
}
|
||||||
|
|
||||||
|
if event.state() == ProximityState::In {
|
||||||
|
let mut shell = self.common.shell.write();
|
||||||
|
shell.update_pointer_position(position.to_local(&output), &output);
|
||||||
|
shell.update_focal_point(
|
||||||
|
&seat,
|
||||||
|
position,
|
||||||
|
self.common.config.cosmic_conf.accessibility_zoom.view_moves,
|
||||||
|
);
|
||||||
|
|
||||||
|
if output != current_output {
|
||||||
|
for session in cursor_sessions_for_output(&shell, ¤t_output) {
|
||||||
|
session.set_cursor_pos(None);
|
||||||
|
}
|
||||||
|
seat.set_active_output(&output);
|
||||||
|
}
|
||||||
|
|
||||||
|
update_output_image_copy_cursor_position(
|
||||||
|
&shell,
|
||||||
|
&self.common.clock,
|
||||||
|
&output,
|
||||||
|
&seat,
|
||||||
|
position,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
InputEvent::TabletToolTip { event, .. } => {
|
InputEvent::TabletToolTip { event, .. } => {
|
||||||
|
{
|
||||||
|
let mut shell = self.common.shell.write();
|
||||||
|
if let Some(Trigger::Tool(desc, trigger)) =
|
||||||
|
shell.overview_mode().0.active_trigger()
|
||||||
|
&& event.tool() == *desc
|
||||||
|
&& matches!(*trigger, tool::GrabTrigger::Tip)
|
||||||
|
&& event.tip_state() == TabletToolTipState::Up
|
||||||
|
{
|
||||||
|
shell.set_overview_mode(None, self.common.event_loop_handle.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let maybe_seat = self
|
let maybe_seat = self
|
||||||
.common
|
.common
|
||||||
.shell
|
.shell
|
||||||
|
|
@ -1718,7 +1892,22 @@ impl State {
|
||||||
.cloned();
|
.cloned();
|
||||||
if let Some(seat) = maybe_seat {
|
if let Some(seat) = maybe_seat {
|
||||||
self.common.idle_notifier_state.notify_activity(&seat);
|
self.common.idle_notifier_state.notify_activity(&seat);
|
||||||
notify_cursor_activity(self, &seat);
|
notify_cursor_activity(self, &seat, PointerEventKind::Other);
|
||||||
|
|
||||||
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
|
let output = seat.active_output();
|
||||||
|
let shell = self.common.shell.write();
|
||||||
|
let position =
|
||||||
|
transform_output_mapped_position(&output, &event, shell.zoom_state());
|
||||||
|
let under = State::element_under(position, &output, &shell, &seat);
|
||||||
|
drop(shell);
|
||||||
|
|
||||||
|
if event.tip_state() == TabletToolTipState::Down
|
||||||
|
&& let Some(target) = under.as_ref()
|
||||||
|
{
|
||||||
|
Shell::set_focus(self, Some(target), &seat, Some(serial), false);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(tool) = seat.tablet_seat().get_tool(&event.tool()) {
|
if let Some(tool) = seat.tablet_seat().get_tool(&event.tool()) {
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
match event.tip_state() {
|
match event.tip_state() {
|
||||||
|
|
@ -1727,7 +1916,7 @@ impl State {
|
||||||
self,
|
self,
|
||||||
&tool::DownEvent {
|
&tool::DownEvent {
|
||||||
serial,
|
serial,
|
||||||
time: event.time_msec(),
|
time: event.time(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -1736,13 +1925,13 @@ impl State {
|
||||||
self,
|
self,
|
||||||
&tool::UpEvent {
|
&tool::UpEvent {
|
||||||
serial,
|
serial,
|
||||||
time: event.time_msec(),
|
time: event.time(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tool.frame(self, event.time_msec());
|
tool.frame(self, event.time());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1756,7 +1945,7 @@ impl State {
|
||||||
.cloned();
|
.cloned();
|
||||||
if let Some(seat) = maybe_seat {
|
if let Some(seat) = maybe_seat {
|
||||||
self.common.idle_notifier_state.notify_activity(&seat);
|
self.common.idle_notifier_state.notify_activity(&seat);
|
||||||
notify_cursor_activity(self, &seat);
|
notify_cursor_activity(self, &seat, PointerEventKind::Other);
|
||||||
if let Some(tool) = seat.tablet_seat().get_tool(&event.tool()) {
|
if let Some(tool) = seat.tablet_seat().get_tool(&event.tool()) {
|
||||||
tool.button(
|
tool.button(
|
||||||
self,
|
self,
|
||||||
|
|
@ -1764,11 +1953,11 @@ impl State {
|
||||||
button: event.button(),
|
button: event.button(),
|
||||||
state: event.button_state(),
|
state: event.button_state(),
|
||||||
serial: SERIAL_COUNTER.next_serial(),
|
serial: SERIAL_COUNTER.next_serial(),
|
||||||
time: event.time_msec(),
|
time: event.time(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
tool.frame(self, event.time_msec());
|
tool.frame(self, event.time());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1871,7 +2060,7 @@ impl State {
|
||||||
let Some(pointer) = seat.get_pointer() else {
|
let Some(pointer) = seat.get_pointer() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let time = self.common.clock.now().as_millis();
|
let time = InputTime::now();
|
||||||
for button in buttons {
|
for button in buttons {
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
pointer.button(
|
pointer.button(
|
||||||
|
|
@ -1889,13 +2078,7 @@ impl State {
|
||||||
|
|
||||||
/// Mirror the seat's current modifier state to every libei sender with a keyboard via
|
/// Mirror the seat's current modifier state to every libei sender with a keyboard via
|
||||||
/// `ei_keyboard.modifiers`
|
/// `ei_keyboard.modifiers`
|
||||||
pub(crate) fn broadcast_ei_keyboard_modifiers(&self, seat: &Seat<State>) {
|
pub(crate) fn broadcast_ei_keyboard_modifiers(&self, keyboard: &KeyboardHandle<State>) {
|
||||||
if self.common.ei_seats.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let Some(keyboard) = seat.get_keyboard() else {
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
let s = keyboard.modifier_state().serialized;
|
let s = keyboard.modifier_state().serialized;
|
||||||
for ei_seat in self.common.ei_seats.values() {
|
for ei_seat in self.common.ei_seats.values() {
|
||||||
ei_seat.keyboard_modifiers(s.depressed, s.locked, s.latched, s.layout_effective);
|
ei_seat.keyboard_modifiers(s.depressed, s.locked, s.latched, s.layout_effective);
|
||||||
|
|
@ -1933,14 +2116,14 @@ impl State {
|
||||||
modifiers: &ModifiersState,
|
modifiers: &ModifiersState,
|
||||||
handle: KeysymHandle<'_>,
|
handle: KeysymHandle<'_>,
|
||||||
serial: Serial,
|
serial: Serial,
|
||||||
time: u32,
|
time: InputTime,
|
||||||
keycode: Keycode,
|
keycode: Keycode,
|
||||||
key_state: KeyState,
|
key_state: KeyState,
|
||||||
previous_modifiers: ModifiersState,
|
previous_modifiers: ModifiersState,
|
||||||
) -> FilterResult<Option<(Action, shortcuts::Binding)>> {
|
) -> FilterResult<Option<(Action, shortcuts::Binding)>> {
|
||||||
if previous_modifiers != *modifiers {
|
if previous_modifiers != *modifiers {
|
||||||
seat.set_last_modifier_change(backend_id, serial);
|
seat.set_last_modifier_change(backend_id, serial);
|
||||||
self.broadcast_ei_keyboard_modifiers(seat);
|
self.broadcast_ei_keyboard_modifiers(&seat.get_keyboard().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
let current_focus = seat.get_keyboard().unwrap().current_focus();
|
let current_focus = seat.get_keyboard().unwrap().current_focus();
|
||||||
|
|
@ -1960,6 +2143,21 @@ impl State {
|
||||||
backend_id, seat, modifiers, handle, serial, keycode, key_state, time,
|
backend_id, seat, modifiers, handle, serial, keycode, key_state, time,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// A modifier-only binding fires on the modifier's release, but its press was already
|
||||||
|
// forwarded. Forward the release too.
|
||||||
|
if key_state == KeyState::Released
|
||||||
|
&& matches!(&result, FilterResult::Intercept(Some((_, binding))) if binding.key.is_none())
|
||||||
|
{
|
||||||
|
seat.get_keyboard().unwrap().input_forward(
|
||||||
|
self,
|
||||||
|
keycode,
|
||||||
|
key_state,
|
||||||
|
serial,
|
||||||
|
time,
|
||||||
|
previous_modifiers != *modifiers,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (matches!(result, FilterResult::Forward)
|
if (matches!(result, FilterResult::Forward)
|
||||||
&& !seat.get_keyboard().unwrap().is_grabbed()
|
&& !seat.get_keyboard().unwrap().is_grabbed()
|
||||||
&& !shortcuts_inhibited
|
&& !shortcuts_inhibited
|
||||||
|
|
@ -1993,7 +2191,7 @@ impl State {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
let time = self.common.clock.now().as_millis();
|
let time = InputTime::now();
|
||||||
let previous_modifiers = keyboard.modifier_state();
|
let previous_modifiers = keyboard.modifier_state();
|
||||||
let result = keyboard
|
let result = keyboard
|
||||||
.input_from_source(
|
.input_from_source(
|
||||||
|
|
@ -2142,7 +2340,7 @@ impl State {
|
||||||
serial: Serial,
|
serial: Serial,
|
||||||
keycode: Keycode,
|
keycode: Keycode,
|
||||||
key_state: KeyState,
|
key_state: KeyState,
|
||||||
time: u32,
|
time: InputTime,
|
||||||
) -> FilterResult<Option<(Action, shortcuts::Binding)>> {
|
) -> FilterResult<Option<(Action, shortcuts::Binding)>> {
|
||||||
// Pre-compute for layout-agnostic shortcut matching
|
// Pre-compute for layout-agnostic shortcut matching
|
||||||
let raw_syms = handle.raw_syms();
|
let raw_syms = handle.raw_syms();
|
||||||
|
|
@ -2155,17 +2353,15 @@ impl State {
|
||||||
|
|
||||||
let keyboard = seat.get_keyboard().unwrap();
|
let keyboard = seat.get_keyboard().unwrap();
|
||||||
let pointer = seat.get_pointer().unwrap();
|
let pointer = seat.get_pointer().unwrap();
|
||||||
// We're only interested in filtering keyboard grabs if we initiated them.
|
// Escape only cancels grabs the compositor started for its own interactions
|
||||||
// The easiest way to check that is to check the type of the grab.
|
// For keyboard: SwapWindowGrab
|
||||||
let keyboard_grabbed = keyboard.with_grab(|_serial, grab| {
|
// For pointer: MoveGrab, ResizeGrab, and MenuGrab
|
||||||
grab.is::<SwapWindowGrab>() || grab.is::<PopupKeyboardGrab<State>>()
|
let keyboard_grabbed =
|
||||||
|
keyboard.with_grab(|_serial, grab| grab.is::<SwapWindowGrab>()) == Some(true);
|
||||||
|
let pointer_grabbed = pointer.with_grab(|_serial, grab| {
|
||||||
|
grab.is::<MoveGrab>() || grab.is::<ResizeGrab>() || grab.is::<MenuGrab>()
|
||||||
}) == Some(true);
|
}) == Some(true);
|
||||||
// A virtual-keyboard key can arrive while the seat's pointer is grabbed by that
|
let is_grabbed = keyboard_grabbed || pointer_grabbed;
|
||||||
// same on-screen keyboard's own button press (the implicit grab from clicking an OSK
|
|
||||||
// key). That pointer grab must not capture the injected key, otherwise e.g.
|
|
||||||
// pressing esc on a virtual keyboard gets swallowed here
|
|
||||||
let from_vk = matches!(backend_id, InputBackendId::VirtualKeyboard);
|
|
||||||
let is_grabbed = keyboard_grabbed || (pointer.is_grabbed() && !from_vk);
|
|
||||||
|
|
||||||
let current_focus = keyboard.current_focus();
|
let current_focus = keyboard.current_focus();
|
||||||
//this should fall back to active output since there may not be a focused output
|
//this should fall back to active output since there may not be a focused output
|
||||||
|
|
@ -2307,7 +2503,9 @@ impl State {
|
||||||
&backend_id_clone,
|
&backend_id_clone,
|
||||||
&seat_clone,
|
&seat_clone,
|
||||||
serial,
|
serial,
|
||||||
time.overflowing_add(duration as u32).0,
|
InputTime::from_millis(
|
||||||
|
time.millis().overflowing_add(duration as u32).0,
|
||||||
|
),
|
||||||
key_pattern_clone.clone(),
|
key_pattern_clone.clone(),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
@ -2359,7 +2557,7 @@ impl State {
|
||||||
self.common.event_loop_handle.insert_idle(move |state| {
|
self.common.event_loop_handle.insert_idle(move |state| {
|
||||||
if let Some(keyboard) = seat.get_keyboard() {
|
if let Some(keyboard) = seat.get_keyboard() {
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
let time = state.common.clock.now().as_millis();
|
let time = InputTime::now();
|
||||||
keyboard.input(
|
keyboard.input(
|
||||||
state,
|
state,
|
||||||
key_code,
|
key_code,
|
||||||
|
|
@ -2885,6 +3083,7 @@ impl State {
|
||||||
surface: &WlSurface,
|
surface: &WlSurface,
|
||||||
pointer: &PointerHandle<Self>,
|
pointer: &PointerHandle<Self>,
|
||||||
mut location: Point<f64, Logical>,
|
mut location: Point<f64, Logical>,
|
||||||
|
constraint: Option<&PointerConstraint>,
|
||||||
) {
|
) {
|
||||||
let Some(client) = surface.client() else {
|
let Some(client) = surface.client() else {
|
||||||
return;
|
return;
|
||||||
|
|
@ -2909,15 +3108,13 @@ impl State {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
with_pointer_constraint(surface, pointer, |constraint| {
|
if let Some(constraint) = constraint
|
||||||
if let Some(constraint) = constraint
|
&& let Some(region) = constraint.region()
|
||||||
&& let Some(region) = constraint.region()
|
{
|
||||||
{
|
let point_in_surface = (p - surface_offset.to_f64()).to_i32_floor();
|
||||||
let point_in_surface = (p - surface_offset.to_f64()).to_i32_floor();
|
return region.contains(point_in_surface);
|
||||||
return region.contains(point_in_surface);
|
}
|
||||||
}
|
true
|
||||||
true
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let workspace_origin = output.geometry().loc.to_f64();
|
let workspace_origin = output.geometry().loc.to_f64();
|
||||||
|
|
@ -2941,23 +3138,23 @@ impl State {
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
let under = State::surface_under(point, &output, &self.common.shell.write())
|
let under = State::surface_under(point, &output, &self.common.shell.write())
|
||||||
.map(|(target, pos)| (target, pos.as_logical()));
|
.map(|(target, pos)| (target, pos.as_logical()));
|
||||||
let time = self.common.clock.now();
|
let time = InputTime::now();
|
||||||
pointer.relative_motion(
|
pointer.relative_motion(
|
||||||
self,
|
self,
|
||||||
under.clone(),
|
under.clone(),
|
||||||
&RelativeMotionEvent {
|
&RelativeMotionEvent {
|
||||||
delta: (0., 0.).into(),
|
delta: (0., 0.).into(),
|
||||||
delta_unaccel: (0., 0.).into(),
|
delta_unaccel: (0., 0.).into(),
|
||||||
utime: time.as_micros(),
|
time,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
pointer.motion(
|
pointer.motion(
|
||||||
self,
|
self,
|
||||||
under,
|
under,
|
||||||
&MotionEvent {
|
&PointerMotionEvent {
|
||||||
location: point.as_logical(),
|
location: point.as_logical(),
|
||||||
serial,
|
serial,
|
||||||
time: time.as_millis(),
|
time,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
pointer.frame(self);
|
pointer.frame(self);
|
||||||
|
|
|
||||||
256
src/input/tablet_emu.rs
Normal file
256
src/input/tablet_emu.rs
Normal file
|
|
@ -0,0 +1,256 @@
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
//! Pointer Emulating TabletTool grab.
|
||||||
|
|
||||||
|
use crate::{shell::focus::target::PointerFocusTarget, state::State};
|
||||||
|
use smithay::{
|
||||||
|
backend::input::{ButtonState, InputTime, MouseButton},
|
||||||
|
input::{
|
||||||
|
Seat, pointer,
|
||||||
|
tablet::{
|
||||||
|
self,
|
||||||
|
tool::{
|
||||||
|
AxisFrame, ButtonEvent, DownEvent, GrabStartData, ProximityInEvent,
|
||||||
|
ProximityOutEvent, TabletToolGrab, TabletToolInnerHandle, UpEvent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
utils::{Logical, Point, SERIAL_COUNTER},
|
||||||
|
};
|
||||||
|
|
||||||
|
// from https://gitlab.freedesktop.org/libinput/libinput/-/blob/main/include/linux/linux/input-event-codes.h
|
||||||
|
const BTN_STYLUS: u32 = 0x14b;
|
||||||
|
const BTN_STYLUS_2: u32 = 0x14c;
|
||||||
|
const BTN_LEFT: u32 = 0x110;
|
||||||
|
const BTN_RIGHT: u32 = 0x111;
|
||||||
|
const BTN_MIDDLE: u32 = 0x112;
|
||||||
|
const BTN_FORWARD: u32 = 0x115;
|
||||||
|
const BTN_BACK: u32 = 0x116;
|
||||||
|
|
||||||
|
pub struct PointerEmulationGrab {
|
||||||
|
start_data: GrabStartData<State>,
|
||||||
|
seat: Seat<State>,
|
||||||
|
|
||||||
|
above_source: bool,
|
||||||
|
tip_down: bool,
|
||||||
|
button_down: Vec<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PointerEmulationGrab {
|
||||||
|
pub fn new(start_data: GrabStartData<State>, seat: Seat<State>) -> Self {
|
||||||
|
Self {
|
||||||
|
start_data,
|
||||||
|
seat,
|
||||||
|
|
||||||
|
above_source: true,
|
||||||
|
tip_down: false,
|
||||||
|
button_down: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button_to_mouse(&self, button: u32) -> Option<MouseButton> {
|
||||||
|
// FIXME: This should be configurable.
|
||||||
|
if button == BTN_STYLUS {
|
||||||
|
Some(MouseButton::Right)
|
||||||
|
} else if button == BTN_STYLUS_2 {
|
||||||
|
Some(MouseButton::Middle)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pointer_button(&self, data: &mut State, button: MouseButton, state: ButtonState) {
|
||||||
|
if let Some(pointer) = self.seat.get_pointer() {
|
||||||
|
let button = match button {
|
||||||
|
MouseButton::Left => BTN_LEFT,
|
||||||
|
MouseButton::Right => BTN_RIGHT,
|
||||||
|
MouseButton::Middle => BTN_MIDDLE,
|
||||||
|
MouseButton::Back => BTN_BACK,
|
||||||
|
MouseButton::Forward => BTN_FORWARD,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
pointer.button(
|
||||||
|
data,
|
||||||
|
&pointer::ButtonEvent {
|
||||||
|
serial: SERIAL_COUNTER.next_serial(),
|
||||||
|
button,
|
||||||
|
state,
|
||||||
|
time: InputTime::now(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
pointer.frame(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Type = tablet::tool::MotionEvent;
|
||||||
|
|
||||||
|
impl TabletToolGrab<State> for PointerEmulationGrab {
|
||||||
|
fn proximity_in(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||||
|
event: &ProximityInEvent,
|
||||||
|
) {
|
||||||
|
handle.proximity_in(data, focus.clone(), event);
|
||||||
|
|
||||||
|
if !self.above_source && !self.tip_down && self.button_down.is_empty() {
|
||||||
|
handle.unset_grab(
|
||||||
|
self,
|
||||||
|
data,
|
||||||
|
SERIAL_COUNTER.next_serial(),
|
||||||
|
InputTime::now(),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
} else if let Some(pointer) = self.seat.get_pointer() {
|
||||||
|
pointer.motion(
|
||||||
|
data,
|
||||||
|
focus,
|
||||||
|
&pointer::MotionEvent {
|
||||||
|
location: event.location,
|
||||||
|
serial: SERIAL_COUNTER.next_serial(),
|
||||||
|
time: InputTime::now(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
pointer.frame(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proximity_out(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &ProximityOutEvent,
|
||||||
|
) {
|
||||||
|
handle.proximity_out(data, event);
|
||||||
|
|
||||||
|
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn down(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &DownEvent,
|
||||||
|
) {
|
||||||
|
handle.down(data, event);
|
||||||
|
self.tip_down = true;
|
||||||
|
|
||||||
|
self.pointer_button(data, MouseButton::Left, ButtonState::Pressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn up(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &UpEvent,
|
||||||
|
) {
|
||||||
|
handle.up(data, event);
|
||||||
|
|
||||||
|
self.pointer_button(data, MouseButton::Left, ButtonState::Released);
|
||||||
|
self.tip_down = false;
|
||||||
|
|
||||||
|
if !self.above_source && !self.tip_down && self.button_down.is_empty() {
|
||||||
|
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn motion(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||||
|
event: &Type,
|
||||||
|
) {
|
||||||
|
self.above_source = focus.as_ref().map(|(target, _)| target)
|
||||||
|
== self.start_data.focus.as_ref().map(|(target, _)| target);
|
||||||
|
handle.motion(data, self.start_data.focus.clone(), event);
|
||||||
|
|
||||||
|
if !self.above_source && !self.tip_down && self.button_down.is_empty() {
|
||||||
|
handle.unset_grab(
|
||||||
|
self,
|
||||||
|
data,
|
||||||
|
SERIAL_COUNTER.next_serial(),
|
||||||
|
InputTime::now(),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
} else if let Some(pointer) = self.seat.get_pointer() {
|
||||||
|
pointer.motion(
|
||||||
|
data,
|
||||||
|
focus,
|
||||||
|
&pointer::MotionEvent {
|
||||||
|
location: event.location,
|
||||||
|
serial: SERIAL_COUNTER.next_serial(),
|
||||||
|
time: InputTime::now(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
pointer.frame(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &ButtonEvent,
|
||||||
|
) {
|
||||||
|
handle.button(data, event);
|
||||||
|
|
||||||
|
self.button_down.retain(|b| b != &event.button);
|
||||||
|
if matches!(event.state, ButtonState::Pressed) {
|
||||||
|
self.button_down.push(event.button);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(button) = self.button_to_mouse(event.button) {
|
||||||
|
self.pointer_button(data, button, event.state);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.above_source && !self.tip_down && self.button_down.is_empty() {
|
||||||
|
handle.unset_grab(
|
||||||
|
self,
|
||||||
|
data,
|
||||||
|
SERIAL_COUNTER.next_serial(),
|
||||||
|
InputTime::now(),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn axis(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
frame: AxisFrame,
|
||||||
|
) {
|
||||||
|
handle.axis(data, frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
time: InputTime,
|
||||||
|
) {
|
||||||
|
handle.frame(data, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unset(&mut self, data: &mut State) {
|
||||||
|
if self.tip_down {
|
||||||
|
self.pointer_button(data, MouseButton::Left, ButtonState::Released);
|
||||||
|
}
|
||||||
|
|
||||||
|
let buttons = std::mem::take(&mut self.button_down);
|
||||||
|
for button in buttons.into_iter() {
|
||||||
|
if let Some(button) = self.button_to_mouse(button) {
|
||||||
|
self.pointer_button(data, button, ButtonState::Released);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_data(&self) -> &GrabStartData<State> {
|
||||||
|
&self.start_data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -289,7 +289,7 @@ Project home page: https://github.com/pop-os/cosmic-comp
|
||||||
Options:
|
Options:
|
||||||
-h, --help Show this message
|
-h, --help Show this message
|
||||||
--no-xwayland Run without Xwayland
|
--no-xwayland Run without Xwayland
|
||||||
-v, --version Show the version of cosmic-comp"#
|
-V, --version Show the version of cosmic-comp"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -124,7 +124,9 @@ pub fn setup_ei(
|
||||||
data.update_ei_input_method();
|
data.update_ei_input_method();
|
||||||
// Notify the remaining libei clients of the now-cleared modifier state
|
// Notify the remaining libei clients of the now-cleared modifier state
|
||||||
let seat = data.common.shell.read().seats.last_active().clone();
|
let seat = data.common.shell.read().seats.last_active().clone();
|
||||||
data.broadcast_ei_keyboard_modifiers(&seat);
|
if let Some(keyboard) = seat.get_keyboard() {
|
||||||
|
data.broadcast_ei_keyboard_modifiers(&keyboard);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
EiInputEvent::Event(event) => {
|
EiInputEvent::Event(event) => {
|
||||||
use smithay::backend::input::{InputEvent, KeyboardKeyEvent};
|
use smithay::backend::input::{InputEvent, KeyboardKeyEvent};
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,10 @@ use std::str::FromStr;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
use tracing::{debug, info, warn};
|
#[cfg(feature = "systemd")]
|
||||||
|
use tracing::warn;
|
||||||
|
use tracing::{debug, info};
|
||||||
|
#[cfg(feature = "systemd")]
|
||||||
use tracing_journald as journald;
|
use tracing_journald as journald;
|
||||||
use tracing_subscriber::{EnvFilter, filter::Directive, fmt, prelude::*};
|
use tracing_subscriber::{EnvFilter, filter::Directive, fmt, prelude::*};
|
||||||
|
|
||||||
|
|
@ -29,6 +32,7 @@ pub fn init_logger() -> Result<()> {
|
||||||
|
|
||||||
let fmt_layer = fmt::layer().compact();
|
let fmt_layer = fmt::layer().compact();
|
||||||
|
|
||||||
|
#[cfg(feature = "systemd")]
|
||||||
match journald::layer() {
|
match journald::layer() {
|
||||||
Ok(journald_layer) => tracing_subscriber::registry()
|
Ok(journald_layer) => tracing_subscriber::registry()
|
||||||
.with(fmt_layer)
|
.with(fmt_layer)
|
||||||
|
|
@ -43,6 +47,11 @@ pub fn init_logger() -> Result<()> {
|
||||||
warn!(?err, "Failed to init journald logging.");
|
warn!(?err, "Failed to init journald logging.");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
#[cfg(not(feature = "systemd"))]
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(fmt_layer)
|
||||||
|
.with(filter)
|
||||||
|
.init();
|
||||||
log_panics::init();
|
log_panics::init();
|
||||||
|
|
||||||
info!("Version: {}", std::env!("CARGO_PKG_VERSION"));
|
info!("Version: {}", std::env!("CARGO_PKG_VERSION"));
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,6 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// libcosmic-yoda enables zbus' `tokio` feature, so zbus 5.14 expects an
|
|
||||||
// ambient Tokio runtime via Handle::current(). cosmic-comp's loop is not
|
|
||||||
// async, so hold a runtime guard for the lifetime of run().
|
|
||||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
.expect("failed to build tokio runtime");
|
|
||||||
let _guard = runtime.enter();
|
|
||||||
|
|
||||||
if let Err(err) = cosmic_comp::run(Default::default()) {
|
if let Err(err) = cosmic_comp::run(Default::default()) {
|
||||||
tracing::error!("Error occured in main(): {}", err);
|
tracing::error!("Error occured in main(): {}", err);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ use id_tree::NodeId;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
drm::DrmNode,
|
drm::DrmNode,
|
||||||
input::KeyState,
|
input::{InputTime, KeyState},
|
||||||
renderer::{
|
renderer::{
|
||||||
element::{
|
element::{
|
||||||
Element, Kind, RenderElement, UnderlyingStorage,
|
Element, Kind, RenderElement, UnderlyingStorage,
|
||||||
|
|
@ -430,6 +430,10 @@ impl CosmicMapped {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_sticky(&self) -> bool {
|
||||||
|
self.active_window().is_sticky()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pending_size(&self) -> Option<Size<i32, Logical>> {
|
pub fn pending_size(&self) -> Option<Size<i32, Logical>> {
|
||||||
match &self.element {
|
match &self.element {
|
||||||
CosmicMappedInternal::Stack(s) => s.pending_size(),
|
CosmicMappedInternal::Stack(s) => s.pending_size(),
|
||||||
|
|
@ -1012,7 +1016,7 @@ impl KeyboardTarget<State> for CosmicMapped {
|
||||||
key: KeysymHandle<'_>,
|
key: KeysymHandle<'_>,
|
||||||
state: KeyState,
|
state: KeyState,
|
||||||
serial: Serial,
|
serial: Serial,
|
||||||
time: u32,
|
time: InputTime,
|
||||||
) {
|
) {
|
||||||
match &self.element {
|
match &self.element {
|
||||||
CosmicMappedInternal::Stack(s) => {
|
CosmicMappedInternal::Stack(s) => {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ use crate::{
|
||||||
shell::{
|
shell::{
|
||||||
element::{CosmicMappedKey, CosmicMappedKeyInner},
|
element::{CosmicMappedKey, CosmicMappedKeyInner},
|
||||||
focus::target::PointerFocusTarget,
|
focus::target::PointerFocusTarget,
|
||||||
grabs::{ReleaseMode, ResizeEdge},
|
grabs::{GrabType, ReleaseMode, ResizeEdge},
|
||||||
layout::tiling::NodeDesc,
|
layout::tiling::NodeDesc,
|
||||||
},
|
},
|
||||||
state::State,
|
state::State,
|
||||||
|
|
@ -38,7 +38,7 @@ use shortcuts::action::{Direction, FocusDirection};
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
drm::DrmNode,
|
drm::DrmNode,
|
||||||
input::KeyState,
|
input::{InputTime, KeyState, TabletToolDescriptor},
|
||||||
renderer::{
|
renderer::{
|
||||||
ImportAll, ImportMem, Renderer,
|
ImportAll, ImportMem, Renderer,
|
||||||
element::{Element, Id as RendererId, Kind, RenderElement, UnderlyingStorage},
|
element::{Element, Id as RendererId, Kind, RenderElement, UnderlyingStorage},
|
||||||
|
|
@ -52,14 +52,23 @@ use smithay::{
|
||||||
Seat,
|
Seat,
|
||||||
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
|
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
|
||||||
pointer::{
|
pointer::{
|
||||||
AxisFrame, ButtonEvent, CursorImageStatus, GestureHoldBeginEvent, GestureHoldEndEvent,
|
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent, CursorImageStatus,
|
||||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent,
|
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||||
|
GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent as PointerMotionEvent,
|
||||||
PointerTarget, RelativeMotionEvent,
|
PointerTarget, RelativeMotionEvent,
|
||||||
},
|
},
|
||||||
|
tablet::{
|
||||||
|
Tablet, TabletSeatTrait,
|
||||||
|
tool::{
|
||||||
|
AxisFrame as ToolAxisFrame, ButtonEvent as ToolButtonEvent,
|
||||||
|
DownEvent as ToolDownEvent, MotionEvent as ToolMotionEvent, TabletToolTarget,
|
||||||
|
UpEvent as ToolUpEvent,
|
||||||
|
},
|
||||||
|
},
|
||||||
touch::{
|
touch::{
|
||||||
DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent,
|
DownEvent as TouchDownEvent, FrameMarker, MotionEvent as TouchMotionEvent,
|
||||||
TouchTarget, UpEvent,
|
OrientationEvent, ShapeEvent, TouchTarget, UpEvent as TouchUpEvent,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
output::Output,
|
output::Output,
|
||||||
|
|
@ -112,6 +121,7 @@ pub struct CosmicStackInternal {
|
||||||
previous_keyboard: AtomicUsize,
|
previous_keyboard: AtomicUsize,
|
||||||
pointer_entered: AtomicU8,
|
pointer_entered: AtomicU8,
|
||||||
touch_serial: AtomicU32,
|
touch_serial: AtomicU32,
|
||||||
|
tablet_serial: AtomicU32,
|
||||||
reenter: AtomicBool,
|
reenter: AtomicBool,
|
||||||
potential_drag: Mutex<Option<usize>>,
|
potential_drag: Mutex<Option<usize>>,
|
||||||
override_alive: AtomicBool,
|
override_alive: AtomicBool,
|
||||||
|
|
@ -171,6 +181,7 @@ impl CosmicStack {
|
||||||
previous_keyboard: AtomicUsize::new(0),
|
previous_keyboard: AtomicUsize::new(0),
|
||||||
pointer_entered: AtomicU8::new(0),
|
pointer_entered: AtomicU8::new(0),
|
||||||
touch_serial: AtomicU32::new(0),
|
touch_serial: AtomicU32::new(0),
|
||||||
|
tablet_serial: AtomicU32::new(0),
|
||||||
reenter: AtomicBool::new(false),
|
reenter: AtomicBool::new(false),
|
||||||
potential_drag: Mutex::new(None),
|
potential_drag: Mutex::new(None),
|
||||||
override_alive: AtomicBool::new(true),
|
override_alive: AtomicBool::new(true),
|
||||||
|
|
@ -831,7 +842,7 @@ impl CosmicStack {
|
||||||
)));
|
)));
|
||||||
};
|
};
|
||||||
|
|
||||||
let radii = radii.map(|[a, _, c, _]| [a, 0, c, 0]);
|
let radii = radii.map(|[_, _, c, d]| [0, 0, c, d]);
|
||||||
windows[active].push_render_elements(
|
windows[active].push_render_elements(
|
||||||
renderer,
|
renderer,
|
||||||
window_loc,
|
window_loc,
|
||||||
|
|
@ -909,12 +920,19 @@ impl CosmicStack {
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
if let Some((grab, focus)) = res {
|
if let Some((grab, focus)) = res {
|
||||||
if grab.is_touch_grab() {
|
match grab.grab_type() {
|
||||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
GrabType::Touch => {
|
||||||
} else {
|
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||||
seat.get_pointer()
|
}
|
||||||
|
GrabType::Pointer => seat
|
||||||
|
.get_pointer()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.set_grab(state, grab, serial, focus);
|
.set_grab(state, grab, serial, focus),
|
||||||
|
GrabType::TabletTool => seat
|
||||||
|
.tablet_seat()
|
||||||
|
.get_tool(grab.tool().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.set_grab(state, grab, InputTime::now(), serial, focus),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -997,17 +1015,17 @@ impl CosmicStack {
|
||||||
.corner_radius(geometry_size)
|
.corner_radius(geometry_size)
|
||||||
.unwrap_or([default_radius; 4]);
|
.unwrap_or([default_radius; 4]);
|
||||||
|
|
||||||
|
corners[0] = 0;
|
||||||
corners[1] = 0;
|
corners[1] = 0;
|
||||||
corners[3] = 0;
|
|
||||||
|
|
||||||
corners
|
corners
|
||||||
} else {
|
} else {
|
||||||
let mut corners = active_window.corner_radius(geometry_size).unwrap_or(radii);
|
let mut corners = active_window.corner_radius(geometry_size).unwrap_or(radii);
|
||||||
|
|
||||||
corners[0] = radii[0].max(corners[0]);
|
corners[0] = radii[0];
|
||||||
corners[1] = radii[1];
|
corners[1] = radii[1];
|
||||||
corners[2] = radii[2].max(corners[2]);
|
corners[2] = radii[2].max(corners[2]);
|
||||||
corners[3] = radii[3];
|
corners[3] = radii[3].max(corners[3]);
|
||||||
|
|
||||||
corners
|
corners
|
||||||
}
|
}
|
||||||
|
|
@ -1089,12 +1107,19 @@ impl Program for CosmicStackInternal {
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
if let Some((grab, focus)) = res {
|
if let Some((grab, focus)) = res {
|
||||||
if grab.is_touch_grab() {
|
match grab.grab_type() {
|
||||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
GrabType::Touch => {
|
||||||
} else {
|
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||||
seat.get_pointer()
|
}
|
||||||
|
GrabType::Pointer => seat
|
||||||
|
.get_pointer()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.set_grab(state, grab, serial, focus);
|
.set_grab(state, grab, serial, focus),
|
||||||
|
GrabType::TabletTool => seat
|
||||||
|
.tablet_seat()
|
||||||
|
.get_tool(grab.tool().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.set_grab(state, grab, InputTime::now(), serial, focus),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -1547,7 +1572,7 @@ impl KeyboardTarget<State> for CosmicStack {
|
||||||
key: KeysymHandle<'_>,
|
key: KeysymHandle<'_>,
|
||||||
state: KeyState,
|
state: KeyState,
|
||||||
serial: Serial,
|
serial: Serial,
|
||||||
time: u32,
|
time: InputTime,
|
||||||
) {
|
) {
|
||||||
let active = self.keyboard_leave_if_previous(seat, data, serial);
|
let active = self.keyboard_leave_if_previous(seat, data, serial);
|
||||||
self.0.with_program(|p| {
|
self.0.with_program(|p| {
|
||||||
|
|
@ -1587,7 +1612,7 @@ impl KeyboardTarget<State> for CosmicStack {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PointerTarget<State> for CosmicStack {
|
impl PointerTarget<State> for CosmicStack {
|
||||||
fn enter(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
|
fn enter(&self, seat: &Seat<State>, data: &mut State, event: &PointerMotionEvent) {
|
||||||
let mut event = event.clone();
|
let mut event = event.clone();
|
||||||
self.0.with_program(|p| {
|
self.0.with_program(|p| {
|
||||||
let active_window = &p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)];
|
let active_window = &p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)];
|
||||||
|
|
@ -1615,7 +1640,7 @@ impl PointerTarget<State> for CosmicStack {
|
||||||
PointerTarget::enter(&self.0, seat, data, &event)
|
PointerTarget::enter(&self.0, seat, data, &event)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
|
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &PointerMotionEvent) {
|
||||||
let mut event = event.clone();
|
let mut event = event.clone();
|
||||||
self.0.with_program(|p| {
|
self.0.with_program(|p| {
|
||||||
let active = p.active.load(Ordering::SeqCst);
|
let active = p.active.load(Ordering::SeqCst);
|
||||||
|
|
@ -1658,7 +1683,7 @@ impl PointerTarget<State> for CosmicStack {
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {
|
fn button(&self, seat: &Seat<State>, data: &mut State, event: &PointerButtonEvent) {
|
||||||
match self.0.with_program(|p| p.current_focus()) {
|
match self.0.with_program(|p| p.current_focus()) {
|
||||||
Some(Focus::Header) => PointerTarget::button(&self.0, seat, data, event),
|
Some(Focus::Header) => PointerTarget::button(&self.0, seat, data, event),
|
||||||
Some(x) => {
|
Some(x) => {
|
||||||
|
|
@ -1689,14 +1714,12 @@ impl PointerTarget<State> for CosmicStack {
|
||||||
state.common.config.cosmic_conf.edge_snap_threshold,
|
state.common.config.cosmic_conf.edge_snap_threshold,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
if let Some((grab, focus)) = res {
|
if let Some((grab, focus)) = res
|
||||||
if grab.is_touch_grab() {
|
&& let GrabType::Pointer = grab.grab_type()
|
||||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
{
|
||||||
} else {
|
seat.get_pointer()
|
||||||
seat.get_pointer()
|
.unwrap()
|
||||||
.unwrap()
|
.set_grab(state, grab, serial, focus);
|
||||||
.set_grab(state, grab, serial, focus);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -1704,7 +1727,7 @@ impl PointerTarget<State> for CosmicStack {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: AxisFrame) {
|
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: PointerAxisFrame) {
|
||||||
if let Some(Focus::Header) = self.0.with_program(|p| p.current_focus()) {
|
if let Some(Focus::Header) = self.0.with_program(|p| p.current_focus()) {
|
||||||
PointerTarget::axis(&self.0, seat, data, frame)
|
PointerTarget::axis(&self.0, seat, data, frame)
|
||||||
}
|
}
|
||||||
|
|
@ -1716,7 +1739,7 @@ impl PointerTarget<State> for CosmicStack {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: u32) {
|
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: InputTime) {
|
||||||
self.0.with_program(|p| {
|
self.0.with_program(|p| {
|
||||||
let mut cursor_state = seat
|
let mut cursor_state = seat
|
||||||
.user_data()
|
.user_data()
|
||||||
|
|
@ -1752,14 +1775,12 @@ impl PointerTarget<State> for CosmicStack {
|
||||||
&state.common.event_loop_handle,
|
&state.common.event_loop_handle,
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
if let Some((grab, focus)) = res {
|
if let Some((grab, focus)) = res
|
||||||
if grab.is_touch_grab() {
|
&& let GrabType::Pointer = grab.grab_type()
|
||||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
{
|
||||||
} else {
|
seat.get_pointer()
|
||||||
seat.get_pointer()
|
.unwrap()
|
||||||
.unwrap()
|
.set_grab(state, grab, serial, focus);
|
||||||
.set_grab(state, grab, serial, focus);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -1832,7 +1853,7 @@ impl PointerTarget<State> for CosmicStack {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TouchTarget<State> for CosmicStack {
|
impl TouchTarget<State> for CosmicStack {
|
||||||
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {
|
fn down(&self, seat: &Seat<State>, data: &mut State, event: &TouchDownEvent) {
|
||||||
let mut event = event.clone();
|
let mut event = event.clone();
|
||||||
let active_window_geo = self.0.with_program(|p| {
|
let active_window_geo = self.0.with_program(|p| {
|
||||||
p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)].geometry()
|
p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)].geometry()
|
||||||
|
|
@ -1843,7 +1864,7 @@ impl TouchTarget<State> for CosmicStack {
|
||||||
TouchTarget::down(&self.0, seat, data, &event)
|
TouchTarget::down(&self.0, seat, data, &event)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn up(&self, seat: &Seat<State>, data: &mut State, event: &UpEvent) {
|
fn up(&self, seat: &Seat<State>, data: &mut State, event: &TouchUpEvent) {
|
||||||
TouchTarget::up(&self.0, seat, data, event)
|
TouchTarget::up(&self.0, seat, data, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1891,6 +1912,185 @@ impl TouchTarget<State> for CosmicStack {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TabletToolTarget<State> for CosmicStack {
|
||||||
|
fn proximity_in(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
tablet: &Tablet,
|
||||||
|
serial: Serial,
|
||||||
|
) {
|
||||||
|
TabletToolTarget::proximity_in(&self.0, seat, data, tool_descriptor, tablet, serial)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proximity_out(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
) {
|
||||||
|
self.0.with_program(|p| {
|
||||||
|
let mut cursor_state = seat
|
||||||
|
.user_data()
|
||||||
|
.get::<CursorState>()
|
||||||
|
.unwrap()
|
||||||
|
.lock()
|
||||||
|
.unwrap();
|
||||||
|
cursor_state.unset_shape();
|
||||||
|
let _previous = p.swap_focus(None);
|
||||||
|
});
|
||||||
|
TabletToolTarget::proximity_out(&self.0, seat, data, tool_descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn down(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
event: &ToolDownEvent,
|
||||||
|
) {
|
||||||
|
self.0.with_program(|p| {
|
||||||
|
p.tablet_serial
|
||||||
|
.store(event.serial.into(), Ordering::Release)
|
||||||
|
});
|
||||||
|
match self.0.with_program(|p| p.current_focus()) {
|
||||||
|
Some(Focus::Header) => {
|
||||||
|
TabletToolTarget::down(&self.0, seat, data, tool_descriptor, event)
|
||||||
|
}
|
||||||
|
Some(x) => {
|
||||||
|
let serial = self
|
||||||
|
.0
|
||||||
|
.with_program(|p| p.tablet_serial.load(Ordering::Acquire))
|
||||||
|
.into();
|
||||||
|
let seat = seat.clone();
|
||||||
|
let Some(surface) = self.0.with_program(|p| {
|
||||||
|
let window = &p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)];
|
||||||
|
window.wl_surface().map(Cow::into_owned)
|
||||||
|
}) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.0.loop_handle().insert_idle(move |state| {
|
||||||
|
let res = state.common.shell.write().resize_request(
|
||||||
|
&surface,
|
||||||
|
&seat,
|
||||||
|
serial,
|
||||||
|
match x {
|
||||||
|
Focus::ResizeTop => ResizeEdge::TOP,
|
||||||
|
Focus::ResizeTopLeft => ResizeEdge::TOP_LEFT,
|
||||||
|
Focus::ResizeTopRight => ResizeEdge::TOP_RIGHT,
|
||||||
|
Focus::ResizeBottom => ResizeEdge::BOTTOM,
|
||||||
|
Focus::ResizeBottomLeft => ResizeEdge::BOTTOM_LEFT,
|
||||||
|
Focus::ResizeBottomRight => ResizeEdge::BOTTOM_RIGHT,
|
||||||
|
Focus::ResizeLeft => ResizeEdge::LEFT,
|
||||||
|
Focus::ResizeRight => ResizeEdge::RIGHT,
|
||||||
|
Focus::Header => unreachable!(),
|
||||||
|
},
|
||||||
|
state.common.config.cosmic_conf.edge_snap_threshold,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
if let Some((grab, focus)) = res
|
||||||
|
&& let GrabType::TabletTool = grab.grab_type()
|
||||||
|
{
|
||||||
|
seat.tablet_seat()
|
||||||
|
.get_tool(grab.tool().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.set_grab(state, grab, InputTime::now(), serial, focus);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn up(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
event: &ToolUpEvent,
|
||||||
|
) {
|
||||||
|
TabletToolTarget::up(&self.0, seat, data, tool_descriptor, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn motion(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
event: &ToolMotionEvent,
|
||||||
|
) {
|
||||||
|
let mut event = event.clone();
|
||||||
|
self.0.with_program(|p| {
|
||||||
|
let active = p.active.load(Ordering::SeqCst);
|
||||||
|
let active_window = &p.windows.lock().unwrap()[active];
|
||||||
|
let Some(next) = Focus::under(active_window, TAB_HEIGHT, event.location) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let _previous = p.swap_focus(Some(next));
|
||||||
|
|
||||||
|
let mut cursor_state = seat
|
||||||
|
.user_data()
|
||||||
|
.get::<CursorState>()
|
||||||
|
.unwrap()
|
||||||
|
.lock()
|
||||||
|
.unwrap();
|
||||||
|
cursor_state.set_shape(next.cursor_shape());
|
||||||
|
seat.set_cursor_image_status(CursorImageStatus::default_named());
|
||||||
|
});
|
||||||
|
|
||||||
|
let active_window_geo = self.0.with_program(|p| {
|
||||||
|
p.windows.lock().unwrap()[p.active.load(Ordering::SeqCst)].geometry()
|
||||||
|
});
|
||||||
|
event.location -= active_window_geo.loc.to_f64();
|
||||||
|
|
||||||
|
TabletToolTarget::motion(&self.0, seat, data, tool_descriptor, &event);
|
||||||
|
if event.location.y < 0.0
|
||||||
|
|| event.location.y > TAB_HEIGHT as f64
|
||||||
|
|| event.location.x < 64.0
|
||||||
|
|| event.location.x > (active_window_geo.size.w as f64 - 64.0)
|
||||||
|
{
|
||||||
|
self.start_drag(
|
||||||
|
data,
|
||||||
|
seat,
|
||||||
|
self.0
|
||||||
|
.with_program(|p| p.tablet_serial.load(Ordering::Acquire))
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn axis(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
frame: ToolAxisFrame,
|
||||||
|
) {
|
||||||
|
TabletToolTarget::axis(&self.0, seat, data, tool_descriptor, frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
event: &ToolButtonEvent,
|
||||||
|
) {
|
||||||
|
TabletToolTarget::button(&self.0, seat, data, tool_descriptor, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
time: InputTime,
|
||||||
|
) {
|
||||||
|
TabletToolTarget::frame(&self.0, seat, data, tool_descriptor, time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub enum CosmicStackRenderElement<R: Renderer + ImportAll + ImportMem> {
|
pub enum CosmicStackRenderElement<R: Renderer + ImportAll + ImportMem> {
|
||||||
Header(IcedRenderElement<R>),
|
Header(IcedRenderElement<R>),
|
||||||
Shadow(PixelShaderElement),
|
Shadow(PixelShaderElement),
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use cosmic::{
|
||||||
layout::{Layout, Limits, Node},
|
layout::{Layout, Limits, Node},
|
||||||
mouse, overlay, renderer,
|
mouse, overlay, renderer,
|
||||||
text::{Ellipsize, EllipsizeHeightLimit, Shaping, Wrapping},
|
text::{Ellipsize, EllipsizeHeightLimit, Shaping, Wrapping},
|
||||||
|
touch,
|
||||||
widget::{Id, Widget, operation::Operation, tree::Tree},
|
widget::{Id, Widget, operation::Operation, tree::Tree},
|
||||||
},
|
},
|
||||||
widget::{self, container::draw_background, rule::FillMode, scrollable::AbsoluteOffset},
|
widget::{self, container::draw_background, rule::FillMode, scrollable::AbsoluteOffset},
|
||||||
|
|
@ -370,6 +371,7 @@ where
|
||||||
if matches!(
|
if matches!(
|
||||||
event,
|
event,
|
||||||
event::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
event::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left))
|
||||||
|
| event::Event::Touch(touch::Event::FingerPressed { .. })
|
||||||
) && let Some(message) = self.press_message.clone()
|
) && let Some(message) = self.press_message.clone()
|
||||||
{
|
{
|
||||||
shell.publish(message);
|
shell.publish(message);
|
||||||
|
|
@ -388,6 +390,7 @@ where
|
||||||
if matches!(
|
if matches!(
|
||||||
event,
|
event,
|
||||||
event::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
event::Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
|
||||||
|
| event::Event::Touch(touch::Event::FingerLifted { .. })
|
||||||
) {
|
) {
|
||||||
shell.publish(Message::activate(self.idx));
|
shell.publish(Message::activate(self.idx));
|
||||||
shell.capture_event();
|
shell.capture_event();
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ use keyframe::{
|
||||||
functions::{EaseInOutCubic, EaseOutCubic},
|
functions::{EaseInOutCubic, EaseOutCubic},
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet, VecDeque},
|
collections::{HashMap, VecDeque},
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -757,12 +757,10 @@ where
|
||||||
state.last_state = Some(current_state.clone());
|
state.last_state = Some(current_state.clone());
|
||||||
}
|
}
|
||||||
let last_state = state.last_state.as_mut().unwrap();
|
let last_state = state.last_state.as_mut().unwrap();
|
||||||
let unknown_keys = current_state
|
let unknown_keys = current_state.len() != last_state.len()
|
||||||
.keys()
|
|| current_state
|
||||||
.collect::<HashSet<_>>()
|
.keys()
|
||||||
.symmetric_difference(&last_state.keys().collect::<HashSet<_>>())
|
.any(|key| !last_state.contains_key(key));
|
||||||
.next()
|
|
||||||
.is_some();
|
|
||||||
|
|
||||||
enum Difference {
|
enum Difference {
|
||||||
NewOrRemoved,
|
NewOrRemoved,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ use std::{
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
drm::DrmNode,
|
drm::DrmNode,
|
||||||
|
input::InputTime,
|
||||||
renderer::{
|
renderer::{
|
||||||
ImportAll, Renderer, buffer_has_alpha,
|
ImportAll, Renderer, buffer_has_alpha,
|
||||||
element::{Kind, RenderElementStates, surface::KindEvaluation},
|
element::{Kind, RenderElementStates, surface::KindEvaluation},
|
||||||
|
|
@ -1084,7 +1085,7 @@ impl KeyboardTarget<State> for CosmicSurface {
|
||||||
key: KeysymHandle<'_>,
|
key: KeysymHandle<'_>,
|
||||||
state: smithay::backend::input::KeyState,
|
state: smithay::backend::input::KeyState,
|
||||||
serial: smithay::utils::Serial,
|
serial: smithay::utils::Serial,
|
||||||
time: u32,
|
time: InputTime,
|
||||||
) {
|
) {
|
||||||
match self.0.underlying_surface() {
|
match self.0.underlying_surface() {
|
||||||
WindowSurface::Wayland(toplevel) => {
|
WindowSurface::Wayland(toplevel) => {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use crate::{
|
||||||
shell::{
|
shell::{
|
||||||
element::{CosmicMappedKey, CosmicMappedKeyInner},
|
element::{CosmicMappedKey, CosmicMappedKeyInner},
|
||||||
focus::target::PointerFocusTarget,
|
focus::target::PointerFocusTarget,
|
||||||
grabs::{ReleaseMode, ResizeEdge},
|
grabs::{GrabType, ReleaseMode, ResizeEdge},
|
||||||
},
|
},
|
||||||
state::State,
|
state::State,
|
||||||
utils::{
|
utils::{
|
||||||
|
|
@ -21,7 +21,7 @@ use cosmic_comp_config::AppearanceConfig;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
drm::DrmNode,
|
drm::DrmNode,
|
||||||
input::KeyState,
|
input::{InputTime, KeyState, TabletToolDescriptor},
|
||||||
renderer::{
|
renderer::{
|
||||||
ImportAll, ImportMem, Renderer,
|
ImportAll, ImportMem, Renderer,
|
||||||
element::{Element, Id as RendererId, Kind, RenderElement, UnderlyingStorage},
|
element::{Element, Id as RendererId, Kind, RenderElement, UnderlyingStorage},
|
||||||
|
|
@ -35,14 +35,23 @@ use smithay::{
|
||||||
Seat,
|
Seat,
|
||||||
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
|
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
|
||||||
pointer::{
|
pointer::{
|
||||||
AxisFrame, ButtonEvent, CursorIcon, CursorImageStatus, GestureHoldBeginEvent,
|
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent, CursorIcon,
|
||||||
GestureHoldEndEvent, GesturePinchBeginEvent, GesturePinchEndEvent,
|
CursorImageStatus, GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||||
GesturePinchUpdateEvent, GestureSwipeBeginEvent, GestureSwipeEndEvent,
|
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||||
GestureSwipeUpdateEvent, MotionEvent, PointerTarget, RelativeMotionEvent,
|
GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent as PointerMotionEvent,
|
||||||
|
PointerTarget, RelativeMotionEvent,
|
||||||
|
},
|
||||||
|
tablet::{
|
||||||
|
Tablet, TabletSeatTrait,
|
||||||
|
tool::{
|
||||||
|
AxisFrame as ToolAxisFrame, ButtonEvent as ToolButtonEvent,
|
||||||
|
DownEvent as ToolDownEvent, MotionEvent as ToolMotionEvent, TabletToolTarget,
|
||||||
|
UpEvent as ToolUpEvent,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
touch::{
|
touch::{
|
||||||
DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent,
|
DownEvent as TouchDownEvent, FrameMarker, MotionEvent as TouchMotionEvent,
|
||||||
TouchTarget, UpEvent,
|
OrientationEvent, ShapeEvent, TouchTarget, UpEvent as TouchUpEvent,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
output::Output,
|
output::Output,
|
||||||
|
|
@ -429,12 +438,12 @@ impl CosmicWindow {
|
||||||
.map(|x| (x * scale as f32).round() as u8);
|
.map(|x| (x * scale as f32).round() as u8);
|
||||||
if has_ssd && !clip {
|
if has_ssd && !clip {
|
||||||
// bottom corners
|
// bottom corners
|
||||||
radii[0] = 0;
|
|
||||||
radii[2] = 0;
|
radii[2] = 0;
|
||||||
|
radii[3] = 0;
|
||||||
if is_tiled {
|
if is_tiled {
|
||||||
// top corners
|
// top corners
|
||||||
|
radii[0] = 0;
|
||||||
radii[1] = 0;
|
radii[1] = 0;
|
||||||
radii[3] = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -501,12 +510,12 @@ impl CosmicWindow {
|
||||||
&& !is_maximized;
|
&& !is_maximized;
|
||||||
if has_ssd && !clip {
|
if has_ssd && !clip {
|
||||||
// bottom corners
|
// bottom corners
|
||||||
radii[0] = 0;
|
|
||||||
radii[2] = 0;
|
radii[2] = 0;
|
||||||
|
radii[3] = 0;
|
||||||
if is_tiled {
|
if is_tiled {
|
||||||
// top corners
|
// top corners
|
||||||
|
radii[0] = 0;
|
||||||
radii[1] = 0;
|
radii[1] = 0;
|
||||||
radii[3] = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -551,8 +560,8 @@ impl CosmicWindow {
|
||||||
self.0.with_program(|p| {
|
self.0.with_program(|p| {
|
||||||
let mut radii = radii;
|
let mut radii = radii;
|
||||||
if has_ssd {
|
if has_ssd {
|
||||||
|
radii[0] = 0;
|
||||||
radii[1] = 0;
|
radii[1] = 0;
|
||||||
radii[3] = 0;
|
|
||||||
}
|
}
|
||||||
let theme = p.theme.lock().unwrap();
|
let theme = p.theme.lock().unwrap();
|
||||||
let frosted = if theme.cosmic().frosted_windows {
|
let frosted = if theme.cosmic().frosted_windows {
|
||||||
|
|
@ -576,8 +585,8 @@ impl CosmicWindow {
|
||||||
});
|
});
|
||||||
|
|
||||||
if has_ssd {
|
if has_ssd {
|
||||||
radii[0] = 0;
|
|
||||||
radii[2] = 0;
|
radii[2] = 0;
|
||||||
|
radii[3] = 0;
|
||||||
let ssd_loc = location
|
let ssd_loc = location
|
||||||
+ self
|
+ self
|
||||||
.0
|
.0
|
||||||
|
|
@ -682,26 +691,26 @@ impl CosmicWindow {
|
||||||
(has_ssd, true) => {
|
(has_ssd, true) => {
|
||||||
let mut corners = p.window.corner_radius(geometry_size).unwrap_or(radii);
|
let mut corners = p.window.corner_radius(geometry_size).unwrap_or(radii);
|
||||||
|
|
||||||
corners[0] = radii[0].max(corners[0]);
|
corners[0] = if has_ssd {
|
||||||
|
radii[0]
|
||||||
|
} else {
|
||||||
|
radii[0].max(corners[0])
|
||||||
|
};
|
||||||
corners[1] = if has_ssd {
|
corners[1] = if has_ssd {
|
||||||
radii[1]
|
radii[1]
|
||||||
} else {
|
} else {
|
||||||
radii[1].max(corners[1])
|
radii[1].max(corners[1])
|
||||||
};
|
};
|
||||||
corners[2] = radii[2].max(corners[2]);
|
corners[2] = radii[2].max(corners[2]);
|
||||||
corners[3] = if has_ssd {
|
corners[3] = radii[3].max(corners[3]);
|
||||||
radii[3]
|
|
||||||
} else {
|
|
||||||
radii[3].max(corners[3])
|
|
||||||
};
|
|
||||||
|
|
||||||
corners
|
corners
|
||||||
}
|
}
|
||||||
(true, false) => p
|
(true, false) => p
|
||||||
.window
|
.window
|
||||||
.corner_radius(geometry_size)
|
.corner_radius(geometry_size)
|
||||||
.map(|[a, _, c, _]| [a, radii[1], c, radii[3]])
|
.map(|[_, _, c, d]| [radii[0], radii[1], c, d])
|
||||||
.unwrap_or([default_radius, radii[1], default_radius, radii[3]]),
|
.unwrap_or([radii[0], radii[1], default_radius, default_radius]),
|
||||||
(false, false) => p
|
(false, false) => p
|
||||||
.window
|
.window
|
||||||
.corner_radius(geometry_size)
|
.corner_radius(geometry_size)
|
||||||
|
|
@ -746,12 +755,19 @@ impl Program for CosmicWindowInternal {
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
if let Some((grab, focus)) = res {
|
if let Some((grab, focus)) = res {
|
||||||
if grab.is_touch_grab() {
|
match grab.grab_type() {
|
||||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
GrabType::Touch => {
|
||||||
} else {
|
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||||
seat.get_pointer()
|
}
|
||||||
|
GrabType::Pointer => seat
|
||||||
|
.get_pointer()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.set_grab(state, grab, serial, focus);
|
.set_grab(state, grab, serial, focus),
|
||||||
|
GrabType::TabletTool => seat
|
||||||
|
.tablet_seat()
|
||||||
|
.get_tool(grab.tool().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.set_grab(state, grab, InputTime::now(), serial, focus),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -981,7 +997,7 @@ impl KeyboardTarget<State> for CosmicWindow {
|
||||||
key: KeysymHandle<'_>,
|
key: KeysymHandle<'_>,
|
||||||
state: KeyState,
|
state: KeyState,
|
||||||
serial: Serial,
|
serial: Serial,
|
||||||
time: u32,
|
time: InputTime,
|
||||||
) {
|
) {
|
||||||
self.0
|
self.0
|
||||||
.with_program(|p| KeyboardTarget::key(&p.window, seat, data, key, state, serial, time))
|
.with_program(|p| KeyboardTarget::key(&p.window, seat, data, key, state, serial, time))
|
||||||
|
|
@ -999,7 +1015,7 @@ impl KeyboardTarget<State> for CosmicWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PointerTarget<State> for CosmicWindow {
|
impl PointerTarget<State> for CosmicWindow {
|
||||||
fn enter(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
|
fn enter(&self, seat: &Seat<State>, data: &mut State, event: &PointerMotionEvent) {
|
||||||
let mut event = event.clone();
|
let mut event = event.clone();
|
||||||
self.0.with_program(|p| {
|
self.0.with_program(|p| {
|
||||||
let has_ssd = p.has_ssd(false);
|
let has_ssd = p.has_ssd(false);
|
||||||
|
|
@ -1012,8 +1028,7 @@ impl PointerTarget<State> for CosmicWindow {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
let old_focus = p.swap_focus(Some(next));
|
let _ = p.swap_focus(Some(next));
|
||||||
assert_eq!(old_focus, None);
|
|
||||||
|
|
||||||
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
|
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
|
||||||
cursor_state.lock().unwrap().set_shape(next.cursor_shape());
|
cursor_state.lock().unwrap().set_shape(next.cursor_shape());
|
||||||
|
|
@ -1025,7 +1040,7 @@ impl PointerTarget<State> for CosmicWindow {
|
||||||
PointerTarget::enter(&self.0, seat, data, &event)
|
PointerTarget::enter(&self.0, seat, data, &event)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &MotionEvent) {
|
fn motion(&self, seat: &Seat<State>, data: &mut State, event: &PointerMotionEvent) {
|
||||||
let mut event = event.clone();
|
let mut event = event.clone();
|
||||||
self.0.with_program(|p| {
|
self.0.with_program(|p| {
|
||||||
let has_ssd = p.has_ssd(false);
|
let has_ssd = p.has_ssd(false);
|
||||||
|
|
@ -1057,7 +1072,7 @@ impl PointerTarget<State> for CosmicWindow {
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {
|
fn button(&self, seat: &Seat<State>, data: &mut State, event: &PointerButtonEvent) {
|
||||||
match self.0.with_program(|p| p.current_focus()) {
|
match self.0.with_program(|p| p.current_focus()) {
|
||||||
Some(Focus::Header) => PointerTarget::button(&self.0, seat, data, event),
|
Some(Focus::Header) => PointerTarget::button(&self.0, seat, data, event),
|
||||||
Some(x) => {
|
Some(x) => {
|
||||||
|
|
@ -1093,14 +1108,12 @@ impl PointerTarget<State> for CosmicWindow {
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some((grab, focus)) = res {
|
if let Some((grab, focus)) = res
|
||||||
if grab.is_touch_grab() {
|
&& let GrabType::Pointer = grab.grab_type()
|
||||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
{
|
||||||
} else {
|
seat.get_pointer()
|
||||||
seat.get_pointer()
|
.unwrap()
|
||||||
.unwrap()
|
.set_grab(state, grab, serial, focus)
|
||||||
.set_grab(state, grab, serial, focus);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -1108,7 +1121,7 @@ impl PointerTarget<State> for CosmicWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: AxisFrame) {
|
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: PointerAxisFrame) {
|
||||||
if let Some(Focus::Header) = self.0.with_program(|p| p.current_focus()) {
|
if let Some(Focus::Header) = self.0.with_program(|p| p.current_focus()) {
|
||||||
PointerTarget::axis(&self.0, seat, data, frame)
|
PointerTarget::axis(&self.0, seat, data, frame)
|
||||||
}
|
}
|
||||||
|
|
@ -1120,7 +1133,7 @@ impl PointerTarget<State> for CosmicWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: u32) {
|
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: InputTime) {
|
||||||
self.0.with_program(|p| {
|
self.0.with_program(|p| {
|
||||||
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
|
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
|
||||||
cursor_state.lock().unwrap().unset_shape();
|
cursor_state.lock().unwrap().unset_shape();
|
||||||
|
|
@ -1195,7 +1208,7 @@ impl PointerTarget<State> for CosmicWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TouchTarget<State> for CosmicWindow {
|
impl TouchTarget<State> for CosmicWindow {
|
||||||
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {
|
fn down(&self, seat: &Seat<State>, data: &mut State, event: &TouchDownEvent) {
|
||||||
let mut event = event.clone();
|
let mut event = event.clone();
|
||||||
self.0.with_program(|p| {
|
self.0.with_program(|p| {
|
||||||
event.location -= p.window.geometry().loc.to_f64();
|
event.location -= p.window.geometry().loc.to_f64();
|
||||||
|
|
@ -1203,7 +1216,7 @@ impl TouchTarget<State> for CosmicWindow {
|
||||||
TouchTarget::down(&self.0, seat, data, &event)
|
TouchTarget::down(&self.0, seat, data, &event)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn up(&self, seat: &Seat<State>, data: &mut State, event: &UpEvent) {
|
fn up(&self, seat: &Seat<State>, data: &mut State, event: &TouchUpEvent) {
|
||||||
TouchTarget::up(&self.0, seat, data, event)
|
TouchTarget::up(&self.0, seat, data, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1234,6 +1247,155 @@ impl TouchTarget<State> for CosmicWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TabletToolTarget<State> for CosmicWindow {
|
||||||
|
fn proximity_in(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
tablet: &Tablet,
|
||||||
|
serial: Serial,
|
||||||
|
) {
|
||||||
|
TabletToolTarget::proximity_in(&self.0, seat, data, tool_descriptor, tablet, serial)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proximity_out(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
) {
|
||||||
|
self.0.with_program(|p| {
|
||||||
|
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
|
||||||
|
cursor_state.lock().unwrap().unset_shape();
|
||||||
|
let _previous = p.swap_focus(None);
|
||||||
|
});
|
||||||
|
TabletToolTarget::proximity_out(&self.0, seat, data, tool_descriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn down(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
event: &ToolDownEvent,
|
||||||
|
) {
|
||||||
|
match self.0.with_program(|p| p.current_focus()) {
|
||||||
|
Some(Focus::Header) => {
|
||||||
|
TabletToolTarget::down(&self.0, seat, data, tool_descriptor, event)
|
||||||
|
}
|
||||||
|
Some(x) => {
|
||||||
|
let serial = event.serial;
|
||||||
|
let seat = seat.clone();
|
||||||
|
let Some(surface) = self.wl_surface().map(Cow::into_owned) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.0.loop_handle().insert_idle(move |state| {
|
||||||
|
let res = state.common.shell.write().resize_request(
|
||||||
|
&surface,
|
||||||
|
&seat,
|
||||||
|
serial,
|
||||||
|
match x {
|
||||||
|
Focus::ResizeTop => ResizeEdge::TOP,
|
||||||
|
Focus::ResizeTopLeft => ResizeEdge::TOP_LEFT,
|
||||||
|
Focus::ResizeTopRight => ResizeEdge::TOP_RIGHT,
|
||||||
|
Focus::ResizeBottom => ResizeEdge::BOTTOM,
|
||||||
|
Focus::ResizeBottomLeft => ResizeEdge::BOTTOM_LEFT,
|
||||||
|
Focus::ResizeBottomRight => ResizeEdge::BOTTOM_RIGHT,
|
||||||
|
Focus::ResizeLeft => ResizeEdge::LEFT,
|
||||||
|
Focus::ResizeRight => ResizeEdge::RIGHT,
|
||||||
|
Focus::Header => unreachable!(),
|
||||||
|
},
|
||||||
|
state.common.config.cosmic_conf.edge_snap_threshold,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some((grab, focus)) = res
|
||||||
|
&& let GrabType::TabletTool = grab.grab_type()
|
||||||
|
{
|
||||||
|
seat.tablet_seat()
|
||||||
|
.get_tool(grab.tool().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.set_grab(state, grab, InputTime::now(), serial, focus)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn up(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
event: &ToolUpEvent,
|
||||||
|
) {
|
||||||
|
TabletToolTarget::up(&self.0, seat, data, tool_descriptor, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn motion(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
event: &ToolMotionEvent,
|
||||||
|
) {
|
||||||
|
let mut event = event.clone();
|
||||||
|
self.0.with_program(|p| {
|
||||||
|
let has_ssd = p.has_ssd(false);
|
||||||
|
if has_ssd || p.has_tiled_state() {
|
||||||
|
let Some(next) = Focus::under(
|
||||||
|
&p.window,
|
||||||
|
if has_ssd { SSD_HEIGHT } else { 0 },
|
||||||
|
event.location,
|
||||||
|
) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let _previous = p.swap_focus(Some(next));
|
||||||
|
|
||||||
|
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
|
||||||
|
cursor_state.lock().unwrap().set_shape(next.cursor_shape());
|
||||||
|
seat.set_cursor_image_status(CursorImageStatus::default_named());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
event.location -= self.0.with_program(|p| p.window.geometry().loc.to_f64());
|
||||||
|
TabletToolTarget::motion(&self.0, seat, data, tool_descriptor, &event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn axis(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
frame: ToolAxisFrame,
|
||||||
|
) {
|
||||||
|
TabletToolTarget::axis(&self.0, seat, data, tool_descriptor, frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
event: &ToolButtonEvent,
|
||||||
|
) {
|
||||||
|
TabletToolTarget::button(&self.0, seat, data, tool_descriptor, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
time: InputTime,
|
||||||
|
) {
|
||||||
|
TabletToolTarget::frame(&self.0, seat, data, tool_descriptor, time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl WaylandFocus for CosmicWindow {
|
impl WaylandFocus for CosmicWindow {
|
||||||
fn wl_surface(&self) -> Option<Cow<'_, WlSurface>> {
|
fn wl_surface(&self) -> Option<Cow<'_, WlSurface>> {
|
||||||
self.0.with_program(|p| {
|
self.0.with_program(|p| {
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,18 @@ use crate::{
|
||||||
shell::{CosmicSurface, MinimizedWindow, Shell, Trigger, element::CosmicMapped},
|
shell::{CosmicSurface, MinimizedWindow, Shell, Trigger, element::CosmicMapped},
|
||||||
state::{Common, State},
|
state::{Common, State},
|
||||||
utils::prelude::*,
|
utils::prelude::*,
|
||||||
wayland::handlers::{xdg_shell::PopupGrabData, xwayland_keyboard_grab::XWaylandGrabSeatData},
|
wayland::{
|
||||||
|
handlers::{xdg_shell::PopupGrabData, xwayland_keyboard_grab::XWaylandGrabSeatData},
|
||||||
|
protocols::session_lock_layer::layer_show_on_lock,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use indexmap::IndexSet;
|
use indexmap::IndexSet;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
desktop::{PopupUngrabStrategy, layer_map_for_output},
|
backend::input::InputTime,
|
||||||
|
desktop::{
|
||||||
|
LayerSurface, PopupUngrabStrategy, WindowSurfaceType, find_popup_root_surface,
|
||||||
|
layer_map_for_output,
|
||||||
|
},
|
||||||
input::{Seat, pointer::MotionEvent},
|
input::{Seat, pointer::MotionEvent},
|
||||||
output::Output,
|
output::Output,
|
||||||
reexports::wayland_server::{Resource, protocol::wl_surface::WlSurface},
|
reexports::wayland_server::{Resource, protocol::wl_surface::WlSurface},
|
||||||
|
|
@ -248,8 +255,17 @@ impl Shell {
|
||||||
let workspace = if let Some(workspace) = workspace {
|
let workspace = if let Some(workspace) = workspace {
|
||||||
self.workspaces.space_for_handle_mut(&workspace.0).unwrap()
|
self.workspaces.space_for_handle_mut(&workspace.0).unwrap()
|
||||||
} else {
|
} else {
|
||||||
//should this be the active output or the focused output?
|
let output = match &target {
|
||||||
self.active_space_mut(&seat.focused_or_active_output())
|
FocusTarget::Window(mapped) => self
|
||||||
|
.workspaces
|
||||||
|
.sets
|
||||||
|
.iter()
|
||||||
|
.find(|(_, set)| set.sticky_layer.mapped().any(|m| m == mapped))
|
||||||
|
.map(|(output, _)| output.clone()),
|
||||||
|
FocusTarget::Fullscreen(_) => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.active_space_mut(&output.unwrap_or_else(|| seat.active_output()))
|
||||||
.unwrap()
|
.unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -419,7 +435,7 @@ fn update_focus_state(
|
||||||
&MotionEvent {
|
&MotionEvent {
|
||||||
location: new_pos.as_logical(),
|
location: new_pos.as_logical(),
|
||||||
serial: SERIAL_COUNTER.next_serial(),
|
serial: SERIAL_COUNTER.next_serial(),
|
||||||
time: 0,
|
time: InputTime::now(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -626,19 +642,42 @@ fn focus_target_is_valid(
|
||||||
output: &Output,
|
output: &Output,
|
||||||
target: KeyboardFocusTarget,
|
target: KeyboardFocusTarget,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
// If a session lock is active, only lock surfaces can be focused
|
// If a session lock is active, only lock surfaces and lock layers can be focused
|
||||||
if shell.session_lock.is_some() {
|
if shell.session_lock.is_some() {
|
||||||
|
if let KeyboardFocusTarget::LayerSurface(layer) = &target {
|
||||||
|
return layer_show_on_lock(layer.wl_surface());
|
||||||
|
} else if let KeyboardFocusTarget::Popup(popup) = &target
|
||||||
|
&& let Ok(root) = find_popup_root_surface(popup)
|
||||||
|
{
|
||||||
|
return layer_show_on_lock(&root);
|
||||||
|
}
|
||||||
return matches!(target, KeyboardFocusTarget::LockSurface(_));
|
return matches!(target, KeyboardFocusTarget::LockSurface(_));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If an exclusive layer shell surface exists (on any output), only exclusive
|
// If an exclusive layer shell surface exists (on any output), only exclusive
|
||||||
// shell surfaces can have focus, on the highest layer with exclusive surfaces.
|
// shell surfaces can have focus, on the highest layer with exclusive surfaces.
|
||||||
|
// Popups are judged by their root surface, so an exclusive surface can
|
||||||
|
// still open grabbing popups (menus, dropdowns, context menus).
|
||||||
if let Some(layer) = exclusive_layer_surface_layer(shell) {
|
if let Some(layer) = exclusive_layer_surface_layer(shell) {
|
||||||
return if let KeyboardFocusTarget::LayerSurface(layer_surface) = target {
|
let is_exclusive_on_layer = |layer_surface: &LayerSurface| {
|
||||||
let data = layer_surface.cached_state();
|
let data = layer_surface.cached_state();
|
||||||
(data.keyboard_interactivity, data.layer) == (KeyboardInteractivity::Exclusive, layer)
|
(data.keyboard_interactivity, data.layer) == (KeyboardInteractivity::Exclusive, layer)
|
||||||
} else {
|
};
|
||||||
false
|
return match target {
|
||||||
|
KeyboardFocusTarget::LayerSurface(layer_surface) => {
|
||||||
|
is_exclusive_on_layer(&layer_surface)
|
||||||
|
}
|
||||||
|
KeyboardFocusTarget::Popup(popup) => find_popup_root_surface(&popup)
|
||||||
|
.ok()
|
||||||
|
.and_then(|root| {
|
||||||
|
shell.outputs().find_map(|o| {
|
||||||
|
layer_map_for_output(o)
|
||||||
|
.layer_for_surface(&root, WindowSurfaceType::ALL)
|
||||||
|
.map(&is_exclusive_on_layer)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or(false),
|
||||||
|
_ => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -768,7 +807,7 @@ fn update_pointer_focus(state: &mut State, seat: &Seat<State>) {
|
||||||
&MotionEvent {
|
&MotionEvent {
|
||||||
location: pointer.current_location(),
|
location: pointer.current_location(),
|
||||||
serial: SERIAL_COUNTER.next_serial(),
|
serial: SERIAL_COUNTER.next_serial(),
|
||||||
time: state.common.clock.now().as_millis(),
|
time: InputTime::now(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ use crate::{
|
||||||
prelude::OutputExt,
|
prelude::OutputExt,
|
||||||
quirks::{WORKSPACE_OVERVIEW_NAMESPACE, workspace_overview_is_open},
|
quirks::{WORKSPACE_OVERVIEW_NAMESPACE, workspace_overview_is_open},
|
||||||
},
|
},
|
||||||
wayland::protocols::workspace::WorkspaceHandle,
|
wayland::protocols::{session_lock_layer::layer_show_on_lock, workspace::WorkspaceHandle},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub enum Stage<'a> {
|
pub enum Stage<'a> {
|
||||||
|
|
@ -77,6 +77,8 @@ fn render_input_order_internal<R: 'static>(
|
||||||
element_filter: ElementFilter,
|
element_filter: ElementFilter,
|
||||||
mut callback: impl FnMut(Stage) -> ControlFlow<Result<R, OutputNoMode>, ()>,
|
mut callback: impl FnMut(Stage) -> ControlFlow<Result<R, OutputNoMode>, ()>,
|
||||||
) -> ControlFlow<Result<R, OutputNoMode>, ()> {
|
) -> ControlFlow<Result<R, OutputNoMode>, ()> {
|
||||||
|
// NOTE: Keep in sync with other surface iteration functions
|
||||||
|
|
||||||
if shell
|
if shell
|
||||||
.zoom_state
|
.zoom_state
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -87,7 +89,92 @@ fn render_input_order_internal<R: 'static>(
|
||||||
|
|
||||||
// Session Lock
|
// Session Lock
|
||||||
if let Some(session_lock) = &shell.session_lock {
|
if let Some(session_lock) = &shell.session_lock {
|
||||||
return callback(Stage::SessionLock(session_lock.surfaces.get(output)));
|
for (layer, popup, location) in layer_popups(output, Layer::Overlay, element_filter) {
|
||||||
|
if !layer_show_on_lock(layer.wl_surface()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
callback(Stage::LayerPopup {
|
||||||
|
layer,
|
||||||
|
popup: &popup,
|
||||||
|
location,
|
||||||
|
workspace_idx: current.1,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
for (layer, location) in layer_surfaces(output, Layer::Overlay, element_filter) {
|
||||||
|
if !layer_show_on_lock(layer.wl_surface()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
callback(Stage::LayerSurface {
|
||||||
|
layer,
|
||||||
|
location,
|
||||||
|
workspace_idx: current.1,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
for (layer, popup, location) in layer_popups(output, Layer::Top, element_filter) {
|
||||||
|
if !layer_show_on_lock(layer.wl_surface()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
callback(Stage::LayerPopup {
|
||||||
|
layer,
|
||||||
|
popup: &popup,
|
||||||
|
location,
|
||||||
|
workspace_idx: current.1,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
for (layer, location) in layer_surfaces(output, Layer::Top, element_filter) {
|
||||||
|
if !layer_show_on_lock(layer.wl_surface()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
callback(Stage::LayerSurface {
|
||||||
|
layer,
|
||||||
|
location,
|
||||||
|
workspace_idx: current.1,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
callback(Stage::SessionLock(session_lock.surfaces.get(output)))?;
|
||||||
|
for (layer, popup, location) in layer_popups(output, Layer::Bottom, element_filter) {
|
||||||
|
if !layer_show_on_lock(layer.wl_surface()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
callback(Stage::LayerPopup {
|
||||||
|
layer,
|
||||||
|
popup: &popup,
|
||||||
|
location,
|
||||||
|
workspace_idx: current.1,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
for (layer, popup, location) in layer_popups(output, Layer::Background, element_filter) {
|
||||||
|
if !layer_show_on_lock(layer.wl_surface()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
callback(Stage::LayerPopup {
|
||||||
|
layer,
|
||||||
|
popup: &popup,
|
||||||
|
location,
|
||||||
|
workspace_idx: current.1,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
for (layer, location) in layer_surfaces(output, Layer::Bottom, element_filter) {
|
||||||
|
if !layer_show_on_lock(layer.wl_surface()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
callback(Stage::LayerSurface {
|
||||||
|
layer,
|
||||||
|
location,
|
||||||
|
workspace_idx: current.1,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
for (layer, location) in layer_surfaces(output, Layer::Background, element_filter) {
|
||||||
|
if !layer_show_on_lock(layer.wl_surface()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
callback(Stage::LayerSurface {
|
||||||
|
layer,
|
||||||
|
location,
|
||||||
|
workspace_idx: current.1,
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
return ControlFlow::Continue(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Overlay-level layer shell
|
// Overlay-level layer shell
|
||||||
|
|
|
||||||
|
|
@ -16,21 +16,30 @@ use crate::{
|
||||||
};
|
};
|
||||||
use id_tree::NodeId;
|
use id_tree::NodeId;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::input::KeyState,
|
backend::input::{InputTime, KeyState, TabletToolDescriptor},
|
||||||
desktop::{LayerSurface, PopupKind, WindowSurface, WindowSurfaceType, space::SpaceElement},
|
desktop::{LayerSurface, PopupKind, WindowSurface, WindowSurfaceType, space::SpaceElement},
|
||||||
input::{
|
input::{
|
||||||
Seat,
|
Seat,
|
||||||
dnd::{DndFocus, OfferData, Source},
|
dnd::{DndFocus, OfferData, Source},
|
||||||
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
|
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
|
||||||
pointer::{
|
pointer::{
|
||||||
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent,
|
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent,
|
||||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||||
MotionEvent as PointerMotionEvent, PointerTarget, RelativeMotionEvent,
|
GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent as PointerMotionEvent,
|
||||||
|
PointerTarget, RelativeMotionEvent,
|
||||||
|
},
|
||||||
|
tablet::{
|
||||||
|
Tablet,
|
||||||
|
tool::{
|
||||||
|
AxisFrame as ToolAxisFrame, ButtonEvent as ToolButtonEvent,
|
||||||
|
DownEvent as ToolDownEvent, MotionEvent as ToolMotionEvent, TabletToolHandle,
|
||||||
|
TabletToolTarget, UpEvent as ToolUpEvent,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
touch::{
|
touch::{
|
||||||
DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent,
|
DownEvent as TouchDownEvent, FrameMarker, MotionEvent as TouchMotionEvent,
|
||||||
TouchTarget, UpEvent,
|
OrientationEvent, ShapeEvent, TouchTarget, UpEvent as TouchUpEvent,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
reexports::wayland_server::{
|
reexports::wayland_server::{
|
||||||
|
|
@ -164,6 +173,17 @@ impl PointerFocusTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn inner_tablet_tool_target(&self) -> &dyn TabletToolTarget<State> {
|
||||||
|
match self {
|
||||||
|
PointerFocusTarget::WlSurface { surface, .. } => surface,
|
||||||
|
PointerFocusTarget::X11Surface { surface, .. } => surface,
|
||||||
|
PointerFocusTarget::StackUI(u) => u,
|
||||||
|
PointerFocusTarget::WindowUI(u) => u,
|
||||||
|
PointerFocusTarget::ResizeFork(f) => f,
|
||||||
|
PointerFocusTarget::ZoomUI(e) => e,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn under_surface<P: Into<Point<f64, Logical>>>(
|
pub fn under_surface<P: Into<Point<f64, Logical>>>(
|
||||||
surface: &CosmicSurface,
|
surface: &CosmicSurface,
|
||||||
point: P,
|
point: P,
|
||||||
|
|
@ -272,9 +292,10 @@ impl PointerFocusTarget {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let cursor_hotspot = if let Some(CursorGeometry { hotspot, .. }) =
|
let cursor_hotspot = if let Some(CursorGeometry { hotspot, .. }) = seat.cursor_geometry(
|
||||||
seat.cursor_geometry((0.0, 0.0), Duration::from_millis(event.time as u64).into())
|
(0.0, 0.0),
|
||||||
{
|
Duration::from_millis(event.time.millis() as u64).into(),
|
||||||
|
) {
|
||||||
hotspot
|
hotspot
|
||||||
} else {
|
} else {
|
||||||
Point::from((0, 0))
|
Point::from((0, 0))
|
||||||
|
|
@ -285,6 +306,16 @@ impl PointerFocusTarget {
|
||||||
session.set_cursor_hotspot(cursor_hotspot);
|
session.set_cursor_hotspot(cursor_hotspot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn supports_tool(&self, tool_handle: &TabletToolHandle<State>) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::WlSurface { surface, .. } if surface.client().is_some() => tool_handle
|
||||||
|
.client_tools(&surface.client().unwrap())
|
||||||
|
.next()
|
||||||
|
.is_some(),
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KeyboardFocusTarget {
|
impl KeyboardFocusTarget {
|
||||||
|
|
@ -408,16 +439,16 @@ impl PointerTarget<State> for PointerFocusTarget {
|
||||||
self.inner_pointer_target()
|
self.inner_pointer_target()
|
||||||
.relative_motion(seat, data, event);
|
.relative_motion(seat, data, event);
|
||||||
}
|
}
|
||||||
fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {
|
fn button(&self, seat: &Seat<State>, data: &mut State, event: &PointerButtonEvent) {
|
||||||
self.inner_pointer_target().button(seat, data, event);
|
self.inner_pointer_target().button(seat, data, event);
|
||||||
}
|
}
|
||||||
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: AxisFrame) {
|
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: PointerAxisFrame) {
|
||||||
self.inner_pointer_target().axis(seat, data, frame);
|
self.inner_pointer_target().axis(seat, data, frame);
|
||||||
}
|
}
|
||||||
fn frame(&self, seat: &Seat<State>, data: &mut State) {
|
fn frame(&self, seat: &Seat<State>, data: &mut State) {
|
||||||
self.inner_pointer_target().frame(seat, data);
|
self.inner_pointer_target().frame(seat, data);
|
||||||
}
|
}
|
||||||
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: u32) {
|
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: InputTime) {
|
||||||
let toplevel = self.toplevel(&data.common.shell.read());
|
let toplevel = self.toplevel(&data.common.shell.read());
|
||||||
if let Some(element) = toplevel {
|
if let Some(element) = toplevel {
|
||||||
for session in element.cursor_sessions() {
|
for session in element.cursor_sessions() {
|
||||||
|
|
@ -497,12 +528,102 @@ impl PointerTarget<State> for PointerFocusTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TabletToolTarget<State> for PointerFocusTarget {
|
||||||
|
fn proximity_in(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
tablet: &Tablet,
|
||||||
|
serial: Serial,
|
||||||
|
) {
|
||||||
|
self.inner_tablet_tool_target()
|
||||||
|
.proximity_in(seat, data, tool_descriptor, tablet, serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proximity_out(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
) {
|
||||||
|
self.inner_tablet_tool_target()
|
||||||
|
.proximity_out(seat, data, tool_descriptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn down(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
event: &ToolDownEvent,
|
||||||
|
) {
|
||||||
|
self.inner_tablet_tool_target()
|
||||||
|
.down(seat, data, tool_descriptor, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn up(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
event: &ToolUpEvent,
|
||||||
|
) {
|
||||||
|
self.inner_tablet_tool_target()
|
||||||
|
.up(seat, data, tool_descriptor, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn motion(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
event: &ToolMotionEvent,
|
||||||
|
) {
|
||||||
|
self.inner_tablet_tool_target()
|
||||||
|
.motion(seat, data, tool_descriptor, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn axis(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
frame: ToolAxisFrame,
|
||||||
|
) {
|
||||||
|
self.inner_tablet_tool_target()
|
||||||
|
.axis(seat, data, tool_descriptor, frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
event: &ToolButtonEvent,
|
||||||
|
) {
|
||||||
|
self.inner_tablet_tool_target()
|
||||||
|
.button(seat, data, tool_descriptor, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
time: InputTime,
|
||||||
|
) {
|
||||||
|
self.inner_tablet_tool_target()
|
||||||
|
.frame(seat, data, tool_descriptor, time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TouchTarget<State> for PointerFocusTarget {
|
impl TouchTarget<State> for PointerFocusTarget {
|
||||||
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {
|
fn down(&self, seat: &Seat<State>, data: &mut State, event: &TouchDownEvent) {
|
||||||
self.inner_touch_target().down(seat, data, event);
|
self.inner_touch_target().down(seat, data, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn up(&self, seat: &Seat<State>, data: &mut State, event: &UpEvent) {
|
fn up(&self, seat: &Seat<State>, data: &mut State, event: &TouchUpEvent) {
|
||||||
self.inner_touch_target().up(seat, data, event);
|
self.inner_touch_target().up(seat, data, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -593,7 +714,7 @@ impl DndFocus<State> for PointerFocusTarget {
|
||||||
offer: Option<&mut CosmicOfferData<S>>,
|
offer: Option<&mut CosmicOfferData<S>>,
|
||||||
seat: &Seat<State>,
|
seat: &Seat<State>,
|
||||||
location: Point<f64, Logical>,
|
location: Point<f64, Logical>,
|
||||||
time: u32,
|
time: InputTime,
|
||||||
) {
|
) {
|
||||||
match self {
|
match self {
|
||||||
PointerFocusTarget::WlSurface { surface, .. } => {
|
PointerFocusTarget::WlSurface { surface, .. } => {
|
||||||
|
|
@ -695,7 +816,7 @@ impl KeyboardTarget<State> for KeyboardFocusTarget {
|
||||||
key: KeysymHandle<'_>,
|
key: KeysymHandle<'_>,
|
||||||
state: KeyState,
|
state: KeyState,
|
||||||
serial: Serial,
|
serial: Serial,
|
||||||
time: u32,
|
time: InputTime,
|
||||||
) {
|
) {
|
||||||
if let Some(inner) = self.inner_keyboard_target() {
|
if let Some(inner) = self.inner_keyboard_target() {
|
||||||
inner.key(seat, data, key, state, serial, time);
|
inner.key(seat, data, key, state, serial, time);
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,34 @@
|
||||||
use smithay::{
|
use smithay::{
|
||||||
|
backend::input::{InputTime, TabletToolDescriptor},
|
||||||
input::{
|
input::{
|
||||||
Seat, SeatHandler,
|
Seat, SeatHandler,
|
||||||
pointer::{
|
pointer::{
|
||||||
AxisFrame, ButtonEvent, Focus, GestureHoldBeginEvent, GestureHoldEndEvent,
|
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent, Focus,
|
||||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||||
GrabStartData as PointerGrabStartData, MotionEvent as PointerMotionEvent, PointerGrab,
|
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
|
||||||
PointerInnerHandle, RelativeMotionEvent,
|
MotionEvent as PointerMotionEvent, PointerGrab, PointerInnerHandle,
|
||||||
|
RelativeMotionEvent,
|
||||||
|
},
|
||||||
|
tablet::{
|
||||||
|
TabletSeatHandler, TabletSeatTrait,
|
||||||
|
tool::{
|
||||||
|
AxisFrame as TabletAxisFrame, ButtonEvent as TabletButtonEvent,
|
||||||
|
DownEvent as TabletDownEvent, GrabStartData as TabletGrabStartData,
|
||||||
|
MotionEvent as TabletMotionEvent, ProximityInEvent, ProximityOutEvent,
|
||||||
|
TabletToolGrab, TabletToolInnerHandle, UpEvent as TabletUpEvent,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
touch::{
|
touch::{
|
||||||
DownEvent, GrabStartData as TouchGrabStartData, MotionEvent as TouchMotionEvent,
|
DownEvent as TouchDownEvent, GrabStartData as TouchGrabStartData,
|
||||||
OrientationEvent, ShapeEvent, TouchGrab, TouchInnerHandle, UpEvent,
|
MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchGrab,
|
||||||
|
TouchInnerHandle, UpEvent as TouchUpEvent,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
utils::{Logical, Point, SERIAL_COUNTER, Serial},
|
utils::{Logical, Point, SERIAL_COUNTER, Serial},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::state::State;
|
use crate::{shell::grabs::GrabType, state::State};
|
||||||
|
|
||||||
use super::GrabStartData;
|
use super::GrabStartData;
|
||||||
|
|
||||||
|
|
@ -44,10 +56,15 @@ impl<G> DelayGrab<G> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_touch_grab(&self) -> bool {
|
pub fn grab_type(&self) -> GrabType {
|
||||||
match self.start_data {
|
self.start_data.type_()
|
||||||
GrabStartData::Touch(_) => true,
|
}
|
||||||
GrabStartData::Pointer(_) => false,
|
|
||||||
|
pub fn tool(&self) -> Option<&TabletToolDescriptor> {
|
||||||
|
if let GrabStartData::TabletTool { tool, .. } = &self.start_data {
|
||||||
|
Some(tool)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -92,7 +109,7 @@ impl<G: PointerGrab<State>> PointerGrab<State> for DelayGrab<G> {
|
||||||
&mut self,
|
&mut self,
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut PointerInnerHandle<'_, State>,
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
event: &ButtonEvent,
|
event: &PointerButtonEvent,
|
||||||
) {
|
) {
|
||||||
handle.button(data, event);
|
handle.button(data, event);
|
||||||
if handle.current_pressed().is_empty() {
|
if handle.current_pressed().is_empty() {
|
||||||
|
|
@ -104,7 +121,7 @@ impl<G: PointerGrab<State>> PointerGrab<State> for DelayGrab<G> {
|
||||||
&mut self,
|
&mut self,
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut PointerInnerHandle<'_, State>,
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
details: AxisFrame,
|
details: PointerAxisFrame,
|
||||||
) {
|
) {
|
||||||
handle.axis(data, details);
|
handle.axis(data, details);
|
||||||
}
|
}
|
||||||
|
|
@ -201,12 +218,17 @@ impl<G: TouchGrab<State>> TouchGrab<State> for DelayGrab<G> {
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut TouchInnerHandle<'_, State>,
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
focus: Option<(<State as SeatHandler>::TouchFocus, Point<f64, Logical>)>,
|
focus: Option<(<State as SeatHandler>::TouchFocus, Point<f64, Logical>)>,
|
||||||
event: &DownEvent,
|
event: &TouchDownEvent,
|
||||||
) {
|
) {
|
||||||
handle.down(data, focus, event);
|
handle.down(data, focus, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
|
fn up(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
|
event: &TouchUpEvent,
|
||||||
|
) {
|
||||||
handle.up(data, event);
|
handle.up(data, event);
|
||||||
|
|
||||||
if event.slot == TouchGrab::start_data(self).slot {
|
if event.slot == TouchGrab::start_data(self).slot {
|
||||||
|
|
@ -273,3 +295,110 @@ impl<G: TouchGrab<State>> TouchGrab<State> for DelayGrab<G> {
|
||||||
|
|
||||||
fn unset(&mut self, _data: &mut State) {}
|
fn unset(&mut self, _data: &mut State) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<G: TabletToolGrab<State>> TabletToolGrab<State> for DelayGrab<G> {
|
||||||
|
fn start_data(&self) -> &TabletGrabStartData<State> {
|
||||||
|
match &self.start_data {
|
||||||
|
GrabStartData::TabletTool { data, .. } => data,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proximity_out(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &ProximityOutEvent,
|
||||||
|
) {
|
||||||
|
handle.proximity_out(data, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn motion(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
|
||||||
|
event: &TabletMotionEvent,
|
||||||
|
) {
|
||||||
|
handle.motion(data, focus, event);
|
||||||
|
|
||||||
|
let distance = self.start_data.distance(event.location);
|
||||||
|
if distance >= 1.
|
||||||
|
&& let Some(factory) = self.grab_factory.take()
|
||||||
|
{
|
||||||
|
let seat = self.seat.clone();
|
||||||
|
let serial = self.serial.unwrap_or_else(|| SERIAL_COUNTER.next_serial());
|
||||||
|
let time = InputTime::now();
|
||||||
|
let Some(tool) = self.tool().cloned() else {
|
||||||
|
handle.unset_grab(self, data, serial, time, true);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
data.common.event_loop_handle.insert_idle(move |data| {
|
||||||
|
if let Some((grab, focus)) = factory(data) {
|
||||||
|
seat.tablet_seat()
|
||||||
|
.get_tool(&tool)
|
||||||
|
.unwrap()
|
||||||
|
.set_grab(data, grab, time, serial, focus);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn down(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &TabletDownEvent,
|
||||||
|
) {
|
||||||
|
handle.down(data, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn up(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &TabletUpEvent,
|
||||||
|
) {
|
||||||
|
handle.up(data, event);
|
||||||
|
handle.unset_grab(self, data, event.serial, event.time, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &TabletButtonEvent,
|
||||||
|
) {
|
||||||
|
handle.button(data, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn axis(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
frame: TabletAxisFrame,
|
||||||
|
) {
|
||||||
|
handle.axis(data, frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
time: InputTime,
|
||||||
|
) {
|
||||||
|
handle.frame(data, time)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unset(&mut self, _data: &mut State) {}
|
||||||
|
|
||||||
|
fn proximity_in(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
|
||||||
|
event: &ProximityInEvent,
|
||||||
|
) {
|
||||||
|
handle.proximity_in(data, focus, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
use cosmic_settings_config::shortcuts::Action;
|
use cosmic_settings_config::shortcuts::Action;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
input::pointer::MotionEvent, reexports::wayland_server::protocol::wl_surface::WlSurface,
|
backend::input::InputTime,
|
||||||
utils::SERIAL_COUNTER, wayland::seat::WaylandFocus,
|
input::{pointer::MotionEvent, tablet::TabletSeatTrait},
|
||||||
|
reexports::wayland_server::protocol::wl_surface::WlSurface,
|
||||||
|
utils::SERIAL_COUNTER,
|
||||||
|
wayland::seat::WaylandFocus,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -10,7 +13,7 @@ use crate::{
|
||||||
shell::{
|
shell::{
|
||||||
CosmicSurface, PointGlobalExt, Shell,
|
CosmicSurface, PointGlobalExt, Shell,
|
||||||
element::{CosmicMapped, CosmicWindow},
|
element::{CosmicMapped, CosmicWindow},
|
||||||
grabs::ReleaseMode,
|
grabs::{GrabType, ReleaseMode},
|
||||||
},
|
},
|
||||||
state::State,
|
state::State,
|
||||||
utils::{prelude::SeatExt, screenshot::screenshot_window},
|
utils::{prelude::SeatExt, screenshot::screenshot_window},
|
||||||
|
|
@ -352,19 +355,20 @@ pub fn window_items(
|
||||||
|
|
||||||
std::mem::drop(shell);
|
std::mem::drop(shell);
|
||||||
if let Some((grab, focus)) = res {
|
if let Some((grab, focus)) = res {
|
||||||
if grab.is_touch_grab() {
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
seat.get_touch().unwrap().set_grab(
|
match grab.grab_type() {
|
||||||
state,
|
GrabType::Touch => {
|
||||||
grab,
|
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||||
SERIAL_COUNTER.next_serial(),
|
}
|
||||||
)
|
GrabType::Pointer => seat
|
||||||
} else {
|
.get_pointer()
|
||||||
seat.get_pointer().unwrap().set_grab(
|
.unwrap()
|
||||||
state,
|
.set_grab(state, grab, serial, focus),
|
||||||
grab,
|
GrabType::TabletTool => seat
|
||||||
SERIAL_COUNTER.next_serial(),
|
.tablet_seat()
|
||||||
focus,
|
.get_tool(grab.tool().unwrap())
|
||||||
);
|
.unwrap()
|
||||||
|
.set_grab(state, grab, InputTime::now(), serial, focus),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -388,21 +392,29 @@ pub fn window_items(
|
||||||
std::mem::drop(shell);
|
std::mem::drop(shell);
|
||||||
if let Some(((target, loc), (grab, focus))) = res {
|
if let Some(((target, loc), (grab, focus))) = res {
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
if grab.is_touch_grab() {
|
match grab.grab_type() {
|
||||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
GrabType::Touch => {
|
||||||
} else {
|
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||||
let pointer = seat.get_pointer().unwrap();
|
}
|
||||||
pointer.motion(
|
GrabType::TabletTool => seat
|
||||||
state,
|
.tablet_seat()
|
||||||
target,
|
.get_tool(grab.tool().unwrap())
|
||||||
&MotionEvent {
|
.unwrap()
|
||||||
location: loc.as_logical().to_f64(),
|
.set_grab(state, grab, InputTime::now(), serial, focus),
|
||||||
serial,
|
GrabType::Pointer => {
|
||||||
time: state.common.clock.now().as_millis(),
|
let pointer = seat.get_pointer().unwrap();
|
||||||
},
|
pointer.motion(
|
||||||
);
|
state,
|
||||||
pointer.frame(state);
|
target,
|
||||||
pointer.set_grab(state, grab, serial, focus);
|
&MotionEvent {
|
||||||
|
location: loc.as_logical().to_f64(),
|
||||||
|
serial,
|
||||||
|
time: InputTime::now(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
pointer.frame(state);
|
||||||
|
pointer.set_grab(state, grab, serial, focus);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -423,21 +435,30 @@ pub fn window_items(
|
||||||
std::mem::drop(shell);
|
std::mem::drop(shell);
|
||||||
if let Some(((target, loc), (grab, focus))) = res {
|
if let Some(((target, loc), (grab, focus))) = res {
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
if grab.is_touch_grab() {
|
match grab.grab_type() {
|
||||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
GrabType::Touch => {
|
||||||
} else {
|
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||||
let pointer = seat.get_pointer().unwrap();
|
}
|
||||||
pointer.motion(
|
GrabType::Pointer => {
|
||||||
state,
|
let pointer = seat.get_pointer().unwrap();
|
||||||
target,
|
pointer.motion(
|
||||||
&MotionEvent {
|
state,
|
||||||
location: loc.as_logical().to_f64(),
|
target,
|
||||||
serial,
|
&MotionEvent {
|
||||||
time: state.common.clock.now().as_millis(),
|
location: loc.as_logical().to_f64(),
|
||||||
},
|
serial,
|
||||||
);
|
time: InputTime::now(),
|
||||||
pointer.frame(state);
|
},
|
||||||
pointer.set_grab(state, grab, serial, focus);
|
);
|
||||||
|
pointer.frame(state);
|
||||||
|
pointer.set_grab(state, grab, serial, focus);
|
||||||
|
}
|
||||||
|
GrabType::TabletTool => {
|
||||||
|
seat.tablet_seat()
|
||||||
|
.get_tool(grab.tool().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.set_grab(state, grab, InputTime::now(), serial, focus);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -458,21 +479,30 @@ pub fn window_items(
|
||||||
std::mem::drop(shell);
|
std::mem::drop(shell);
|
||||||
if let Some(((target, loc), (grab, focus))) = res {
|
if let Some(((target, loc), (grab, focus))) = res {
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
if grab.is_touch_grab() {
|
match grab.grab_type() {
|
||||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
GrabType::Touch => {
|
||||||
} else {
|
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||||
let pointer = seat.get_pointer().unwrap();
|
}
|
||||||
pointer.motion(
|
GrabType::Pointer => {
|
||||||
state,
|
let pointer = seat.get_pointer().unwrap();
|
||||||
target,
|
pointer.motion(
|
||||||
&MotionEvent {
|
state,
|
||||||
location: loc.as_logical().to_f64(),
|
target,
|
||||||
serial,
|
&MotionEvent {
|
||||||
time: state.common.clock.now().as_millis(),
|
location: loc.as_logical().to_f64(),
|
||||||
},
|
serial,
|
||||||
);
|
time: InputTime::now(),
|
||||||
pointer.frame(state);
|
},
|
||||||
pointer.set_grab(state, grab, serial, focus);
|
);
|
||||||
|
pointer.frame(state);
|
||||||
|
pointer.set_grab(state, grab, serial, focus);
|
||||||
|
}
|
||||||
|
GrabType::TabletTool => {
|
||||||
|
seat.tablet_seat()
|
||||||
|
.get_tool(grab.tool().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.set_grab(state, grab, InputTime::now(), serial, focus);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -493,21 +523,29 @@ pub fn window_items(
|
||||||
std::mem::drop(shell);
|
std::mem::drop(shell);
|
||||||
if let Some(((target, loc), (grab, focus))) = res {
|
if let Some(((target, loc), (grab, focus))) = res {
|
||||||
let serial = SERIAL_COUNTER.next_serial();
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
if grab.is_touch_grab() {
|
match grab.grab_type() {
|
||||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
GrabType::Touch => {
|
||||||
} else {
|
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||||
let pointer = seat.get_pointer().unwrap();
|
}
|
||||||
pointer.motion(
|
GrabType::Pointer => {
|
||||||
state,
|
let pointer = seat.get_pointer().unwrap();
|
||||||
target,
|
pointer.motion(
|
||||||
&MotionEvent {
|
state,
|
||||||
location: loc.as_logical().to_f64(),
|
target,
|
||||||
serial,
|
&MotionEvent {
|
||||||
time: state.common.clock.now().as_millis(),
|
location: loc.as_logical().to_f64(),
|
||||||
},
|
serial,
|
||||||
);
|
time: InputTime::now(),
|
||||||
pointer.frame(state);
|
},
|
||||||
pointer.set_grab(state, grab, serial, focus);
|
);
|
||||||
|
pointer.frame(state);
|
||||||
|
pointer.set_grab(state, grab, serial, focus);
|
||||||
|
}
|
||||||
|
GrabType::TabletTool => seat
|
||||||
|
.tablet_seat()
|
||||||
|
.get_tool(grab.tool().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.set_grab(state, grab, InputTime::now(), serial, focus),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -627,19 +665,20 @@ pub fn fullscreen_items(window: &CosmicSurface, config: &Config) -> impl Iterato
|
||||||
|
|
||||||
std::mem::drop(shell);
|
std::mem::drop(shell);
|
||||||
if let Some((grab, focus)) = res {
|
if let Some((grab, focus)) = res {
|
||||||
if grab.is_touch_grab() {
|
let serial = SERIAL_COUNTER.next_serial();
|
||||||
seat.get_touch().unwrap().set_grab(
|
match grab.grab_type() {
|
||||||
state,
|
GrabType::Touch => {
|
||||||
grab,
|
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||||
SERIAL_COUNTER.next_serial(),
|
}
|
||||||
)
|
GrabType::Pointer => seat
|
||||||
} else {
|
.get_pointer()
|
||||||
seat.get_pointer().unwrap().set_grab(
|
.unwrap()
|
||||||
state,
|
.set_grab(state, grab, serial, focus),
|
||||||
grab,
|
GrabType::TabletTool => seat
|
||||||
SERIAL_COUNTER.next_serial(),
|
.tablet_seat()
|
||||||
focus,
|
.get_tool(grab.tool().unwrap())
|
||||||
);
|
.unwrap()
|
||||||
|
.set_grab(state, grab, InputTime::now(), serial, focus),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,22 +19,33 @@ use cosmic::{
|
||||||
};
|
};
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
input::{ButtonState, TouchSlot},
|
input::{ButtonState, InputTime, TabletToolDescriptor, TouchSlot},
|
||||||
renderer::ImportMem,
|
renderer::ImportMem,
|
||||||
},
|
},
|
||||||
desktop::space::SpaceElement,
|
desktop::space::SpaceElement,
|
||||||
input::{
|
input::{
|
||||||
Seat,
|
Seat,
|
||||||
pointer::{
|
pointer::{
|
||||||
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent,
|
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent,
|
||||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||||
GrabStartData as PointerGrabStartData, MotionEvent as PointerMotionEvent, PointerGrab,
|
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
|
||||||
PointerInnerHandle, PointerTarget, RelativeMotionEvent,
|
MotionEvent as PointerMotionEvent, PointerGrab, PointerInnerHandle, PointerTarget,
|
||||||
|
RelativeMotionEvent,
|
||||||
|
},
|
||||||
|
tablet::{
|
||||||
|
TabletSeatHandler,
|
||||||
|
tool::{
|
||||||
|
AxisFrame as TabletAxisFrame, ButtonEvent as TabletButtonEvent,
|
||||||
|
DownEvent as TabletDownEvent, GrabStartData as TabletGrabStartData,
|
||||||
|
MotionEvent as TabletMotionEvent, ProximityInEvent, ProximityOutEvent,
|
||||||
|
TabletToolGrab, TabletToolInnerHandle, TabletToolTarget, UpEvent as TabletUpEvent,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
touch::{
|
touch::{
|
||||||
DownEvent, GrabStartData as TouchGrabStartData, MotionEvent as TouchMotionEvent,
|
DownEvent as TouchDownEvent, GrabStartData as TouchGrabStartData,
|
||||||
TouchGrab, TouchInnerHandle, TouchTarget, UpEvent,
|
MotionEvent as TouchMotionEvent, TouchGrab, TouchInnerHandle, TouchTarget,
|
||||||
|
UpEvent as TouchUpEvent,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
output::Output,
|
output::Output,
|
||||||
|
|
@ -43,7 +54,7 @@ use smithay::{
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::render::element::AsGlowRenderer,
|
backend::render::element::AsGlowRenderer,
|
||||||
shell::{SeatExt, focus::target::PointerFocusTarget},
|
shell::{SeatExt, focus::target::PointerFocusTarget, grabs::GrabType},
|
||||||
state::State,
|
state::State,
|
||||||
utils::{
|
utils::{
|
||||||
iced::{IcedElement, IcedRenderElement, Program},
|
iced::{IcedElement, IcedRenderElement, Program},
|
||||||
|
|
@ -350,6 +361,7 @@ impl Program for ContextMenu {
|
||||||
position,
|
position,
|
||||||
pointer_entered: false,
|
pointer_entered: false,
|
||||||
touch_entered: None,
|
touch_entered: None,
|
||||||
|
tablet_entered: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -509,6 +521,7 @@ pub struct Element {
|
||||||
position: Point<i32, Global>,
|
position: Point<i32, Global>,
|
||||||
pointer_entered: bool,
|
pointer_entered: bool,
|
||||||
touch_entered: Option<TouchSlot>,
|
touch_entered: Option<TouchSlot>,
|
||||||
|
tablet_entered: Option<TabletToolDescriptor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MenuGrab {
|
pub struct MenuGrab {
|
||||||
|
|
@ -517,6 +530,7 @@ pub struct MenuGrab {
|
||||||
seat: Seat<State>,
|
seat: Seat<State>,
|
||||||
screen_space_relative: Option<Output>,
|
screen_space_relative: Option<Output>,
|
||||||
scale: Arc<Mutex<f64>>,
|
scale: Arc<Mutex<f64>>,
|
||||||
|
last_tablet_idx: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PointerGrab<State> for MenuGrab {
|
impl PointerGrab<State> for MenuGrab {
|
||||||
|
|
@ -565,10 +579,12 @@ impl PointerGrab<State> for MenuGrab {
|
||||||
PointerTarget::motion(&element.iced, &self.seat, state, &new_event);
|
PointerTarget::motion(&element.iced, &self.seat, state, &new_event);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Keep the root menu hovered while a submenu is open
|
||||||
|
let keep_root = usize::from(elements.len() > 1);
|
||||||
elements
|
elements
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.filter(|element| element.pointer_entered)
|
.filter(|element| element.pointer_entered)
|
||||||
.skip(1)
|
.skip(keep_root)
|
||||||
.for_each(|element| {
|
.for_each(|element| {
|
||||||
PointerTarget::leave(
|
PointerTarget::leave(
|
||||||
&element.iced,
|
&element.iced,
|
||||||
|
|
@ -599,7 +615,7 @@ impl PointerGrab<State> for MenuGrab {
|
||||||
&mut self,
|
&mut self,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
handle: &mut PointerInnerHandle<'_, State>,
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
event: &ButtonEvent,
|
event: &PointerButtonEvent,
|
||||||
) {
|
) {
|
||||||
let any_entered = self
|
let any_entered = self
|
||||||
.elements
|
.elements
|
||||||
|
|
@ -633,7 +649,7 @@ impl PointerGrab<State> for MenuGrab {
|
||||||
&mut self,
|
&mut self,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
handle: &mut PointerInnerHandle<'_, State>,
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
details: AxisFrame,
|
details: PointerAxisFrame,
|
||||||
) {
|
) {
|
||||||
handle.axis(state, details);
|
handle.axis(state, details);
|
||||||
}
|
}
|
||||||
|
|
@ -730,7 +746,7 @@ impl TouchGrab<State> for MenuGrab {
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut TouchInnerHandle<'_, State>,
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||||
event: &DownEvent,
|
event: &TouchDownEvent,
|
||||||
) {
|
) {
|
||||||
{
|
{
|
||||||
let mut guard = self.elements.lock().unwrap();
|
let mut guard = self.elements.lock().unwrap();
|
||||||
|
|
@ -758,7 +774,7 @@ impl TouchGrab<State> for MenuGrab {
|
||||||
}) {
|
}) {
|
||||||
let element = &mut elements[i];
|
let element = &mut elements[i];
|
||||||
|
|
||||||
let new_event = DownEvent {
|
let new_event = TouchDownEvent {
|
||||||
slot: event.slot,
|
slot: event.slot,
|
||||||
location: event_location - element.position.as_logical().to_f64(),
|
location: event_location - element.position.as_logical().to_f64(),
|
||||||
serial: event.serial,
|
serial: event.serial,
|
||||||
|
|
@ -773,7 +789,12 @@ impl TouchGrab<State> for MenuGrab {
|
||||||
handle.down(data, None, event);
|
handle.down(data, None, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
|
fn up(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
|
event: &TouchUpEvent,
|
||||||
|
) {
|
||||||
{
|
{
|
||||||
let elements = self.elements.lock().unwrap();
|
let elements = self.elements.lock().unwrap();
|
||||||
for element in elements.iter().filter(|elem| {
|
for element in elements.iter().filter(|elem| {
|
||||||
|
|
@ -849,6 +870,249 @@ impl TouchGrab<State> for MenuGrab {
|
||||||
fn unset(&mut self, _data: &mut State) {}
|
fn unset(&mut self, _data: &mut State) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TabletToolGrab<State> for MenuGrab {
|
||||||
|
fn start_data(&self) -> &TabletGrabStartData<State> {
|
||||||
|
match &self.start_data {
|
||||||
|
GrabStartData::TabletTool { data, .. } => data,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proximity_in(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
_focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
|
||||||
|
event: &ProximityInEvent,
|
||||||
|
) {
|
||||||
|
{
|
||||||
|
let location = if let Some(output) = self.screen_space_relative.as_ref() {
|
||||||
|
if data.common.shell.read().zoom_state().is_some() {
|
||||||
|
event
|
||||||
|
.location
|
||||||
|
.as_global()
|
||||||
|
.to_zoomed(output)
|
||||||
|
.to_global(output)
|
||||||
|
.as_logical()
|
||||||
|
} else {
|
||||||
|
event.location
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
event.location
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut elements = self.elements.lock().unwrap();
|
||||||
|
if let Some(i) = elements.iter().position(|elem| {
|
||||||
|
let mut bbox = elem.iced.bbox();
|
||||||
|
bbox.loc = elem.position.as_logical();
|
||||||
|
|
||||||
|
bbox.contains(location.to_i32_floor())
|
||||||
|
}) {
|
||||||
|
let element = &mut elements[i];
|
||||||
|
|
||||||
|
let new_event = TabletMotionEvent {
|
||||||
|
location: location - element.position.as_logical().to_f64(),
|
||||||
|
serial: event.serial,
|
||||||
|
time: event.time,
|
||||||
|
};
|
||||||
|
|
||||||
|
TabletToolTarget::proximity_in(
|
||||||
|
&element.iced,
|
||||||
|
&self.seat,
|
||||||
|
data,
|
||||||
|
handle.descriptor(),
|
||||||
|
&handle.current_tablet(),
|
||||||
|
event.serial,
|
||||||
|
);
|
||||||
|
element.tablet_entered = Some(handle.descriptor().clone());
|
||||||
|
TabletToolTarget::motion(
|
||||||
|
&element.iced,
|
||||||
|
&self.seat,
|
||||||
|
data,
|
||||||
|
handle.descriptor(),
|
||||||
|
&new_event,
|
||||||
|
);
|
||||||
|
self.last_tablet_idx = Some(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handle.proximity_in(data, None, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proximity_out(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &ProximityOutEvent,
|
||||||
|
) {
|
||||||
|
{
|
||||||
|
let mut elements = self.elements.lock().unwrap();
|
||||||
|
for element in elements.iter_mut().filter(|elem| {
|
||||||
|
elem.tablet_entered
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|tool| tool == handle.descriptor())
|
||||||
|
}) {
|
||||||
|
TabletToolTarget::proximity_out(
|
||||||
|
&element.iced,
|
||||||
|
&self.seat,
|
||||||
|
data,
|
||||||
|
handle.descriptor(),
|
||||||
|
);
|
||||||
|
element.tablet_entered.take();
|
||||||
|
}
|
||||||
|
self.last_tablet_idx.take();
|
||||||
|
}
|
||||||
|
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn motion(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
_focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
|
||||||
|
event: &TabletMotionEvent,
|
||||||
|
) {
|
||||||
|
{
|
||||||
|
let location = if let Some(output) = self.screen_space_relative.as_ref() {
|
||||||
|
if data.common.shell.read().zoom_state().is_some() {
|
||||||
|
event
|
||||||
|
.location
|
||||||
|
.as_global()
|
||||||
|
.to_zoomed(output)
|
||||||
|
.to_global(output)
|
||||||
|
.as_logical()
|
||||||
|
} else {
|
||||||
|
event.location
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
event.location
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut elements = self.elements.lock().unwrap();
|
||||||
|
if let Some(i) = elements.iter().position(|elem| {
|
||||||
|
let mut bbox = elem.iced.bbox();
|
||||||
|
bbox.loc = elem.position.as_logical();
|
||||||
|
|
||||||
|
bbox.contains(location.to_i32_floor())
|
||||||
|
}) {
|
||||||
|
let element = &mut elements[i];
|
||||||
|
|
||||||
|
let new_event = TabletMotionEvent {
|
||||||
|
location: location - element.position.as_logical().to_f64(),
|
||||||
|
serial: event.serial,
|
||||||
|
time: event.time,
|
||||||
|
};
|
||||||
|
|
||||||
|
if element.tablet_entered.is_none() {
|
||||||
|
TabletToolTarget::proximity_in(
|
||||||
|
&element.iced,
|
||||||
|
&self.seat,
|
||||||
|
data,
|
||||||
|
handle.descriptor(),
|
||||||
|
&handle.current_tablet(),
|
||||||
|
event.serial,
|
||||||
|
);
|
||||||
|
element.tablet_entered = Some(handle.descriptor().clone());
|
||||||
|
}
|
||||||
|
TabletToolTarget::motion(
|
||||||
|
&element.iced,
|
||||||
|
&self.seat,
|
||||||
|
data,
|
||||||
|
handle.descriptor(),
|
||||||
|
&new_event,
|
||||||
|
);
|
||||||
|
self.last_tablet_idx = Some(i);
|
||||||
|
} else {
|
||||||
|
let keep_root = usize::from(elements.len() > 1);
|
||||||
|
elements
|
||||||
|
.iter_mut()
|
||||||
|
.filter(|element| element.tablet_entered.is_some())
|
||||||
|
.skip(keep_root)
|
||||||
|
.for_each(|element| {
|
||||||
|
TabletToolTarget::proximity_out(
|
||||||
|
&element.iced,
|
||||||
|
&self.seat,
|
||||||
|
data,
|
||||||
|
handle.descriptor(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
self.last_tablet_idx.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handle.motion(data, None, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn down(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &TabletDownEvent,
|
||||||
|
) {
|
||||||
|
let mut guard = self.elements.lock().unwrap();
|
||||||
|
let elements = &mut *guard;
|
||||||
|
|
||||||
|
if let Some(elem) = self.last_tablet_idx.and_then(|i| elements.get_mut(i))
|
||||||
|
&& elem
|
||||||
|
.tablet_entered
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|desc| desc == handle.descriptor())
|
||||||
|
{
|
||||||
|
TabletToolTarget::down(&elem.iced, &self.seat, data, handle.descriptor(), event);
|
||||||
|
} else {
|
||||||
|
handle.down(data, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn up(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &TabletUpEvent,
|
||||||
|
) {
|
||||||
|
let mut guard = self.elements.lock().unwrap();
|
||||||
|
let elements = &mut *guard;
|
||||||
|
|
||||||
|
if let Some(elem) = self.last_tablet_idx.and_then(|i| elements.get_mut(i))
|
||||||
|
&& elem
|
||||||
|
.tablet_entered
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|desc| desc == handle.descriptor())
|
||||||
|
{
|
||||||
|
TabletToolTarget::up(&elem.iced, &self.seat, data, handle.descriptor(), event);
|
||||||
|
} else {
|
||||||
|
handle.up(data, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &TabletButtonEvent,
|
||||||
|
) {
|
||||||
|
handle.button(data, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn axis(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
frame: TabletAxisFrame,
|
||||||
|
) {
|
||||||
|
handle.axis(data, frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
time: InputTime,
|
||||||
|
) {
|
||||||
|
handle.frame(data, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unset(&mut self, _data: &mut State) {}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct MenuAlignment {
|
pub struct MenuAlignment {
|
||||||
pub x: AxisAlignment,
|
pub x: AxisAlignment,
|
||||||
|
|
@ -1052,6 +1316,7 @@ impl MenuGrab {
|
||||||
position,
|
position,
|
||||||
pointer_entered: false,
|
pointer_entered: false,
|
||||||
touch_entered: None,
|
touch_entered: None,
|
||||||
|
tablet_entered: None,
|
||||||
}]));
|
}]));
|
||||||
|
|
||||||
let scale = Arc::new(Mutex::new(screen_space_relative.unwrap_or(1.)));
|
let scale = Arc::new(Mutex::new(screen_space_relative.unwrap_or(1.)));
|
||||||
|
|
@ -1076,6 +1341,7 @@ impl MenuGrab {
|
||||||
seat: seat.clone(),
|
seat: seat.clone(),
|
||||||
screen_space_relative,
|
screen_space_relative,
|
||||||
scale,
|
scale,
|
||||||
|
last_tablet_idx: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1086,10 +1352,15 @@ impl MenuGrab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_touch_grab(&self) -> bool {
|
pub fn grab_type(&self) -> GrabType {
|
||||||
match self.start_data {
|
self.start_data.type_()
|
||||||
GrabStartData::Touch(_) => true,
|
}
|
||||||
GrabStartData::Pointer(_) => false,
|
|
||||||
|
pub fn tool(&self) -> Option<&TabletToolDescriptor> {
|
||||||
|
if let GrabStartData::TabletTool { tool, .. } = &self.start_data {
|
||||||
|
Some(tool)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,26 @@
|
||||||
use calloop::LoopHandle;
|
use calloop::LoopHandle;
|
||||||
use cosmic_settings_config::shortcuts;
|
use cosmic_settings_config::shortcuts;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
|
backend::input::{InputTime, TabletToolDescriptor},
|
||||||
input::{
|
input::{
|
||||||
Seat,
|
Seat,
|
||||||
pointer::{
|
pointer::{
|
||||||
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent,
|
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent,
|
||||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||||
GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle,
|
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
|
||||||
RelativeMotionEvent,
|
MotionEvent, PointerGrab, PointerInnerHandle, RelativeMotionEvent,
|
||||||
|
},
|
||||||
|
tablet::tool::{
|
||||||
|
AxisFrame as TabletAxisFrame, ButtonEvent as TabletButtonEvent,
|
||||||
|
DownEvent as TabletDownEvent, GrabStartData as TabletGrabStartData,
|
||||||
|
MotionEvent as TabletMotionEvent, ProximityOutEvent, TabletToolGrab,
|
||||||
|
TabletToolInnerHandle, UpEvent as TabletUpEvent,
|
||||||
},
|
},
|
||||||
touch::{
|
touch::{
|
||||||
DownEvent, GrabStartData as TouchGrabStartData, MotionEvent as TouchMotionEvent,
|
DownEvent as TouchDownEvent, GrabStartData as TouchGrabStartData,
|
||||||
OrientationEvent, ShapeEvent, TouchGrab, TouchInnerHandle, UpEvent,
|
MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchGrab,
|
||||||
|
TouchInnerHandle, UpEvent as TouchUpEvent,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
output::Output,
|
output::Output,
|
||||||
|
|
@ -39,6 +47,17 @@ use super::{
|
||||||
pub enum GrabStartData {
|
pub enum GrabStartData {
|
||||||
Touch(TouchGrabStartData<State>),
|
Touch(TouchGrabStartData<State>),
|
||||||
Pointer(PointerGrabStartData<State>),
|
Pointer(PointerGrabStartData<State>),
|
||||||
|
TabletTool {
|
||||||
|
tool: TabletToolDescriptor,
|
||||||
|
data: TabletGrabStartData<State>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum GrabType {
|
||||||
|
Touch,
|
||||||
|
Pointer,
|
||||||
|
TabletTool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GrabStartData {
|
impl GrabStartData {
|
||||||
|
|
@ -46,6 +65,7 @@ impl GrabStartData {
|
||||||
match self {
|
match self {
|
||||||
Self::Touch(touch) => touch.focus.as_ref(),
|
Self::Touch(touch) => touch.focus.as_ref(),
|
||||||
Self::Pointer(pointer) => pointer.focus.as_ref(),
|
Self::Pointer(pointer) => pointer.focus.as_ref(),
|
||||||
|
Self::TabletTool { data, .. } => data.focus.as_ref(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,6 +73,7 @@ impl GrabStartData {
|
||||||
match self {
|
match self {
|
||||||
Self::Touch(touch) => touch.focus = focus,
|
Self::Touch(touch) => touch.focus = focus,
|
||||||
Self::Pointer(pointer) => pointer.focus = focus,
|
Self::Pointer(pointer) => pointer.focus = focus,
|
||||||
|
Self::TabletTool { data, .. } => data.focus = focus,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,6 +81,7 @@ impl GrabStartData {
|
||||||
match self {
|
match self {
|
||||||
Self::Touch(touch) => touch.location,
|
Self::Touch(touch) => touch.location,
|
||||||
Self::Pointer(pointer) => pointer.location,
|
Self::Pointer(pointer) => pointer.location,
|
||||||
|
Self::TabletTool { data, .. } => data.location,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,6 +89,7 @@ impl GrabStartData {
|
||||||
match self {
|
match self {
|
||||||
Self::Touch(touch) => touch.location = location,
|
Self::Touch(touch) => touch.location = location,
|
||||||
Self::Pointer(pointer) => pointer.location = location,
|
Self::Pointer(pointer) => pointer.location = location,
|
||||||
|
Self::TabletTool { data, .. } => data.location = location,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,6 +99,14 @@ impl GrabStartData {
|
||||||
|
|
||||||
((new.x - old.x).powi(2) + (new.y - old.y).powi(2)).sqrt()
|
((new.x - old.x).powi(2) + (new.y - old.y).powi(2)).sqrt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn type_(&self) -> GrabType {
|
||||||
|
match self {
|
||||||
|
Self::Pointer(_) => GrabType::Pointer,
|
||||||
|
Self::Touch(_) => GrabType::Touch,
|
||||||
|
Self::TabletTool { .. } => GrabType::TabletTool,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
|
@ -203,10 +234,17 @@ impl From<ResizeForkGrab> for ResizeGrab {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResizeGrab {
|
impl ResizeGrab {
|
||||||
pub fn is_touch_grab(&self) -> bool {
|
pub fn grab_type(&self) -> GrabType {
|
||||||
match self {
|
match self {
|
||||||
ResizeGrab::Floating(grab) => grab.is_touch_grab(),
|
ResizeGrab::Floating(grab) => grab.grab_type(),
|
||||||
ResizeGrab::Tiling(grab) => grab.is_touch_grab(),
|
ResizeGrab::Tiling(grab) => grab.grab_type(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tool(&self) -> Option<&TabletToolDescriptor> {
|
||||||
|
match self {
|
||||||
|
ResizeGrab::Floating(grab) => grab.tool(),
|
||||||
|
ResizeGrab::Tiling(grab) => grab.tool(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -242,11 +280,11 @@ impl PointerGrab<State> for ResizeGrab {
|
||||||
&mut self,
|
&mut self,
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut PointerInnerHandle<'_, State>,
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
event: &ButtonEvent,
|
event: &PointerButtonEvent,
|
||||||
) {
|
) {
|
||||||
match self {
|
match self {
|
||||||
ResizeGrab::Floating(grab) => grab.button(data, handle, event),
|
ResizeGrab::Floating(grab) => PointerGrab::button(grab, data, handle, event),
|
||||||
ResizeGrab::Tiling(grab) => grab.button(data, handle, event),
|
ResizeGrab::Tiling(grab) => PointerGrab::button(grab, data, handle, event),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -254,11 +292,11 @@ impl PointerGrab<State> for ResizeGrab {
|
||||||
&mut self,
|
&mut self,
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut PointerInnerHandle<'_, State>,
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
details: AxisFrame,
|
details: PointerAxisFrame,
|
||||||
) {
|
) {
|
||||||
match self {
|
match self {
|
||||||
ResizeGrab::Floating(grab) => grab.axis(data, handle, details),
|
ResizeGrab::Floating(grab) => PointerGrab::axis(grab, data, handle, details),
|
||||||
ResizeGrab::Tiling(grab) => grab.axis(data, handle, details),
|
ResizeGrab::Tiling(grab) => PointerGrab::axis(grab, data, handle, details),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -386,7 +424,7 @@ impl TouchGrab<State> for ResizeGrab {
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut TouchInnerHandle<'_, State>,
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||||
event: &DownEvent,
|
event: &TouchDownEvent,
|
||||||
) {
|
) {
|
||||||
match self {
|
match self {
|
||||||
ResizeGrab::Floating(grab) => TouchGrab::down(grab, data, handle, focus, event),
|
ResizeGrab::Floating(grab) => TouchGrab::down(grab, data, handle, focus, event),
|
||||||
|
|
@ -394,7 +432,12 @@ impl TouchGrab<State> for ResizeGrab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
|
fn up(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
|
event: &TouchUpEvent,
|
||||||
|
) {
|
||||||
match self {
|
match self {
|
||||||
ResizeGrab::Floating(grab) => TouchGrab::up(grab, data, handle, event),
|
ResizeGrab::Floating(grab) => TouchGrab::up(grab, data, handle, event),
|
||||||
ResizeGrab::Tiling(grab) => TouchGrab::up(grab, data, handle, event),
|
ResizeGrab::Tiling(grab) => TouchGrab::up(grab, data, handle, event),
|
||||||
|
|
@ -467,6 +510,107 @@ impl TouchGrab<State> for ResizeGrab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TabletToolGrab<State> for ResizeGrab {
|
||||||
|
fn down(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &TabletDownEvent,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
ResizeGrab::Floating(grab) => TabletToolGrab::down(grab, data, handle, event),
|
||||||
|
ResizeGrab::Tiling(grab) => TabletToolGrab::down(grab, data, handle, event),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn up(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &TabletUpEvent,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
ResizeGrab::Floating(grab) => TabletToolGrab::up(grab, data, handle, event),
|
||||||
|
ResizeGrab::Tiling(grab) => TabletToolGrab::up(grab, data, handle, event),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn motion(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||||
|
event: &TabletMotionEvent,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
ResizeGrab::Floating(grab) => TabletToolGrab::motion(grab, data, handle, focus, event),
|
||||||
|
ResizeGrab::Tiling(grab) => TabletToolGrab::motion(grab, data, handle, focus, event),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
serial: InputTime,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
ResizeGrab::Floating(grab) => TabletToolGrab::frame(grab, data, handle, serial),
|
||||||
|
ResizeGrab::Tiling(grab) => TabletToolGrab::frame(grab, data, handle, serial),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &TabletButtonEvent,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
ResizeGrab::Floating(grab) => TabletToolGrab::button(grab, data, handle, event),
|
||||||
|
ResizeGrab::Tiling(grab) => TabletToolGrab::button(grab, data, handle, event),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn axis(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
details: TabletAxisFrame,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
ResizeGrab::Floating(grab) => TabletToolGrab::axis(grab, data, handle, details),
|
||||||
|
ResizeGrab::Tiling(grab) => TabletToolGrab::axis(grab, data, handle, details),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proximity_out(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &ProximityOutEvent,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
ResizeGrab::Floating(grab) => TabletToolGrab::proximity_out(grab, data, handle, event),
|
||||||
|
ResizeGrab::Tiling(grab) => TabletToolGrab::proximity_out(grab, data, handle, event),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_data(&self) -> &TabletGrabStartData<State> {
|
||||||
|
match self {
|
||||||
|
ResizeGrab::Floating(grab) => TabletToolGrab::start_data(grab),
|
||||||
|
ResizeGrab::Tiling(grab) => TabletToolGrab::start_data(grab),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unset(&mut self, data: &mut State) {
|
||||||
|
match self {
|
||||||
|
ResizeGrab::Floating(grab) => TabletToolGrab::unset(grab, data),
|
||||||
|
ResizeGrab::Tiling(grab) => TabletToolGrab::unset(grab, data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub enum MoveGrab {
|
pub enum MoveGrab {
|
||||||
Move(moving::MoveGrab),
|
Move(moving::MoveGrab),
|
||||||
Delayed(delay::DelayGrab<MoveGrab>),
|
Delayed(delay::DelayGrab<MoveGrab>),
|
||||||
|
|
@ -536,10 +680,17 @@ impl MoveGrab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_touch_grab(&self) -> bool {
|
pub fn grab_type(&self) -> GrabType {
|
||||||
match self {
|
match self {
|
||||||
MoveGrab::Move(m) => m.is_touch_grab(),
|
MoveGrab::Move(m) => m.grab_type(),
|
||||||
MoveGrab::Delayed(d) => d.is_touch_grab(),
|
MoveGrab::Delayed(d) => d.grab_type(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tool(&self) -> Option<&TabletToolDescriptor> {
|
||||||
|
match self {
|
||||||
|
MoveGrab::Move(m) => m.tool(),
|
||||||
|
MoveGrab::Delayed(d) => d.tool(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -574,11 +725,11 @@ impl PointerGrab<State> for MoveGrab {
|
||||||
&mut self,
|
&mut self,
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut PointerInnerHandle<'_, State>,
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
event: &ButtonEvent,
|
event: &PointerButtonEvent,
|
||||||
) {
|
) {
|
||||||
match self {
|
match self {
|
||||||
MoveGrab::Move(grab) => grab.button(data, handle, event),
|
MoveGrab::Move(grab) => PointerGrab::button(grab, data, handle, event),
|
||||||
MoveGrab::Delayed(grab) => grab.button(data, handle, event),
|
MoveGrab::Delayed(grab) => PointerGrab::button(grab, data, handle, event),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -586,11 +737,11 @@ impl PointerGrab<State> for MoveGrab {
|
||||||
&mut self,
|
&mut self,
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut PointerInnerHandle<'_, State>,
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
details: AxisFrame,
|
details: PointerAxisFrame,
|
||||||
) {
|
) {
|
||||||
match self {
|
match self {
|
||||||
MoveGrab::Move(grab) => grab.axis(data, handle, details),
|
MoveGrab::Move(grab) => PointerGrab::axis(grab, data, handle, details),
|
||||||
MoveGrab::Delayed(grab) => grab.axis(data, handle, details),
|
MoveGrab::Delayed(grab) => PointerGrab::axis(grab, data, handle, details),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -718,7 +869,7 @@ impl TouchGrab<State> for MoveGrab {
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut TouchInnerHandle<'_, State>,
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||||
event: &DownEvent,
|
event: &TouchDownEvent,
|
||||||
) {
|
) {
|
||||||
match self {
|
match self {
|
||||||
MoveGrab::Move(grab) => TouchGrab::down(grab, data, handle, focus, event),
|
MoveGrab::Move(grab) => TouchGrab::down(grab, data, handle, focus, event),
|
||||||
|
|
@ -726,7 +877,12 @@ impl TouchGrab<State> for MoveGrab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
|
fn up(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
|
event: &TouchUpEvent,
|
||||||
|
) {
|
||||||
match self {
|
match self {
|
||||||
MoveGrab::Move(grab) => TouchGrab::up(grab, data, handle, event),
|
MoveGrab::Move(grab) => TouchGrab::up(grab, data, handle, event),
|
||||||
MoveGrab::Delayed(grab) => TouchGrab::up(grab, data, handle, event),
|
MoveGrab::Delayed(grab) => TouchGrab::up(grab, data, handle, event),
|
||||||
|
|
@ -798,3 +954,104 @@ impl TouchGrab<State> for MoveGrab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TabletToolGrab<State> for MoveGrab {
|
||||||
|
fn down(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &TabletDownEvent,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
MoveGrab::Move(grab) => TabletToolGrab::down(grab, data, handle, event),
|
||||||
|
MoveGrab::Delayed(grab) => TabletToolGrab::down(grab, data, handle, event),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn up(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &TabletUpEvent,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
MoveGrab::Move(grab) => TabletToolGrab::up(grab, data, handle, event),
|
||||||
|
MoveGrab::Delayed(grab) => TabletToolGrab::up(grab, data, handle, event),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn motion(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||||
|
event: &TabletMotionEvent,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
MoveGrab::Move(grab) => TabletToolGrab::motion(grab, data, handle, focus, event),
|
||||||
|
MoveGrab::Delayed(grab) => TabletToolGrab::motion(grab, data, handle, focus, event),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
serial: InputTime,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
MoveGrab::Move(grab) => TabletToolGrab::frame(grab, data, handle, serial),
|
||||||
|
MoveGrab::Delayed(grab) => TabletToolGrab::frame(grab, data, handle, serial),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &TabletButtonEvent,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
MoveGrab::Move(grab) => TabletToolGrab::button(grab, data, handle, event),
|
||||||
|
MoveGrab::Delayed(grab) => TabletToolGrab::button(grab, data, handle, event),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn axis(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
details: TabletAxisFrame,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
MoveGrab::Move(grab) => TabletToolGrab::axis(grab, data, handle, details),
|
||||||
|
MoveGrab::Delayed(grab) => TabletToolGrab::axis(grab, data, handle, details),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proximity_out(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &ProximityOutEvent,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
MoveGrab::Move(grab) => TabletToolGrab::proximity_out(grab, data, handle, event),
|
||||||
|
MoveGrab::Delayed(grab) => TabletToolGrab::proximity_out(grab, data, handle, event),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_data(&self) -> &TabletGrabStartData<State> {
|
||||||
|
match self {
|
||||||
|
MoveGrab::Move(grab) => TabletToolGrab::start_data(grab),
|
||||||
|
MoveGrab::Delayed(grab) => TabletToolGrab::start_data(grab),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unset(&mut self, data: &mut State) {
|
||||||
|
match self {
|
||||||
|
MoveGrab::Move(grab) => TabletToolGrab::unset(grab, data),
|
||||||
|
MoveGrab::Delayed(grab) => TabletToolGrab::unset(grab, data),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use crate::{
|
||||||
CosmicMapped, CosmicSurface, Direction, ManagedLayer,
|
CosmicMapped, CosmicSurface, Direction, ManagedLayer,
|
||||||
element::{CosmicMappedRenderElement, stack_hover::StackHover},
|
element::{CosmicMappedRenderElement, stack_hover::StackHover},
|
||||||
focus::target::{KeyboardFocusTarget, PointerFocusTarget},
|
focus::target::{KeyboardFocusTarget, PointerFocusTarget},
|
||||||
|
grabs::GrabType,
|
||||||
layout::floating::TiledCorners,
|
layout::floating::TiledCorners,
|
||||||
},
|
},
|
||||||
utils::prelude::*,
|
utils::prelude::*,
|
||||||
|
|
@ -20,7 +21,7 @@ use smallvec::SmallVec;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
drm::DrmNode,
|
drm::DrmNode,
|
||||||
input::ButtonState,
|
input::{ButtonState, InputTime, TabletToolDescriptor},
|
||||||
renderer::{
|
renderer::{
|
||||||
ImportAll, ImportMem,
|
ImportAll, ImportMem,
|
||||||
element::{RenderElement, utils::RescaleRenderElement},
|
element::{RenderElement, utils::RescaleRenderElement},
|
||||||
|
|
@ -30,13 +31,27 @@ use smithay::{
|
||||||
input::{
|
input::{
|
||||||
Seat,
|
Seat,
|
||||||
pointer::{
|
pointer::{
|
||||||
AxisFrame, ButtonEvent, CursorIcon, GestureHoldBeginEvent, GestureHoldEndEvent,
|
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent, CursorIcon,
|
||||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||||
GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle,
|
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
|
||||||
|
MotionEvent as PointerMotionEvent, PointerGrab, PointerInnerHandle,
|
||||||
RelativeMotionEvent,
|
RelativeMotionEvent,
|
||||||
},
|
},
|
||||||
touch::{self, GrabStartData as TouchGrabStartData, TouchGrab, TouchInnerHandle},
|
tablet::{
|
||||||
|
TabletSeatHandler,
|
||||||
|
tool::{
|
||||||
|
AxisFrame as TabletAxisFrame, ButtonEvent as TabletButtonEvent,
|
||||||
|
DownEvent as TabletDownEvent, GrabStartData as TabletGrabStartData,
|
||||||
|
MotionEvent as TabletMotionEvent, ProximityInEvent, ProximityOutEvent,
|
||||||
|
TabletToolGrab, TabletToolInnerHandle, UpEvent as TabletUpEvent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
touch::{
|
||||||
|
DownEvent as TouchDownEvent, GrabStartData as TouchGrabStartData,
|
||||||
|
MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchGrab,
|
||||||
|
TouchInnerHandle, UpEvent as TouchUpEvent,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
output::Output,
|
output::Output,
|
||||||
utils::{IsAlive, Logical, Point, Rectangle, SERIAL_COUNTER, Scale},
|
utils::{IsAlive, Logical, Point, Rectangle, SERIAL_COUNTER, Scale},
|
||||||
|
|
@ -503,7 +518,7 @@ impl PointerGrab<State> for MoveGrab {
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
handle: &mut PointerInnerHandle<'_, State>,
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||||
event: &MotionEvent,
|
event: &PointerMotionEvent,
|
||||||
) {
|
) {
|
||||||
self.update_location(state, event.location);
|
self.update_location(state, event.location);
|
||||||
|
|
||||||
|
|
@ -529,7 +544,7 @@ impl PointerGrab<State> for MoveGrab {
|
||||||
&mut self,
|
&mut self,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
handle: &mut PointerInnerHandle<'_, State>,
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
event: &ButtonEvent,
|
event: &PointerButtonEvent,
|
||||||
) {
|
) {
|
||||||
handle.button(state, event);
|
handle.button(state, event);
|
||||||
match self.release {
|
match self.release {
|
||||||
|
|
@ -550,7 +565,7 @@ impl PointerGrab<State> for MoveGrab {
|
||||||
&mut self,
|
&mut self,
|
||||||
state: &mut State,
|
state: &mut State,
|
||||||
handle: &mut PointerInnerHandle<'_, State>,
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
details: AxisFrame,
|
details: PointerAxisFrame,
|
||||||
) {
|
) {
|
||||||
handle.axis(state, details);
|
handle.axis(state, details);
|
||||||
}
|
}
|
||||||
|
|
@ -647,7 +662,7 @@ impl TouchGrab<State> for MoveGrab {
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut TouchInnerHandle<'_, State>,
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||||
event: &touch::DownEvent,
|
event: &TouchDownEvent,
|
||||||
) {
|
) {
|
||||||
handle.down(data, None, event)
|
handle.down(data, None, event)
|
||||||
}
|
}
|
||||||
|
|
@ -656,7 +671,7 @@ impl TouchGrab<State> for MoveGrab {
|
||||||
&mut self,
|
&mut self,
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut TouchInnerHandle<'_, State>,
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
event: &touch::UpEvent,
|
event: &TouchUpEvent,
|
||||||
) {
|
) {
|
||||||
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot {
|
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot {
|
||||||
handle.unset_grab(self, data);
|
handle.unset_grab(self, data);
|
||||||
|
|
@ -670,7 +685,7 @@ impl TouchGrab<State> for MoveGrab {
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut TouchInnerHandle<'_, State>,
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||||
event: &touch::MotionEvent,
|
event: &TouchMotionEvent,
|
||||||
) {
|
) {
|
||||||
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot {
|
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot {
|
||||||
self.update_location(data, event.location);
|
self.update_location(data, event.location);
|
||||||
|
|
@ -691,7 +706,7 @@ impl TouchGrab<State> for MoveGrab {
|
||||||
&mut self,
|
&mut self,
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut TouchInnerHandle<'_, State>,
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
event: &touch::ShapeEvent,
|
event: &ShapeEvent,
|
||||||
) {
|
) {
|
||||||
handle.shape(data, event)
|
handle.shape(data, event)
|
||||||
}
|
}
|
||||||
|
|
@ -700,7 +715,7 @@ impl TouchGrab<State> for MoveGrab {
|
||||||
&mut self,
|
&mut self,
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut TouchInnerHandle<'_, State>,
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
event: &touch::OrientationEvent,
|
event: &OrientationEvent,
|
||||||
) {
|
) {
|
||||||
handle.orientation(data, event)
|
handle.orientation(data, event)
|
||||||
}
|
}
|
||||||
|
|
@ -715,6 +730,98 @@ impl TouchGrab<State> for MoveGrab {
|
||||||
fn unset(&mut self, _data: &mut State) {}
|
fn unset(&mut self, _data: &mut State) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TabletToolGrab<State> for MoveGrab {
|
||||||
|
fn start_data(&self) -> &TabletGrabStartData<State> {
|
||||||
|
match &self.start_data {
|
||||||
|
GrabStartData::TabletTool { data, .. } => data,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proximity_out(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &ProximityOutEvent,
|
||||||
|
) {
|
||||||
|
handle.proximity_out(data, event);
|
||||||
|
handle.unset_grab(self, data, event.serial, event.time, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn motion(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
_focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
|
||||||
|
event: &TabletMotionEvent,
|
||||||
|
) {
|
||||||
|
handle.motion(data, None, event);
|
||||||
|
|
||||||
|
self.update_location(data, event.location);
|
||||||
|
if !self.window.alive() {
|
||||||
|
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn down(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &TabletDownEvent,
|
||||||
|
) {
|
||||||
|
handle.down(data, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn up(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &TabletUpEvent,
|
||||||
|
) {
|
||||||
|
handle.up(data, event);
|
||||||
|
handle.unset_grab(self, data, event.serial, event.time, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &TabletButtonEvent,
|
||||||
|
) {
|
||||||
|
handle.button(data, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn axis(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
frame: TabletAxisFrame,
|
||||||
|
) {
|
||||||
|
handle.axis(data, frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
time: InputTime,
|
||||||
|
) {
|
||||||
|
handle.frame(data, time)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proximity_in(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
|
||||||
|
event: &ProximityInEvent,
|
||||||
|
) {
|
||||||
|
handle.proximity_in(data, focus, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unset(&mut self, _data: &mut State) {}
|
||||||
|
}
|
||||||
|
|
||||||
impl MoveGrab {
|
impl MoveGrab {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
start_data: GrabStartData,
|
start_data: GrabStartData,
|
||||||
|
|
@ -778,10 +885,15 @@ impl MoveGrab {
|
||||||
self.previous == ManagedLayer::Tiling
|
self.previous == ManagedLayer::Tiling
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_touch_grab(&self) -> bool {
|
pub fn grab_type(&self) -> GrabType {
|
||||||
match self.start_data {
|
self.start_data.type_()
|
||||||
GrabStartData::Touch(_) => true,
|
}
|
||||||
GrabStartData::Pointer(_) => false,
|
|
||||||
|
pub fn tool(&self) -> Option<&TabletToolDescriptor> {
|
||||||
|
if let GrabStartData::TabletTool { tool, .. } = &self.start_data {
|
||||||
|
Some(tool)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -793,7 +905,7 @@ impl Drop for MoveGrab {
|
||||||
let seat = self.seat.clone();
|
let seat = self.seat.clone();
|
||||||
// false-positive: `Output`s hash is based on it's inner ptr
|
// false-positive: `Output`s hash is based on it's inner ptr
|
||||||
#[allow(clippy::mutable_key_type)]
|
#[allow(clippy::mutable_key_type)]
|
||||||
let window_outputs = self.window_outputs.drain().collect::<HashSet<_>>();
|
let window_outputs = std::mem::take(&mut self.window_outputs);
|
||||||
let previous = self.previous;
|
let previous = self.previous;
|
||||||
let window = self.window.clone();
|
let window = self.window.clone();
|
||||||
let is_touch_grab = matches!(self.start_data, GrabStartData::Touch(_));
|
let is_touch_grab = matches!(self.start_data, GrabStartData::Touch(_));
|
||||||
|
|
@ -953,10 +1065,10 @@ impl Drop for MoveGrab {
|
||||||
position.as_logical().to_f64() - window.geometry().loc.to_f64()
|
position.as_logical().to_f64() - window.geometry().loc.to_f64()
|
||||||
+ offset,
|
+ offset,
|
||||||
)),
|
)),
|
||||||
&MotionEvent {
|
&PointerMotionEvent {
|
||||||
location: pointer.current_location(),
|
location: pointer.current_location(),
|
||||||
serial,
|
serial,
|
||||||
time: state.common.clock.now().as_millis(),
|
time: InputTime::now(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,25 +7,36 @@ use crate::{
|
||||||
shell::{
|
shell::{
|
||||||
element::CosmicMapped,
|
element::CosmicMapped,
|
||||||
focus::target::PointerFocusTarget,
|
focus::target::PointerFocusTarget,
|
||||||
grabs::{GrabStartData, ReleaseMode, ResizeEdge},
|
grabs::{GrabStartData, GrabType, ReleaseMode, ResizeEdge},
|
||||||
},
|
},
|
||||||
utils::prelude::*,
|
utils::prelude::*,
|
||||||
};
|
};
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::input::ButtonState,
|
backend::input::{ButtonState, InputTime, TabletToolDescriptor},
|
||||||
desktop::{WindowSurface, space::SpaceElement},
|
desktop::{WindowSurface, space::SpaceElement},
|
||||||
input::{
|
input::{
|
||||||
Seat,
|
Seat,
|
||||||
pointer::{
|
pointer::{
|
||||||
AxisFrame, ButtonEvent, CursorIcon, GestureHoldBeginEvent, GestureHoldEndEvent,
|
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent, CursorIcon,
|
||||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||||
GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle,
|
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
|
||||||
|
MotionEvent as PointerMotionEvent, PointerGrab, PointerInnerHandle,
|
||||||
RelativeMotionEvent,
|
RelativeMotionEvent,
|
||||||
},
|
},
|
||||||
|
tablet::{
|
||||||
|
TabletSeatHandler,
|
||||||
|
tool::{
|
||||||
|
AxisFrame as TabletAxisFrame, ButtonEvent as TabletButtonEvent,
|
||||||
|
DownEvent as TabletDownEvent, GrabStartData as TabletGrabStartData,
|
||||||
|
MotionEvent as TabletMotionEvent, ProximityInEvent, ProximityOutEvent,
|
||||||
|
TabletToolGrab, TabletToolInnerHandle, UpEvent as TabletUpEvent,
|
||||||
|
},
|
||||||
|
},
|
||||||
touch::{
|
touch::{
|
||||||
DownEvent, GrabStartData as TouchGrabStartData, MotionEvent as TouchMotionEvent,
|
DownEvent as TouchDownEvent, GrabStartData as TouchGrabStartData,
|
||||||
OrientationEvent, ShapeEvent, TouchGrab, TouchInnerHandle, UpEvent,
|
MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchGrab,
|
||||||
|
TouchInnerHandle, UpEvent as TouchUpEvent,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
output::Output,
|
output::Output,
|
||||||
|
|
@ -170,10 +181,19 @@ impl ResizeSurfaceGrab {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_touch_grab(&self) -> bool {
|
pub fn grab_type(&self) -> GrabType {
|
||||||
match self.start_data {
|
match self.start_data {
|
||||||
GrabStartData::Touch(_) => true,
|
GrabStartData::Pointer(_) => GrabType::Pointer,
|
||||||
GrabStartData::Pointer(_) => false,
|
GrabStartData::Touch(_) => GrabType::Touch,
|
||||||
|
GrabStartData::TabletTool { .. } => GrabType::TabletTool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tool(&self) -> Option<&TabletToolDescriptor> {
|
||||||
|
if let GrabStartData::TabletTool { tool, .. } = &self.start_data {
|
||||||
|
Some(tool)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -184,7 +204,7 @@ impl PointerGrab<State> for ResizeSurfaceGrab {
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut PointerInnerHandle<'_, State>,
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||||
event: &MotionEvent,
|
event: &PointerMotionEvent,
|
||||||
) {
|
) {
|
||||||
// While the grab is active, no client has pointer focus
|
// While the grab is active, no client has pointer focus
|
||||||
handle.motion(data, None, event);
|
handle.motion(data, None, event);
|
||||||
|
|
@ -209,7 +229,7 @@ impl PointerGrab<State> for ResizeSurfaceGrab {
|
||||||
&mut self,
|
&mut self,
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut PointerInnerHandle<'_, State>,
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
event: &ButtonEvent,
|
event: &PointerButtonEvent,
|
||||||
) {
|
) {
|
||||||
handle.button(data, event);
|
handle.button(data, event);
|
||||||
match self.release {
|
match self.release {
|
||||||
|
|
@ -230,7 +250,7 @@ impl PointerGrab<State> for ResizeSurfaceGrab {
|
||||||
&mut self,
|
&mut self,
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut PointerInnerHandle<'_, State>,
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
details: AxisFrame,
|
details: PointerAxisFrame,
|
||||||
) {
|
) {
|
||||||
handle.axis(data, details)
|
handle.axis(data, details)
|
||||||
}
|
}
|
||||||
|
|
@ -329,12 +349,17 @@ impl TouchGrab<State> for ResizeSurfaceGrab {
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut TouchInnerHandle<'_, State>,
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||||
event: &DownEvent,
|
event: &TouchDownEvent,
|
||||||
) {
|
) {
|
||||||
handle.down(data, None, event)
|
handle.down(data, None, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
|
fn up(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
|
event: &TouchUpEvent,
|
||||||
|
) {
|
||||||
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot {
|
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot {
|
||||||
handle.unset_grab(self, data);
|
handle.unset_grab(self, data);
|
||||||
}
|
}
|
||||||
|
|
@ -396,6 +421,99 @@ impl TouchGrab<State> for ResizeSurfaceGrab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TabletToolGrab<State> for ResizeSurfaceGrab {
|
||||||
|
fn start_data(&self) -> &TabletGrabStartData<State> {
|
||||||
|
match &self.start_data {
|
||||||
|
GrabStartData::TabletTool { data, .. } => data,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proximity_out(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &ProximityOutEvent,
|
||||||
|
) {
|
||||||
|
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||||
|
handle.proximity_out(data, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn motion(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
_focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
|
||||||
|
event: &TabletMotionEvent,
|
||||||
|
) {
|
||||||
|
if self.update_location(event.location.as_global()) {
|
||||||
|
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
handle.motion(data, None, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn down(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &TabletDownEvent,
|
||||||
|
) {
|
||||||
|
handle.down(data, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn up(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &TabletUpEvent,
|
||||||
|
) {
|
||||||
|
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||||
|
handle.up(data, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &TabletButtonEvent,
|
||||||
|
) {
|
||||||
|
handle.button(data, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn axis(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
frame: TabletAxisFrame,
|
||||||
|
) {
|
||||||
|
handle.axis(data, frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
time: InputTime,
|
||||||
|
) {
|
||||||
|
handle.frame(data, time)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unset(&mut self, _data: &mut State) {
|
||||||
|
self.ungrab()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proximity_in(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
_focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
|
||||||
|
event: &ProximityInEvent,
|
||||||
|
) {
|
||||||
|
handle.proximity_in(data, None, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ResizeGrabMarker(AtomicBool);
|
pub struct ResizeGrabMarker(AtomicBool);
|
||||||
|
|
||||||
impl ResizeGrabMarker {
|
impl ResizeGrabMarker {
|
||||||
|
|
|
||||||
|
|
@ -4,31 +4,42 @@ use crate::{
|
||||||
backend::render::cursor::CursorState,
|
backend::render::cursor::CursorState,
|
||||||
shell::{
|
shell::{
|
||||||
focus::target::PointerFocusTarget,
|
focus::target::PointerFocusTarget,
|
||||||
grabs::{GrabStartData, ReleaseMode},
|
grabs::{GrabStartData, GrabType, ReleaseMode},
|
||||||
layout::Orientation,
|
layout::Orientation,
|
||||||
},
|
},
|
||||||
utils::prelude::*,
|
utils::prelude::*,
|
||||||
};
|
};
|
||||||
use id_tree::{NodeId, Tree};
|
use id_tree::{NodeId, Tree};
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::input::ButtonState,
|
backend::input::{ButtonState, InputTime, TabletToolDescriptor},
|
||||||
input::{
|
input::{
|
||||||
Seat,
|
Seat,
|
||||||
pointer::{
|
pointer::{
|
||||||
AxisFrame, ButtonEvent, CursorIcon, Focus, GestureHoldBeginEvent, GestureHoldEndEvent,
|
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent, CursorIcon, Focus,
|
||||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||||
GrabStartData as PointerGrabStartData, MotionEvent, PointerGrab, PointerInnerHandle,
|
GestureSwipeEndEvent, GestureSwipeUpdateEvent, GrabStartData as PointerGrabStartData,
|
||||||
PointerTarget, RelativeMotionEvent,
|
MotionEvent as PointerMotionEvent, PointerGrab, PointerInnerHandle, PointerTarget,
|
||||||
|
RelativeMotionEvent,
|
||||||
|
},
|
||||||
|
tablet::{
|
||||||
|
Tablet, TabletSeatHandler, TabletSeatTrait,
|
||||||
|
tool::{
|
||||||
|
AxisFrame as TabletAxisFrame, ButtonEvent as TabletButtonEvent,
|
||||||
|
DownEvent as TabletDownEvent, GrabStartData as TabletToolGrabStartData,
|
||||||
|
GrabTrigger as TabletToolGrabTrigger, MotionEvent as TabletMotionEvent,
|
||||||
|
ProximityInEvent, ProximityOutEvent, TabletToolGrab, TabletToolInnerHandle,
|
||||||
|
TabletToolTarget, UpEvent as TabletUpEvent,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
touch::{
|
touch::{
|
||||||
DownEvent, FrameMarker, GrabStartData as TouchGrabStartData,
|
DownEvent as TouchDownEvent, FrameMarker, GrabStartData as TouchGrabStartData,
|
||||||
MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchGrab,
|
MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent, TouchGrab,
|
||||||
TouchInnerHandle, TouchTarget, UpEvent,
|
TouchInnerHandle, TouchTarget, UpEvent as TouchUpEvent,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
output::WeakOutput,
|
output::WeakOutput,
|
||||||
utils::{IsAlive, Logical, Point},
|
utils::{IsAlive, Logical, Point, Serial},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::super::{Data, TilingLayout};
|
use super::super::{Data, TilingLayout};
|
||||||
|
|
@ -48,7 +59,7 @@ impl IsAlive for ResizeForkTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PointerTarget<State> for ResizeForkTarget {
|
impl PointerTarget<State> for ResizeForkTarget {
|
||||||
fn enter(&self, seat: &Seat<State>, _data: &mut State, _event: &MotionEvent) {
|
fn enter(&self, seat: &Seat<State>, _data: &mut State, _event: &PointerMotionEvent) {
|
||||||
let user_data = seat.user_data();
|
let user_data = seat.user_data();
|
||||||
let cursor_state = user_data.get::<CursorState>().unwrap();
|
let cursor_state = user_data.get::<CursorState>().unwrap();
|
||||||
cursor_state
|
cursor_state
|
||||||
|
|
@ -60,19 +71,13 @@ impl PointerTarget<State> for ResizeForkTarget {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn leave(
|
fn leave(&self, seat: &Seat<State>, _data: &mut State, _serial: Serial, _time: InputTime) {
|
||||||
&self,
|
|
||||||
seat: &Seat<State>,
|
|
||||||
_data: &mut State,
|
|
||||||
_serial: smithay::utils::Serial,
|
|
||||||
_time: u32,
|
|
||||||
) {
|
|
||||||
let user_data = seat.user_data();
|
let user_data = seat.user_data();
|
||||||
let cursor_state = user_data.get::<CursorState>().unwrap();
|
let cursor_state = user_data.get::<CursorState>().unwrap();
|
||||||
cursor_state.lock().unwrap().unset_shape();
|
cursor_state.lock().unwrap().unset_shape();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {
|
fn button(&self, seat: &Seat<State>, data: &mut State, event: &PointerButtonEvent) {
|
||||||
if event.button == 0x110 && event.state == ButtonState::Pressed {
|
if event.button == 0x110 && event.state == ButtonState::Pressed {
|
||||||
let seat = seat.clone();
|
let seat = seat.clone();
|
||||||
let node = self.node.clone();
|
let node = self.node.clone();
|
||||||
|
|
@ -106,7 +111,7 @@ impl PointerTarget<State> for ResizeForkTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn motion(&self, _seat: &Seat<State>, _data: &mut State, _event: &MotionEvent) {}
|
fn motion(&self, _seat: &Seat<State>, _data: &mut State, _event: &PointerMotionEvent) {}
|
||||||
fn relative_motion(
|
fn relative_motion(
|
||||||
&self,
|
&self,
|
||||||
_seat: &Seat<State>,
|
_seat: &Seat<State>,
|
||||||
|
|
@ -114,7 +119,7 @@ impl PointerTarget<State> for ResizeForkTarget {
|
||||||
_event: &RelativeMotionEvent,
|
_event: &RelativeMotionEvent,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
fn axis(&self, _seat: &Seat<State>, _data: &mut State, _frame: AxisFrame) {}
|
fn axis(&self, _seat: &Seat<State>, _data: &mut State, _frame: PointerAxisFrame) {}
|
||||||
fn frame(&self, _seat: &Seat<State>, _data: &mut State) {}
|
fn frame(&self, _seat: &Seat<State>, _data: &mut State) {}
|
||||||
fn gesture_swipe_begin(&self, _: &Seat<State>, _: &mut State, _: &GestureSwipeBeginEvent) {}
|
fn gesture_swipe_begin(&self, _: &Seat<State>, _: &mut State, _: &GestureSwipeBeginEvent) {}
|
||||||
fn gesture_swipe_update(&self, _: &Seat<State>, _: &mut State, _: &GestureSwipeUpdateEvent) {}
|
fn gesture_swipe_update(&self, _: &Seat<State>, _: &mut State, _: &GestureSwipeUpdateEvent) {}
|
||||||
|
|
@ -127,7 +132,7 @@ impl PointerTarget<State> for ResizeForkTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TouchTarget<State> for ResizeForkTarget {
|
impl TouchTarget<State> for ResizeForkTarget {
|
||||||
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {
|
fn down(&self, seat: &Seat<State>, data: &mut State, event: &TouchDownEvent) {
|
||||||
let seat = seat.clone();
|
let seat = seat.clone();
|
||||||
let node = self.node.clone();
|
let node = self.node.clone();
|
||||||
let output = self.output.clone();
|
let output = self.output.clone();
|
||||||
|
|
@ -158,7 +163,7 @@ impl TouchTarget<State> for ResizeForkTarget {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn up(&self, _seat: &Seat<State>, _data: &mut State, _event: &UpEvent) {}
|
fn up(&self, _seat: &Seat<State>, _data: &mut State, _event: &TouchUpEvent) {}
|
||||||
fn motion(&self, _seat: &Seat<State>, _data: &mut State, _event: &TouchMotionEvent) {}
|
fn motion(&self, _seat: &Seat<State>, _data: &mut State, _event: &TouchMotionEvent) {}
|
||||||
fn frame(&self, _seat: &Seat<State>, _data: &mut State, _frame: FrameMarker) {}
|
fn frame(&self, _seat: &Seat<State>, _data: &mut State, _frame: FrameMarker) {}
|
||||||
fn cancel(&self, _seat: &Seat<State>, _data: &mut State, _frame: FrameMarker) {}
|
fn cancel(&self, _seat: &Seat<State>, _data: &mut State, _frame: FrameMarker) {}
|
||||||
|
|
@ -169,6 +174,121 @@ impl TouchTarget<State> for ResizeForkTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TabletToolTarget<State> for ResizeForkTarget {
|
||||||
|
fn down(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
event: &TabletDownEvent,
|
||||||
|
) {
|
||||||
|
let seat = seat.clone();
|
||||||
|
let node = self.node.clone();
|
||||||
|
let output = self.output.clone();
|
||||||
|
let left_up_idx = self.left_up_idx;
|
||||||
|
let orientation = self.orientation;
|
||||||
|
let serial = event.serial;
|
||||||
|
let time = event.time;
|
||||||
|
let tool = tool_descriptor.clone();
|
||||||
|
data.common.event_loop_handle.insert_idle(move |state| {
|
||||||
|
let pointer = seat.get_pointer().unwrap();
|
||||||
|
let location = pointer.current_location();
|
||||||
|
let tablet = seat.tablet_seat().get_tool(&tool).unwrap();
|
||||||
|
tablet.set_grab(
|
||||||
|
state,
|
||||||
|
ResizeForkGrab::new(
|
||||||
|
GrabStartData::TabletTool {
|
||||||
|
tool,
|
||||||
|
data: TabletToolGrabStartData {
|
||||||
|
focus: None,
|
||||||
|
location,
|
||||||
|
trigger: TabletToolGrabTrigger::Tip,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
location.as_global(),
|
||||||
|
node,
|
||||||
|
left_up_idx,
|
||||||
|
orientation,
|
||||||
|
output,
|
||||||
|
ReleaseMode::NoMouseButtons,
|
||||||
|
),
|
||||||
|
time,
|
||||||
|
serial,
|
||||||
|
Focus::Keep,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fn motion(
|
||||||
|
&self,
|
||||||
|
_seat: &Seat<State>,
|
||||||
|
_data: &mut State,
|
||||||
|
_tool_descriptor: &TabletToolDescriptor,
|
||||||
|
_event: &TabletMotionEvent,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
fn proximity_out(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
_data: &mut State,
|
||||||
|
_tool_descriptor: &TabletToolDescriptor,
|
||||||
|
) {
|
||||||
|
let user_data = seat.user_data();
|
||||||
|
let cursor_state = user_data.get::<CursorState>().unwrap();
|
||||||
|
cursor_state.lock().unwrap().unset_shape();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proximity_in(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
_data: &mut State,
|
||||||
|
_tool_descriptor: &TabletToolDescriptor,
|
||||||
|
_tablet: &Tablet,
|
||||||
|
_serial: Serial,
|
||||||
|
) {
|
||||||
|
let user_data = seat.user_data();
|
||||||
|
let cursor_state = user_data.get::<CursorState>().unwrap();
|
||||||
|
cursor_state
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.set_shape(match self.orientation {
|
||||||
|
Orientation::Horizontal => CursorIcon::RowResize,
|
||||||
|
Orientation::Vertical => CursorIcon::ColResize,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fn up(
|
||||||
|
&self,
|
||||||
|
_seat: &Seat<State>,
|
||||||
|
_data: &mut State,
|
||||||
|
_tool_descriptor: &TabletToolDescriptor,
|
||||||
|
_event: &TabletUpEvent,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
fn axis(
|
||||||
|
&self,
|
||||||
|
_seat: &Seat<State>,
|
||||||
|
_data: &mut State,
|
||||||
|
_tool_descriptor: &TabletToolDescriptor,
|
||||||
|
_frame: TabletAxisFrame,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
fn button(
|
||||||
|
&self,
|
||||||
|
_seat: &Seat<State>,
|
||||||
|
_data: &mut State,
|
||||||
|
_tool_descriptor: &TabletToolDescriptor,
|
||||||
|
_event: &TabletButtonEvent,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
fn frame(
|
||||||
|
&self,
|
||||||
|
_seat: &Seat<State>,
|
||||||
|
_data: &mut State,
|
||||||
|
_tool_descriptor: &TabletToolDescriptor,
|
||||||
|
_time: InputTime,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ResizeForkGrab {
|
pub struct ResizeForkGrab {
|
||||||
start_data: GrabStartData,
|
start_data: GrabStartData,
|
||||||
last_loc: Point<f64, Global>,
|
last_loc: Point<f64, Global>,
|
||||||
|
|
@ -338,10 +458,19 @@ impl ResizeForkGrab {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_touch_grab(&self) -> bool {
|
pub fn grab_type(&self) -> GrabType {
|
||||||
match self.start_data {
|
match self.start_data {
|
||||||
GrabStartData::Touch(_) => true,
|
GrabStartData::Pointer(_) => GrabType::Pointer,
|
||||||
GrabStartData::Pointer(_) => false,
|
GrabStartData::Touch(_) => GrabType::Touch,
|
||||||
|
GrabStartData::TabletTool { .. } => GrabType::TabletTool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tool(&self) -> Option<&TabletToolDescriptor> {
|
||||||
|
if let GrabStartData::TabletTool { tool, .. } = &self.start_data {
|
||||||
|
Some(tool)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -352,7 +481,7 @@ impl PointerGrab<State> for ResizeForkGrab {
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut PointerInnerHandle<'_, State>,
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||||
event: &MotionEvent,
|
event: &PointerMotionEvent,
|
||||||
) {
|
) {
|
||||||
// While the grab is active, no client has pointer focus
|
// While the grab is active, no client has pointer focus
|
||||||
handle.motion(data, None, event);
|
handle.motion(data, None, event);
|
||||||
|
|
@ -377,7 +506,7 @@ impl PointerGrab<State> for ResizeForkGrab {
|
||||||
&mut self,
|
&mut self,
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut PointerInnerHandle<'_, State>,
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
event: &ButtonEvent,
|
event: &PointerButtonEvent,
|
||||||
) {
|
) {
|
||||||
handle.button(data, event);
|
handle.button(data, event);
|
||||||
match self.release {
|
match self.release {
|
||||||
|
|
@ -398,7 +527,7 @@ impl PointerGrab<State> for ResizeForkGrab {
|
||||||
&mut self,
|
&mut self,
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut PointerInnerHandle<'_, State>,
|
handle: &mut PointerInnerHandle<'_, State>,
|
||||||
details: AxisFrame,
|
details: PointerAxisFrame,
|
||||||
) {
|
) {
|
||||||
handle.axis(data, details)
|
handle.axis(data, details)
|
||||||
}
|
}
|
||||||
|
|
@ -497,12 +626,17 @@ impl TouchGrab<State> for ResizeForkGrab {
|
||||||
data: &mut State,
|
data: &mut State,
|
||||||
handle: &mut TouchInnerHandle<'_, State>,
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
_focus: Option<(PointerFocusTarget, Point<f64, Logical>)>,
|
||||||
event: &DownEvent,
|
event: &TouchDownEvent,
|
||||||
) {
|
) {
|
||||||
handle.down(data, None, event)
|
handle.down(data, None, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn up(&mut self, data: &mut State, handle: &mut TouchInnerHandle<'_, State>, event: &UpEvent) {
|
fn up(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TouchInnerHandle<'_, State>,
|
||||||
|
event: &TouchUpEvent,
|
||||||
|
) {
|
||||||
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot {
|
if event.slot == <Self as TouchGrab<State>>::start_data(self).slot {
|
||||||
handle.unset_grab(self, data);
|
handle.unset_grab(self, data);
|
||||||
}
|
}
|
||||||
|
|
@ -563,3 +697,96 @@ impl TouchGrab<State> for ResizeForkGrab {
|
||||||
self.update_location(data, self.last_loc.as_logical(), true);
|
self.update_location(data, self.last_loc.as_logical(), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TabletToolGrab<State> for ResizeForkGrab {
|
||||||
|
fn start_data(&self) -> &TabletToolGrabStartData<State> {
|
||||||
|
match &self.start_data {
|
||||||
|
GrabStartData::TabletTool { data, .. } => data,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proximity_out(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &ProximityOutEvent,
|
||||||
|
) {
|
||||||
|
handle.proximity_out(data, event);
|
||||||
|
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn motion(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
_focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
|
||||||
|
event: &TabletMotionEvent,
|
||||||
|
) {
|
||||||
|
handle.motion(data, None, event);
|
||||||
|
|
||||||
|
if self.update_location(data, event.location, false) {
|
||||||
|
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn down(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &TabletDownEvent,
|
||||||
|
) {
|
||||||
|
handle.down(data, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn up(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &TabletUpEvent,
|
||||||
|
) {
|
||||||
|
handle.up(data, event);
|
||||||
|
handle.unset_grab(self, data, event.serial, event.time, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
event: &TabletButtonEvent,
|
||||||
|
) {
|
||||||
|
handle.button(data, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn axis(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
frame: TabletAxisFrame,
|
||||||
|
) {
|
||||||
|
handle.axis(data, frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
time: InputTime,
|
||||||
|
) {
|
||||||
|
handle.frame(data, time)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unset(&mut self, data: &mut State) {
|
||||||
|
self.update_location(data, self.last_loc.as_logical(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proximity_in(
|
||||||
|
&mut self,
|
||||||
|
data: &mut State,
|
||||||
|
handle: &mut TabletToolInnerHandle<'_, State>,
|
||||||
|
focus: Option<(<State as TabletSeatHandler>::ToolFocus, Point<f64, Logical>)>,
|
||||||
|
event: &ProximityInEvent,
|
||||||
|
) {
|
||||||
|
handle.proximity_in(data, focus, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use cosmic_settings_config::shortcuts;
|
use cosmic_settings_config::shortcuts;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::input::{KeyState, Keycode},
|
backend::input::{InputTime, KeyState, Keycode},
|
||||||
input::{
|
input::{
|
||||||
Seat, SeatHandler,
|
Seat, SeatHandler,
|
||||||
keyboard::{
|
keyboard::{
|
||||||
|
|
@ -38,7 +38,7 @@ impl KeyboardGrab<State> for SwapWindowGrab {
|
||||||
state: KeyState,
|
state: KeyState,
|
||||||
modifiers: Option<ModifiersState>,
|
modifiers: Option<ModifiersState>,
|
||||||
serial: Serial,
|
serial: Serial,
|
||||||
time: u32,
|
time: InputTime,
|
||||||
) {
|
) {
|
||||||
if !matches!(&data.common.shell.read().overview_mode.active_trigger(), Some(Trigger::KeyboardSwap(_, d)) if d == &self.desc)
|
if !matches!(&data.common.shell.read().overview_mode.active_trigger(), Some(Trigger::KeyboardSwap(_, d)) if d == &self.desc)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3271,12 +3271,34 @@ impl TilingLayout {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let (_, inner) = self.gaps();
|
||||||
let mut result = None;
|
let mut result = None;
|
||||||
|
let mut fork = None;
|
||||||
let mut lookup = Some(root.clone());
|
let mut lookup = Some(root.clone());
|
||||||
while let Some(node) = lookup {
|
while let Some(node) = lookup {
|
||||||
let data = tree.get(&node).unwrap().data();
|
let data = tree.get(&node).unwrap().data();
|
||||||
if data.geometry().contains(location) {
|
if data.geometry().contains(location) {
|
||||||
result = Some(node.clone());
|
result = Some(node.clone());
|
||||||
|
if let Data::Group {
|
||||||
|
orientation,
|
||||||
|
sizes,
|
||||||
|
last_geometry,
|
||||||
|
..
|
||||||
|
} = data
|
||||||
|
{
|
||||||
|
let (pos, mut split) = match orientation {
|
||||||
|
Orientation::Vertical => (location.x, last_geometry.loc.x),
|
||||||
|
Orientation::Horizontal => (location.y, last_geometry.loc.y),
|
||||||
|
};
|
||||||
|
for (idx, size) in
|
||||||
|
sizes.iter().take(sizes.len().saturating_sub(1)).enumerate()
|
||||||
|
{
|
||||||
|
split += size;
|
||||||
|
if (pos - split).abs() <= inner / 2 {
|
||||||
|
fork = Some((node.clone(), idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lookup = None;
|
lookup = None;
|
||||||
|
|
@ -3323,25 +3345,16 @@ impl TilingLayout {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Some((
|
Some((_, Data::Group { .. })) => {
|
||||||
id,
|
let (id, idx) = fork?;
|
||||||
Data::Group {
|
let Data::Group {
|
||||||
orientation,
|
orientation,
|
||||||
last_geometry,
|
last_geometry,
|
||||||
..
|
..
|
||||||
},
|
} = tree.get(&id).unwrap().data().clone()
|
||||||
)) => {
|
else {
|
||||||
let idx = tree
|
unreachable!()
|
||||||
.children(&id)
|
};
|
||||||
.unwrap()
|
|
||||||
.position(|node| {
|
|
||||||
let data = node.data();
|
|
||||||
match orientation {
|
|
||||||
Orientation::Vertical => location.x < data.geometry().loc.x,
|
|
||||||
Orientation::Horizontal => location.y < data.geometry().loc.y,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.and_then(|x| x.checked_sub(1))?;
|
|
||||||
Some((
|
Some((
|
||||||
ResizeForkTarget {
|
ResizeForkTarget {
|
||||||
node: id.clone(),
|
node: id.clone(),
|
||||||
|
|
@ -3381,7 +3394,7 @@ impl TilingLayout {
|
||||||
let Some(root) = tree.root_node_id() else {
|
let Some(root) = tree.root_node_id() else {
|
||||||
if matches!(
|
if matches!(
|
||||||
overview.active_trigger(),
|
overview.active_trigger(),
|
||||||
Some(Trigger::Pointer(_) | Trigger::Touch(_))
|
Some(Trigger::Pointer(_) | Trigger::Touch(_) | Trigger::Tool(_, _))
|
||||||
) && location_f64.is_some()
|
) && location_f64.is_some()
|
||||||
{
|
{
|
||||||
let mut tree = tree.copy_clone();
|
let mut tree = tree.copy_clone();
|
||||||
|
|
@ -3414,7 +3427,7 @@ impl TilingLayout {
|
||||||
|
|
||||||
if matches!(
|
if matches!(
|
||||||
overview.active_trigger(),
|
overview.active_trigger(),
|
||||||
Some(Trigger::Pointer(_) | Trigger::Touch(_))
|
Some(Trigger::Pointer(_) | Trigger::Touch(_) | Trigger::Tool(_, _))
|
||||||
) {
|
) {
|
||||||
let non_exclusive_zone = layer_map_for_output(&self.output)
|
let non_exclusive_zone = layer_map_for_output(&self.output)
|
||||||
.non_exclusive_zone()
|
.non_exclusive_zone()
|
||||||
|
|
@ -4062,8 +4075,11 @@ impl TilingLayout {
|
||||||
let draw_groups = overview.0.alpha();
|
let draw_groups = overview.0.alpha();
|
||||||
|
|
||||||
let is_overview = !matches!(overview.0, OverviewMode::None);
|
let is_overview = !matches!(overview.0, OverviewMode::None);
|
||||||
let is_mouse_tiling = (matches!(overview.0.trigger(), Some(Trigger::Pointer(_))))
|
let is_mouse_tiling = (matches!(
|
||||||
.then(|| self.last_overview_hover.as_ref().map(|(_, zone)| zone));
|
overview.0.trigger(),
|
||||||
|
Some(Trigger::Pointer(_) | Trigger::Tool(_, _))
|
||||||
|
))
|
||||||
|
.then(|| self.last_overview_hover.as_ref().map(|(_, zone)| zone));
|
||||||
let swap_desc = if let Some(Trigger::KeyboardSwap(_, desc)) = overview.0.trigger() {
|
let swap_desc = if let Some(Trigger::KeyboardSwap(_, desc)) = overview.0.trigger() {
|
||||||
Some(desc.clone())
|
Some(desc.clone())
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -4216,8 +4232,11 @@ impl TilingLayout {
|
||||||
};
|
};
|
||||||
let draw_groups = overview.0.alpha();
|
let draw_groups = overview.0.alpha();
|
||||||
|
|
||||||
let is_mouse_tiling = (matches!(overview.0.trigger(), Some(Trigger::Pointer(_))))
|
let is_mouse_tiling = (matches!(
|
||||||
.then(|| self.last_overview_hover.as_ref().map(|(_, zone)| zone));
|
overview.0.trigger(),
|
||||||
|
Some(Trigger::Pointer(_) | Trigger::Tool(_, _))
|
||||||
|
))
|
||||||
|
.then(|| self.last_overview_hover.as_ref().map(|(_, zone)| zone));
|
||||||
let swap_desc = if let Some(Trigger::KeyboardSwap(_, desc)) = overview.0.trigger() {
|
let swap_desc = if let Some(Trigger::KeyboardSwap(_, desc)) = overview.0.trigger() {
|
||||||
Some(desc.clone())
|
Some(desc.clone())
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
105
src/shell/mod.rs
105
src/shell/mod.rs
|
|
@ -19,7 +19,10 @@ use crate::{
|
||||||
utils,
|
utils,
|
||||||
wayland::{
|
wayland::{
|
||||||
handlers::data_device::{self, get_dnd_icon},
|
handlers::data_device::{self, get_dnd_icon},
|
||||||
protocols::workspace::{State as WState, WorkspaceCapabilities},
|
protocols::{
|
||||||
|
session_lock_layer::layer_show_on_lock,
|
||||||
|
workspace::{State as WState, WorkspaceCapabilities},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use cosmic_comp_config::{
|
use cosmic_comp_config::{
|
||||||
|
|
@ -32,7 +35,10 @@ use cosmic_settings_config::shortcuts::action::{Direction, FocusDirection, Resiz
|
||||||
use cosmic_settings_config::{shortcuts, window_rules::ApplicationException};
|
use cosmic_settings_config::{shortcuts, window_rules::ApplicationException};
|
||||||
use keyframe::{ease, functions::EaseInOutCubic};
|
use keyframe::{ease, functions::EaseInOutCubic};
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{input::TouchSlot, renderer::element::RenderElementStates},
|
backend::{
|
||||||
|
input::{TabletToolDescriptor, TouchSlot},
|
||||||
|
renderer::element::RenderElementStates,
|
||||||
|
},
|
||||||
desktop::{
|
desktop::{
|
||||||
LayerSurface, PopupKind, WindowSurface, WindowSurfaceType, layer_map_for_output,
|
LayerSurface, PopupKind, WindowSurface, WindowSurfaceType, layer_map_for_output,
|
||||||
space::SpaceElement,
|
space::SpaceElement,
|
||||||
|
|
@ -46,6 +52,7 @@ use smithay::{
|
||||||
pointer::{
|
pointer::{
|
||||||
CursorImageStatus, CursorImageSurfaceData, Focus, GrabStartData as PointerGrabStartData,
|
CursorImageStatus, CursorImageSurfaceData, Focus, GrabStartData as PointerGrabStartData,
|
||||||
},
|
},
|
||||||
|
tablet::{TabletSeatTrait, tool::GrabTrigger as TabletGrabTrigger},
|
||||||
},
|
},
|
||||||
output::{Output, WeakOutput},
|
output::{Output, WeakOutput},
|
||||||
reexports::{
|
reexports::{
|
||||||
|
|
@ -130,6 +137,7 @@ pub enum Trigger {
|
||||||
KeyboardMove(shortcuts::Modifiers),
|
KeyboardMove(shortcuts::Modifiers),
|
||||||
Pointer(u32),
|
Pointer(u32),
|
||||||
Touch(TouchSlot),
|
Touch(TouchSlot),
|
||||||
|
Tool(TabletToolDescriptor, TabletGrabTrigger),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
@ -254,6 +262,7 @@ pub struct PendingWindow {
|
||||||
pub surface: CosmicSurface,
|
pub surface: CosmicSurface,
|
||||||
pub seat: Seat<State>,
|
pub seat: Seat<State>,
|
||||||
pub fullscreen: Option<Output>,
|
pub fullscreen: Option<Output>,
|
||||||
|
pub minimized: bool,
|
||||||
pub maximized: bool,
|
pub maximized: bool,
|
||||||
pub sticky: bool,
|
pub sticky: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -507,6 +516,12 @@ impl WorkspaceSet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn refresh_focus_stacks(&mut self) {
|
||||||
|
for workspace in &mut self.workspaces {
|
||||||
|
workspace.refresh_focus_stack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn activate(
|
fn activate(
|
||||||
&mut self,
|
&mut self,
|
||||||
idx: usize,
|
idx: usize,
|
||||||
|
|
@ -611,6 +626,7 @@ impl WorkspaceSet {
|
||||||
self.workspaces[self.active].refresh();
|
self.workspaces[self.active].refresh();
|
||||||
}
|
}
|
||||||
self.sticky_layer.refresh();
|
self.sticky_layer.refresh();
|
||||||
|
self.refresh_focus_stacks();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_empty_workspace(&mut self, state: &mut WorkspaceUpdateGuard<State>) {
|
fn add_empty_workspace(&mut self, state: &mut WorkspaceUpdateGuard<State>) {
|
||||||
|
|
@ -1614,6 +1630,29 @@ impl Common {
|
||||||
a11y_keyboard_monitor.refresh();
|
a11y_keyboard_monitor.refresh();
|
||||||
}
|
}
|
||||||
self.image_copy_capture_state.cleanup();
|
self.image_copy_capture_state.cleanup();
|
||||||
|
self.cleanup_cursor_images();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Release the enlarged cursor frames a finished shake or zoom left behind.
|
||||||
|
fn cleanup_cursor_images(&mut self) {
|
||||||
|
let shell = self.shell.read();
|
||||||
|
let zoomed = shell.zoom_state.as_ref().is_some_and(|zoom_state| {
|
||||||
|
shell
|
||||||
|
.outputs()
|
||||||
|
.any(|output| zoom_state.animating_level(output) > 1.0)
|
||||||
|
});
|
||||||
|
let now = Instant::now();
|
||||||
|
for seat in shell.seats.iter() {
|
||||||
|
if let Some(cursor_state) = seat
|
||||||
|
.user_data()
|
||||||
|
.get::<crate::backend::render::cursor::CursorState>()
|
||||||
|
{
|
||||||
|
cursor_state
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.drop_magnified_frames(now, zoomed);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn refresh_idle_inhibit(&mut self) {
|
pub fn refresh_idle_inhibit(&mut self) {
|
||||||
|
|
@ -1731,7 +1770,7 @@ impl Shell {
|
||||||
if let Some(set) = self.workspaces.sets.get_mut(output) {
|
if let Some(set) = self.workspaces.sets.get_mut(output) {
|
||||||
if matches!(
|
if matches!(
|
||||||
self.overview_mode.active_trigger(),
|
self.overview_mode.active_trigger(),
|
||||||
Some(Trigger::Pointer(_) | Trigger::Touch(_))
|
Some(Trigger::Pointer(_) | Trigger::Touch(_) | Trigger::Tool(_, _))
|
||||||
) {
|
) {
|
||||||
set.workspaces[set.active].tiling_layer.cleanup_drag();
|
set.workspaces[set.active].tiling_layer.cleanup_drag();
|
||||||
}
|
}
|
||||||
|
|
@ -1782,7 +1821,7 @@ impl Shell {
|
||||||
if let Some(set) = self.workspaces.sets.get_mut(output) {
|
if let Some(set) = self.workspaces.sets.get_mut(output) {
|
||||||
if matches!(
|
if matches!(
|
||||||
self.overview_mode.active_trigger(),
|
self.overview_mode.active_trigger(),
|
||||||
Some(Trigger::Pointer(_) | Trigger::Touch(_))
|
Some(Trigger::Pointer(_) | Trigger::Touch(_) | Trigger::Tool(_, _))
|
||||||
) {
|
) {
|
||||||
set.workspaces[set.active].tiling_layer.cleanup_drag();
|
set.workspaces[set.active].tiling_layer.cleanup_drag();
|
||||||
}
|
}
|
||||||
|
|
@ -2017,12 +2056,27 @@ impl Shell {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn visible_output_for_surface(&self, surface: &WlSurface) -> Option<&Output> {
|
pub fn visible_output_for_surface(&self, surface: &WlSurface) -> Option<&Output> {
|
||||||
|
// NOTE: Keep in sync with surface iteration in `render_input_order_internal`
|
||||||
|
|
||||||
if let Some(session_lock) = &self.session_lock {
|
if let Some(session_lock) = &self.session_lock {
|
||||||
return session_lock
|
if let Some((output, _)) = session_lock
|
||||||
.surfaces
|
.surfaces
|
||||||
.iter()
|
.iter()
|
||||||
.find(|(_, v)| v.wl_surface() == surface)
|
.find(|(_, v)| v.wl_surface() == surface)
|
||||||
.map(|(k, _)| k);
|
{
|
||||||
|
return Some(output);
|
||||||
|
}
|
||||||
|
for o in self.outputs() {
|
||||||
|
let map = layer_map_for_output(o);
|
||||||
|
if let Some(layer_surface) = map.layer_for_surface(surface, WindowSurfaceType::ALL)
|
||||||
|
{
|
||||||
|
if layer_show_on_lock(layer_surface.wl_surface()) {
|
||||||
|
return Some(o);
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.outputs()
|
self.outputs()
|
||||||
|
|
@ -2286,6 +2340,11 @@ impl Shell {
|
||||||
.is_some_and(|state| state.lock().unwrap().is_animating())
|
.is_some_and(|state| state.lock().unwrap().is_animating())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|| self.seats.iter().any(|seat| {
|
||||||
|
seat.user_data()
|
||||||
|
.get::<crate::backend::render::cursor::CursorState>()
|
||||||
|
.is_some_and(|state| state.lock().unwrap().is_magnifying())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_animations(&mut self) -> HashMap<ClientId, Client> {
|
pub fn update_animations(&mut self) -> HashMap<ClientId, Client> {
|
||||||
|
|
@ -2828,6 +2887,7 @@ impl Shell {
|
||||||
surface: window,
|
surface: window,
|
||||||
seat,
|
seat,
|
||||||
fullscreen: output,
|
fullscreen: output,
|
||||||
|
minimized: should_be_minimized,
|
||||||
maximized: should_be_maximized,
|
maximized: should_be_maximized,
|
||||||
sticky: mut should_be_sticky,
|
sticky: mut should_be_sticky,
|
||||||
} = self.pending_windows.remove(pos);
|
} = self.pending_windows.remove(pos);
|
||||||
|
|
@ -2911,6 +2971,7 @@ impl Shell {
|
||||||
if let Some(FocusTarget::Window(focused)) = maybe_focused
|
if let Some(FocusTarget::Window(focused)) = maybe_focused
|
||||||
&& let Some(stack) = focused.stack_ref()
|
&& let Some(stack) = focused.stack_ref()
|
||||||
&& !is_dialog
|
&& !is_dialog
|
||||||
|
&& !should_be_minimized
|
||||||
&& !should_be_maximized
|
&& !should_be_maximized
|
||||||
&& !(workspace.is_tiled(&focused.active_window()) && floating_exception)
|
&& !(workspace.is_tiled(&focused.active_window()) && floating_exception)
|
||||||
{
|
{
|
||||||
|
|
@ -2960,8 +3021,13 @@ impl Shell {
|
||||||
self.maximize_request(&mapped, &seat, false, loop_handle);
|
self.maximize_request(&mapped, &seat, false, loop_handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_target = if (workspace_output == seat.active_output()
|
if should_be_minimized {
|
||||||
&& active_handle == workspace_handle)
|
self.minimize_request(&window);
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_target = if should_be_minimized {
|
||||||
|
None
|
||||||
|
} else if (workspace_output == seat.active_output() && active_handle == workspace_handle)
|
||||||
|| should_be_sticky
|
|| should_be_sticky
|
||||||
{
|
{
|
||||||
// TODO: enforce focus stealing prevention by also checking the same rules as for the else case.
|
// TODO: enforce focus stealing prevention by also checking the same rules as for the else case.
|
||||||
|
|
@ -3085,6 +3151,7 @@ impl Shell {
|
||||||
surface,
|
surface,
|
||||||
seat: seat.clone(),
|
seat: seat.clone(),
|
||||||
fullscreen: None,
|
fullscreen: None,
|
||||||
|
minimized: false,
|
||||||
maximized: false,
|
maximized: false,
|
||||||
sticky: false,
|
sticky: false,
|
||||||
});
|
});
|
||||||
|
|
@ -3803,6 +3870,7 @@ impl Shell {
|
||||||
let trigger = match &start_data {
|
let trigger = match &start_data {
|
||||||
GrabStartData::Pointer(start_data) => Trigger::Pointer(start_data.button),
|
GrabStartData::Pointer(start_data) => Trigger::Pointer(start_data.button),
|
||||||
GrabStartData::Touch(start_data) => Trigger::Touch(start_data.slot),
|
GrabStartData::Touch(start_data) => Trigger::Touch(start_data.slot),
|
||||||
|
GrabStartData::TabletTool { tool, data } => Trigger::Tool(tool.clone(), data.trigger),
|
||||||
};
|
};
|
||||||
let active_hint = if config.cosmic_conf.active_hint {
|
let active_hint = if config.cosmic_conf.active_hint {
|
||||||
self.theme.cosmic().active_hint as u8
|
self.theme.cosmic().active_hint as u8
|
||||||
|
|
@ -5121,10 +5189,19 @@ pub fn check_grab_preconditions(
|
||||||
|
|
||||||
let pointer = seat.get_pointer().unwrap();
|
let pointer = seat.get_pointer().unwrap();
|
||||||
let touch = seat.get_touch().unwrap();
|
let touch = seat.get_touch().unwrap();
|
||||||
|
let tablet = seat.tablet_seat();
|
||||||
|
let tools = tablet.get_tools();
|
||||||
|
|
||||||
let start_data =
|
let start_data =
|
||||||
if serial.is_some_and(|serial| touch.has_grab(serial)) {
|
if serial.is_some_and(|serial| touch.has_grab(serial)) {
|
||||||
GrabStartData::Touch(touch.grab_start_data().unwrap())
|
GrabStartData::Touch(touch.grab_start_data().unwrap())
|
||||||
|
} else if let Some((desc, tool)) =
|
||||||
|
serial.and_then(|serial| tools.iter().find(|(_, tool)| tool.has_grab(serial)))
|
||||||
|
{
|
||||||
|
GrabStartData::TabletTool {
|
||||||
|
tool: desc.clone(),
|
||||||
|
data: tool.grab_start_data().unwrap(),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
GrabStartData::Pointer(pointer.grab_start_data().unwrap_or_else(|| {
|
GrabStartData::Pointer(pointer.grab_start_data().unwrap_or_else(|| {
|
||||||
PointerGrabStartData {
|
PointerGrabStartData {
|
||||||
|
|
@ -5138,8 +5215,16 @@ pub fn check_grab_preconditions(
|
||||||
if let Some(surface) = client_initiated {
|
if let Some(surface) = client_initiated {
|
||||||
// Check that this surface has a click or touch down grab.
|
// Check that this surface has a click or touch down grab.
|
||||||
if !match serial {
|
if !match serial {
|
||||||
Some(serial) => pointer.has_grab(serial) || touch.has_grab(serial),
|
Some(serial) => {
|
||||||
None => pointer.is_grabbed() | touch.is_grabbed(),
|
pointer.has_grab(serial)
|
||||||
|
|| touch.has_grab(serial)
|
||||||
|
|| tools.values().any(|tool| tool.has_grab(serial))
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
pointer.is_grabbed()
|
||||||
|
|| touch.is_grabbed()
|
||||||
|
|| tools.values().any(|tool| tool.is_grabbed())
|
||||||
|
}
|
||||||
} {
|
} {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -526,10 +526,16 @@ impl Workspace {
|
||||||
.mapped()
|
.mapped()
|
||||||
.chain(self.tiling_layer.mapped().map(|(w, _)| w))
|
.chain(self.tiling_layer.mapped().map(|(w, _)| w))
|
||||||
.chain(move_mapped.iter())
|
.chain(move_mapped.iter())
|
||||||
|
.chain(
|
||||||
|
self.minimized_windows
|
||||||
|
.iter()
|
||||||
|
.flat_map(MinimizedWindow::mapped),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
stack.retain(|w| match w {
|
stack.retain(|w| match w {
|
||||||
FocusTarget::Fullscreen(s) => fullscreen_surfaces.contains(&s),
|
FocusTarget::Fullscreen(s) => fullscreen_surfaces.contains(&s),
|
||||||
FocusTarget::Window(w) => mapped().any(|m| w == m),
|
// Sticky windows live in the `WorkspaceSet` but are shown on whichever workspace is current
|
||||||
|
FocusTarget::Window(w) => w.is_sticky() || mapped().any(|m| w == m),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1767,13 +1773,17 @@ impl Workspace {
|
||||||
|
|
||||||
self.floating_layer.render(
|
self.floating_layer.render(
|
||||||
renderer,
|
renderer,
|
||||||
focused.as_ref().and_then(|target| {
|
render_focus
|
||||||
if let FocusTarget::Window(mapped) = target {
|
.then(|| {
|
||||||
Some(mapped)
|
focused.as_ref().and_then(|target| {
|
||||||
} else {
|
if let FocusTarget::Window(mapped) = target {
|
||||||
None
|
Some(mapped)
|
||||||
}
|
} else {
|
||||||
}),
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.flatten(),
|
||||||
resize_indicator.clone(),
|
resize_indicator.clone(),
|
||||||
indicator_thickness,
|
indicator_thickness,
|
||||||
alpha,
|
alpha,
|
||||||
|
|
|
||||||
|
|
@ -7,23 +7,35 @@ use cosmic::{
|
||||||
theme,
|
theme,
|
||||||
widget::{self, icon::Named},
|
widget::{self, icon::Named},
|
||||||
};
|
};
|
||||||
use cosmic_comp_config::ZoomMovement;
|
use cosmic_comp_config::{ZoomConfig, ZoomMovement};
|
||||||
use cosmic_config::ConfigSet;
|
use cosmic_config::ConfigSet;
|
||||||
use keyframe::{ease, functions::Linear};
|
use keyframe::{ease, functions::Linear};
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::renderer::ImportMem,
|
backend::{
|
||||||
|
input::{InputTime, TabletToolDescriptor},
|
||||||
|
renderer::ImportMem,
|
||||||
|
},
|
||||||
desktop::space::SpaceElement,
|
desktop::space::SpaceElement,
|
||||||
input::{
|
input::{
|
||||||
Seat,
|
Seat,
|
||||||
pointer::{
|
pointer::{
|
||||||
AxisFrame, ButtonEvent, Focus, GestureHoldBeginEvent, GestureHoldEndEvent,
|
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent, Focus,
|
||||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent,
|
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||||
MotionEvent as PointerMotionEvent, PointerTarget, RelativeMotionEvent,
|
GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent as PointerMotionEvent,
|
||||||
|
PointerTarget, RelativeMotionEvent,
|
||||||
|
},
|
||||||
|
tablet::{
|
||||||
|
Tablet, TabletSeatTrait,
|
||||||
|
tool::{
|
||||||
|
AxisFrame as TabletAxisFrame, ButtonEvent as TabletButtonEvent,
|
||||||
|
DownEvent as TabletDownEvent, MotionEvent as TabletMotionEvent, TabletToolTarget,
|
||||||
|
UpEvent as TabletUpEvent,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
touch::{
|
touch::{
|
||||||
DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent,
|
DownEvent as TouchDownEvent, FrameMarker, MotionEvent as TouchMotionEvent,
|
||||||
TouchTarget, UpEvent,
|
OrientationEvent, ShapeEvent, TouchTarget, UpEvent as TouchUpEvent,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
output::Output,
|
output::Output,
|
||||||
|
|
@ -33,6 +45,7 @@ use tracing::error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::render::element::AsGlowRenderer,
|
backend::render::element::AsGlowRenderer,
|
||||||
|
shell::grabs::GrabType,
|
||||||
state::State,
|
state::State,
|
||||||
utils::{
|
utils::{
|
||||||
iced::{IcedElement, IcedRenderElement, Program},
|
iced::{IcedElement, IcedRenderElement, Program},
|
||||||
|
|
@ -447,7 +460,7 @@ pub enum MenuMessage {
|
||||||
|
|
||||||
impl ZoomProgram {
|
impl ZoomProgram {
|
||||||
pub fn new(level: f64, movement: ZoomMovement, increment: u32) -> Self {
|
pub fn new(level: f64, movement: ZoomMovement, increment: u32) -> Self {
|
||||||
let mut increments = vec![25, 50, 100, 150, 200];
|
let mut increments = ZoomConfig::ZOOM_INCREMENT_PRESETS.to_vec();
|
||||||
if !increments.contains(&increment) {
|
if !increments.contains(&increment) {
|
||||||
increments.push(increment);
|
increments.push(increment);
|
||||||
}
|
}
|
||||||
|
|
@ -699,15 +712,27 @@ impl Program for ZoomProgram {
|
||||||
);
|
);
|
||||||
|
|
||||||
std::mem::drop(shell);
|
std::mem::drop(shell);
|
||||||
if grab.is_touch_grab() {
|
match grab.grab_type() {
|
||||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
GrabType::Touch => {
|
||||||
} else {
|
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||||
seat.get_pointer().unwrap().set_grab(
|
}
|
||||||
|
GrabType::Pointer => seat.get_pointer().unwrap().set_grab(
|
||||||
state,
|
state,
|
||||||
grab,
|
grab,
|
||||||
serial,
|
serial,
|
||||||
Focus::Clear,
|
Focus::Clear,
|
||||||
);
|
),
|
||||||
|
GrabType::TabletTool => seat
|
||||||
|
.tablet_seat()
|
||||||
|
.get_tool(grab.tool().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.set_grab(
|
||||||
|
state,
|
||||||
|
grab,
|
||||||
|
InputTime::now(),
|
||||||
|
serial,
|
||||||
|
Focus::Clear,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -786,15 +811,27 @@ impl Program for ZoomProgram {
|
||||||
);
|
);
|
||||||
|
|
||||||
std::mem::drop(shell);
|
std::mem::drop(shell);
|
||||||
if grab.is_touch_grab() {
|
match grab.grab_type() {
|
||||||
seat.get_touch().unwrap().set_grab(state, grab, serial);
|
GrabType::Touch => {
|
||||||
} else {
|
seat.get_touch().unwrap().set_grab(state, grab, serial)
|
||||||
seat.get_pointer().unwrap().set_grab(
|
}
|
||||||
|
GrabType::Pointer => seat.get_pointer().unwrap().set_grab(
|
||||||
state,
|
state,
|
||||||
grab,
|
grab,
|
||||||
serial,
|
serial,
|
||||||
Focus::Clear,
|
Focus::Clear,
|
||||||
);
|
),
|
||||||
|
GrabType::TabletTool => seat
|
||||||
|
.tablet_seat()
|
||||||
|
.get_tool(grab.tool().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.set_grab(
|
||||||
|
state,
|
||||||
|
grab,
|
||||||
|
InputTime::now(),
|
||||||
|
serial,
|
||||||
|
Focus::Clear,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -829,7 +866,7 @@ impl Program for ZoomProgram {
|
||||||
if let Some(pos) = self.increments.iter().position(|val| *val == increment) {
|
if let Some(pos) = self.increments.iter().position(|val| *val == increment) {
|
||||||
self.increment_idx = pos;
|
self.increment_idx = pos;
|
||||||
} else {
|
} else {
|
||||||
let mut increments = vec![25, 50, 100, 150, 200];
|
let mut increments = ZoomConfig::ZOOM_INCREMENT_PRESETS.to_vec();
|
||||||
if !increments.contains(&increment) {
|
if !increments.contains(&increment) {
|
||||||
increments.push(increment);
|
increments.push(increment);
|
||||||
}
|
}
|
||||||
|
|
@ -884,14 +921,14 @@ impl PointerTarget<State> for ZoomFocusTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn button(&self, seat: &Seat<State>, data: &mut State, event: &ButtonEvent) {
|
fn button(&self, seat: &Seat<State>, data: &mut State, event: &PointerButtonEvent) {
|
||||||
match self {
|
match self {
|
||||||
ZoomFocusTarget::Main(elem) => PointerTarget::button(elem, seat, data, event),
|
ZoomFocusTarget::Main(elem) => PointerTarget::button(elem, seat, data, event),
|
||||||
ZoomFocusTarget::Menu(elem) => PointerTarget::button(elem, seat, data, event),
|
ZoomFocusTarget::Menu(elem) => PointerTarget::button(elem, seat, data, event),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: AxisFrame) {
|
fn axis(&self, seat: &Seat<State>, data: &mut State, frame: PointerAxisFrame) {
|
||||||
match self {
|
match self {
|
||||||
ZoomFocusTarget::Main(elem) => PointerTarget::axis(elem, seat, data, frame),
|
ZoomFocusTarget::Main(elem) => PointerTarget::axis(elem, seat, data, frame),
|
||||||
ZoomFocusTarget::Menu(elem) => PointerTarget::axis(elem, seat, data, frame),
|
ZoomFocusTarget::Menu(elem) => PointerTarget::axis(elem, seat, data, frame),
|
||||||
|
|
@ -1024,7 +1061,7 @@ impl PointerTarget<State> for ZoomFocusTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: u32) {
|
fn leave(&self, seat: &Seat<State>, data: &mut State, serial: Serial, time: InputTime) {
|
||||||
match self {
|
match self {
|
||||||
ZoomFocusTarget::Main(elem) => PointerTarget::leave(elem, seat, data, serial, time),
|
ZoomFocusTarget::Main(elem) => PointerTarget::leave(elem, seat, data, serial, time),
|
||||||
ZoomFocusTarget::Menu(elem) => PointerTarget::leave(elem, seat, data, serial, time),
|
ZoomFocusTarget::Menu(elem) => PointerTarget::leave(elem, seat, data, serial, time),
|
||||||
|
|
@ -1033,14 +1070,14 @@ impl PointerTarget<State> for ZoomFocusTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TouchTarget<State> for ZoomFocusTarget {
|
impl TouchTarget<State> for ZoomFocusTarget {
|
||||||
fn down(&self, seat: &Seat<State>, data: &mut State, event: &DownEvent) {
|
fn down(&self, seat: &Seat<State>, data: &mut State, event: &TouchDownEvent) {
|
||||||
match self {
|
match self {
|
||||||
ZoomFocusTarget::Main(elem) => TouchTarget::down(elem, seat, data, event),
|
ZoomFocusTarget::Main(elem) => TouchTarget::down(elem, seat, data, event),
|
||||||
ZoomFocusTarget::Menu(elem) => TouchTarget::down(elem, seat, data, event),
|
ZoomFocusTarget::Menu(elem) => TouchTarget::down(elem, seat, data, event),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn up(&self, seat: &Seat<State>, data: &mut State, event: &UpEvent) {
|
fn up(&self, seat: &Seat<State>, data: &mut State, event: &TouchUpEvent) {
|
||||||
match self {
|
match self {
|
||||||
ZoomFocusTarget::Main(elem) => TouchTarget::up(elem, seat, data, event),
|
ZoomFocusTarget::Main(elem) => TouchTarget::up(elem, seat, data, event),
|
||||||
ZoomFocusTarget::Menu(elem) => TouchTarget::up(elem, seat, data, event),
|
ZoomFocusTarget::Menu(elem) => TouchTarget::up(elem, seat, data, event),
|
||||||
|
|
@ -1090,6 +1127,144 @@ impl TouchTarget<State> for ZoomFocusTarget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TabletToolTarget<State> for ZoomFocusTarget {
|
||||||
|
fn proximity_in(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
tablet: &Tablet,
|
||||||
|
serial: Serial,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
ZoomFocusTarget::Main(elem) => {
|
||||||
|
TabletToolTarget::proximity_in(elem, seat, data, tool_descriptor, tablet, serial)
|
||||||
|
}
|
||||||
|
ZoomFocusTarget::Menu(elem) => {
|
||||||
|
TabletToolTarget::proximity_in(elem, seat, data, tool_descriptor, tablet, serial)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proximity_out(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
ZoomFocusTarget::Main(elem) => {
|
||||||
|
TabletToolTarget::proximity_out(elem, seat, data, tool_descriptor)
|
||||||
|
}
|
||||||
|
ZoomFocusTarget::Menu(elem) => {
|
||||||
|
TabletToolTarget::proximity_out(elem, seat, data, tool_descriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn down(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
event: &TabletDownEvent,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
ZoomFocusTarget::Main(elem) => {
|
||||||
|
TabletToolTarget::down(elem, seat, data, tool_descriptor, event)
|
||||||
|
}
|
||||||
|
ZoomFocusTarget::Menu(elem) => {
|
||||||
|
TabletToolTarget::down(elem, seat, data, tool_descriptor, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn up(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
event: &TabletUpEvent,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
ZoomFocusTarget::Main(elem) => {
|
||||||
|
TabletToolTarget::up(elem, seat, data, tool_descriptor, event)
|
||||||
|
}
|
||||||
|
ZoomFocusTarget::Menu(elem) => {
|
||||||
|
TabletToolTarget::up(elem, seat, data, tool_descriptor, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn motion(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
event: &TabletMotionEvent,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
ZoomFocusTarget::Main(elem) => {
|
||||||
|
TabletToolTarget::motion(elem, seat, data, tool_descriptor, event)
|
||||||
|
}
|
||||||
|
ZoomFocusTarget::Menu(elem) => {
|
||||||
|
TabletToolTarget::motion(elem, seat, data, tool_descriptor, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn axis(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
frame: TabletAxisFrame,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
ZoomFocusTarget::Main(elem) => {
|
||||||
|
TabletToolTarget::axis(elem, seat, data, tool_descriptor, frame)
|
||||||
|
}
|
||||||
|
ZoomFocusTarget::Menu(elem) => {
|
||||||
|
TabletToolTarget::axis(elem, seat, data, tool_descriptor, frame)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
event: &TabletButtonEvent,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
ZoomFocusTarget::Main(elem) => {
|
||||||
|
TabletToolTarget::button(elem, seat, data, tool_descriptor, event)
|
||||||
|
}
|
||||||
|
ZoomFocusTarget::Menu(elem) => {
|
||||||
|
TabletToolTarget::button(elem, seat, data, tool_descriptor, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<State>,
|
||||||
|
data: &mut State,
|
||||||
|
tool_descriptor: &TabletToolDescriptor,
|
||||||
|
time: InputTime,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
ZoomFocusTarget::Main(elem) => {
|
||||||
|
TabletToolTarget::frame(elem, seat, data, tool_descriptor, time)
|
||||||
|
}
|
||||||
|
ZoomFocusTarget::Menu(elem) => {
|
||||||
|
TabletToolTarget::frame(elem, seat, data, tool_descriptor, time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl IsAlive for ZoomFocusTarget {
|
impl IsAlive for ZoomFocusTarget {
|
||||||
fn alive(&self) -> bool {
|
fn alive(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
||||||
51
src/state.rs
51
src/state.rs
|
|
@ -23,6 +23,7 @@ use crate::{
|
||||||
output_configuration::OutputConfigurationState,
|
output_configuration::OutputConfigurationState,
|
||||||
output_power::OutputPowerState,
|
output_power::OutputPowerState,
|
||||||
overlap_notify::OverlapNotifyState,
|
overlap_notify::OverlapNotifyState,
|
||||||
|
session_lock_layer::SessionLockLayerState,
|
||||||
toplevel_info::ToplevelInfoState,
|
toplevel_info::ToplevelInfoState,
|
||||||
toplevel_management::{ManagementCapabilities, ToplevelManagementState},
|
toplevel_management::{ManagementCapabilities, ToplevelManagementState},
|
||||||
workspace::{WorkspaceState, WorkspaceUpdateGuard},
|
workspace::{WorkspaceState, WorkspaceUpdateGuard},
|
||||||
|
|
@ -320,6 +321,7 @@ pub struct Common {
|
||||||
pub xwayland_state: Option<XWaylandState>,
|
pub xwayland_state: Option<XWaylandState>,
|
||||||
pub xwayland_shell_state: XWaylandShellState,
|
pub xwayland_shell_state: XWaylandShellState,
|
||||||
pub pointer_focus_state: Option<PointerFocusState>,
|
pub pointer_focus_state: Option<PointerFocusState>,
|
||||||
|
pub session_lock_layer_state: SessionLockLayerState,
|
||||||
|
|
||||||
#[cfg(feature = "logind")]
|
#[cfg(feature = "logind")]
|
||||||
pub inhibit_lid_fd: Option<OwnedFd>,
|
pub inhibit_lid_fd: Option<OwnedFd>,
|
||||||
|
|
@ -665,7 +667,7 @@ impl State {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let clock = Clock::new();
|
let clock = Clock::new();
|
||||||
let config = Config::load(&handle);
|
let config = Config::load(&handle, kiosk_command.is_some());
|
||||||
let compositor_state = CompositorState::new::<Self>(dh);
|
let compositor_state = CompositorState::new::<Self>(dh);
|
||||||
let corner_radius_state = CornerRadiusState::new::<Self>(dh);
|
let corner_radius_state = CornerRadiusState::new::<Self>(dh);
|
||||||
let data_device_state = DataDeviceState::new::<Self>(dh);
|
let data_device_state = DataDeviceState::new::<Self>(dh);
|
||||||
|
|
@ -763,6 +765,9 @@ impl State {
|
||||||
|
|
||||||
let dbus_state = DBusState::init(&handle);
|
let dbus_state = DBusState::init(&handle);
|
||||||
|
|
||||||
|
let session_lock_layer_state =
|
||||||
|
SessionLockLayerState::new::<State, _>(dh, client_not_sandboxed);
|
||||||
|
|
||||||
State {
|
State {
|
||||||
common: Common {
|
common: Common {
|
||||||
config,
|
config,
|
||||||
|
|
@ -830,6 +835,7 @@ impl State {
|
||||||
pointer_focus_state: None,
|
pointer_focus_state: None,
|
||||||
dbus_state,
|
dbus_state,
|
||||||
keyboard_layout_state,
|
keyboard_layout_state,
|
||||||
|
session_lock_layer_state,
|
||||||
|
|
||||||
#[cfg(feature = "logind")]
|
#[cfg(feature = "logind")]
|
||||||
inhibit_lid_fd: None,
|
inhibit_lid_fd: None,
|
||||||
|
|
@ -968,6 +974,8 @@ impl Common {
|
||||||
output: &Output,
|
output: &Output,
|
||||||
render_element_states: &RenderElementStates,
|
render_element_states: &RenderElementStates,
|
||||||
) {
|
) {
|
||||||
|
// NOTE: Keep in sync with surface iteration in `render_input_order_internal`
|
||||||
|
|
||||||
let shell = self.shell.read();
|
let shell = self.shell.read();
|
||||||
let processor = |namespace: Option<usize>| {
|
let processor = |namespace: Option<usize>| {
|
||||||
move |surface: &WlSurface, states: &SurfaceData| {
|
move |surface: &WlSurface, states: &SurfaceData| {
|
||||||
|
|
@ -1074,6 +1082,8 @@ impl Common {
|
||||||
render_element_states: &RenderElementStates,
|
render_element_states: &RenderElementStates,
|
||||||
mut dmabuf_feedback: impl FnMut(DrmNode) -> Option<SurfaceDmabufFeedback>,
|
mut dmabuf_feedback: impl FnMut(DrmNode) -> Option<SurfaceDmabufFeedback>,
|
||||||
) {
|
) {
|
||||||
|
// NOTE: Keep in sync with surface iteration in `render_input_order_internal`
|
||||||
|
|
||||||
let shell = self.shell.read();
|
let shell = self.shell.read();
|
||||||
|
|
||||||
if let Some(session_lock) = shell.session_lock.as_ref()
|
if let Some(session_lock) = shell.session_lock.as_ref()
|
||||||
|
|
@ -1287,6 +1297,8 @@ impl Common {
|
||||||
|
|
||||||
#[profiling::function]
|
#[profiling::function]
|
||||||
pub fn send_frames(&self, output: &Output, sequence: Option<usize>) {
|
pub fn send_frames(&self, output: &Output, sequence: Option<usize>) {
|
||||||
|
// NOTE: Keep in sync with surface iteration in `render_input_order_internal`
|
||||||
|
|
||||||
let time = self.clock.now();
|
let time = self.clock.now();
|
||||||
let should_send = |surface: &WlSurface, states: &SurfaceData| {
|
let should_send = |surface: &WlSurface, states: &SurfaceData| {
|
||||||
// Do the standard primary scanout output check. For pointer surfaces it deduplicates
|
// Do the standard primary scanout output check. For pointer surfaces it deduplicates
|
||||||
|
|
@ -1328,8 +1340,12 @@ impl Common {
|
||||||
const THROTTLE: Option<Duration> = Some(Duration::from_millis(995));
|
const THROTTLE: Option<Duration> = Some(Duration::from_millis(995));
|
||||||
const SCREENCOPY_THROTTLE: Option<Duration> = Some(Duration::from_nanos(16_666_666));
|
const SCREENCOPY_THROTTLE: Option<Duration> = Some(Duration::from_nanos(16_666_666));
|
||||||
|
|
||||||
fn throttle(session_holder: &impl SessionHolder) -> Option<Duration> {
|
fn throttle(session_holder: &impl SessionHolder, is_xwayland: bool) -> Option<Duration> {
|
||||||
if session_holder.sessions().is_empty() && session_holder.cursor_sessions().is_empty() {
|
if is_xwayland {
|
||||||
|
Some(Duration::ZERO)
|
||||||
|
} else if session_holder.sessions().is_empty()
|
||||||
|
&& session_holder.cursor_sessions().is_empty()
|
||||||
|
{
|
||||||
THROTTLE
|
THROTTLE
|
||||||
} else {
|
} else {
|
||||||
SCREENCOPY_THROTTLE
|
SCREENCOPY_THROTTLE
|
||||||
|
|
@ -1365,7 +1381,8 @@ impl Common {
|
||||||
&& let Some(grab_state) = move_grab.lock().unwrap().as_ref()
|
&& let Some(grab_state) = move_grab.lock().unwrap().as_ref()
|
||||||
{
|
{
|
||||||
for (window, _) in grab_state.element().windows() {
|
for (window, _) in grab_state.element().windows() {
|
||||||
window.send_frame(output, time, throttle(&window), should_send);
|
let throttle = throttle(&window, window.x11_surface().is_some());
|
||||||
|
window.send_frame(output, time, throttle, should_send);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1389,25 +1406,28 @@ impl Common {
|
||||||
.mapped()
|
.mapped()
|
||||||
.for_each(|mapped| {
|
.for_each(|mapped| {
|
||||||
for (window, _) in mapped.windows() {
|
for (window, _) in mapped.windows() {
|
||||||
window.send_frame(output, time, throttle(&window), should_send);
|
let throttle = throttle(&window, window.x11_surface().is_some());
|
||||||
|
window.send_frame(output, time, throttle, should_send);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(active) = shell.active_space(output) {
|
if let Some(active) = shell.active_space(output) {
|
||||||
if let Some(fs) = active.get_fullscreen(shell.seats.last_active()) {
|
if let Some(fs) = active.get_fullscreen(shell.seats.last_active()) {
|
||||||
fs.surface
|
let throttle = throttle(&fs.surface, fs.surface.x11_surface().is_some());
|
||||||
.send_frame(output, time, throttle(&fs.surface), should_send);
|
fs.surface.send_frame(output, time, throttle, should_send);
|
||||||
}
|
}
|
||||||
active.mapped().for_each(|mapped| {
|
active.mapped().for_each(|mapped| {
|
||||||
for (window, _) in mapped.windows() {
|
for (window, _) in mapped.windows() {
|
||||||
window.send_frame(output, time, throttle(&window), should_send);
|
let throttle = throttle(&window, window.x11_surface().is_some());
|
||||||
|
window.send_frame(output, time, throttle, should_send);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// other (throttled) windows
|
// other (throttled) windows
|
||||||
active.minimized_windows.iter().for_each(|m| {
|
active.minimized_windows.iter().for_each(|m| {
|
||||||
for window in m.windows() {
|
for window in m.windows() {
|
||||||
window.send_frame(output, time, throttle(&window), |_, _| None);
|
let throttle = throttle(&window, window.x11_surface().is_some());
|
||||||
|
window.send_frame(output, time, throttle, |_, _| None);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1417,18 +1437,25 @@ impl Common {
|
||||||
.filter(|w| w.handle != active.handle)
|
.filter(|w| w.handle != active.handle)
|
||||||
{
|
{
|
||||||
if let Some(fs) = space.get_fullscreen(shell.seats.last_active()) {
|
if let Some(fs) = space.get_fullscreen(shell.seats.last_active()) {
|
||||||
let throttle = min(throttle(space), throttle(&fs.surface));
|
let throttle = min(
|
||||||
|
throttle(space, false),
|
||||||
|
throttle(&fs.surface, fs.surface.x11_surface().is_some()),
|
||||||
|
);
|
||||||
fs.surface.send_frame(output, time, throttle, |_, _| None);
|
fs.surface.send_frame(output, time, throttle, |_, _| None);
|
||||||
}
|
}
|
||||||
space.mapped().for_each(|mapped| {
|
space.mapped().for_each(|mapped| {
|
||||||
for (window, _) in mapped.windows() {
|
for (window, _) in mapped.windows() {
|
||||||
let throttle = min(throttle(space), throttle(&window));
|
let throttle = min(
|
||||||
|
throttle(space, false),
|
||||||
|
throttle(&window, window.x11_surface().is_some()),
|
||||||
|
);
|
||||||
window.send_frame(output, time, throttle, |_, _| None);
|
window.send_frame(output, time, throttle, |_, _| None);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
space.minimized_windows.iter().for_each(|m| {
|
space.minimized_windows.iter().for_each(|m| {
|
||||||
for window in m.windows() {
|
for window in m.windows() {
|
||||||
window.send_frame(output, time, throttle(&window), |_, _| None);
|
let throttle = throttle(&window, window.x11_surface().is_some());
|
||||||
|
window.send_frame(output, time, throttle, |_, _| None);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ use ordered_float::OrderedFloat;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
allocator::Fourcc,
|
allocator::Fourcc,
|
||||||
input::{ButtonState, KeyState},
|
input::{ButtonState, InputTime, KeyState, TabletToolDescriptor},
|
||||||
renderer::{
|
renderer::{
|
||||||
ImportMem,
|
ImportMem,
|
||||||
element::{
|
element::{
|
||||||
|
|
@ -45,14 +45,23 @@ use smithay::{
|
||||||
Seat,
|
Seat,
|
||||||
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
|
keyboard::{KeyboardTarget, KeysymHandle, ModifiersState},
|
||||||
pointer::{
|
pointer::{
|
||||||
AxisFrame, ButtonEvent, GestureHoldBeginEvent, GestureHoldEndEvent,
|
AxisFrame as PointerAxisFrame, ButtonEvent as PointerButtonEvent,
|
||||||
GesturePinchBeginEvent, GesturePinchEndEvent, GesturePinchUpdateEvent,
|
GestureHoldBeginEvent, GestureHoldEndEvent, GesturePinchBeginEvent,
|
||||||
GestureSwipeBeginEvent, GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent,
|
GesturePinchEndEvent, GesturePinchUpdateEvent, GestureSwipeBeginEvent,
|
||||||
|
GestureSwipeEndEvent, GestureSwipeUpdateEvent, MotionEvent as PointerMotionEvent,
|
||||||
PointerTarget, RelativeMotionEvent,
|
PointerTarget, RelativeMotionEvent,
|
||||||
},
|
},
|
||||||
|
tablet::{
|
||||||
|
Tablet,
|
||||||
|
tool::{
|
||||||
|
AxisFrame as ToolAxisFrame, ButtonEvent as ToolButtonEvent,
|
||||||
|
DownEvent as ToolDownEvent, MotionEvent as ToolMotionEvent, TabletToolTarget,
|
||||||
|
UpEvent as ToolUpEvent,
|
||||||
|
},
|
||||||
|
},
|
||||||
touch::{
|
touch::{
|
||||||
DownEvent, FrameMarker, MotionEvent as TouchMotionEvent, OrientationEvent, ShapeEvent,
|
DownEvent as TouchDownEvent, FrameMarker, MotionEvent as TouchMotionEvent,
|
||||||
TouchTarget, UpEvent,
|
OrientationEvent, ShapeEvent, TouchTarget, UpEvent as TouchUpEvent,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
output::Output,
|
output::Output,
|
||||||
|
|
@ -189,6 +198,7 @@ pub(crate) struct IcedElementInternal<P: Program + Send + 'static> {
|
||||||
touch_map: HashMap<Finger, IcedPoint>,
|
touch_map: HashMap<Finger, IcedPoint>,
|
||||||
last_touch_frame: Option<FrameMarker>,
|
last_touch_frame: Option<FrameMarker>,
|
||||||
last_touch_serial: Option<Serial>,
|
last_touch_serial: Option<Serial>,
|
||||||
|
last_tablet_serial: Option<Serial>,
|
||||||
|
|
||||||
// iced
|
// iced
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
|
|
@ -240,6 +250,7 @@ impl<P: Program + Send + Clone + 'static> Clone for IcedElementInternal<P> {
|
||||||
touch_map: self.touch_map.clone(),
|
touch_map: self.touch_map.clone(),
|
||||||
last_touch_frame: None,
|
last_touch_frame: None,
|
||||||
last_touch_serial: None,
|
last_touch_serial: None,
|
||||||
|
last_tablet_serial: None,
|
||||||
theme: self.theme.clone(),
|
theme: self.theme.clone(),
|
||||||
renderer,
|
renderer,
|
||||||
state,
|
state,
|
||||||
|
|
@ -267,6 +278,7 @@ impl<P: Program + Send + 'static> fmt::Debug for IcedElementInternal<P> {
|
||||||
.field("touch_map", &self.touch_map)
|
.field("touch_map", &self.touch_map)
|
||||||
.field("last_touch_frame", &self.last_touch_frame)
|
.field("last_touch_frame", &self.last_touch_frame)
|
||||||
.field("last_touch_serial", &self.last_touch_serial)
|
.field("last_touch_serial", &self.last_touch_serial)
|
||||||
|
.field("last_tablet_serial", &self.last_tablet_serial)
|
||||||
.field("theme", &"...")
|
.field("theme", &"...")
|
||||||
.field("renderer", &"...")
|
.field("renderer", &"...")
|
||||||
.field("state", &"...")
|
.field("state", &"...")
|
||||||
|
|
@ -326,6 +338,7 @@ impl<P: Program + Send + 'static> IcedElement<P> {
|
||||||
touch_map: HashMap::new(),
|
touch_map: HashMap::new(),
|
||||||
last_touch_frame: None,
|
last_touch_frame: None,
|
||||||
last_touch_serial: None,
|
last_touch_serial: None,
|
||||||
|
last_tablet_serial: None,
|
||||||
theme,
|
theme,
|
||||||
renderer,
|
renderer,
|
||||||
state,
|
state,
|
||||||
|
|
@ -481,12 +494,144 @@ impl<P: Program + Send + 'static> IcedElementInternal<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<P: Program + Send + 'static> TabletToolTarget<crate::state::State> for IcedElement<P> {
|
||||||
|
fn proximity_in(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<crate::state::State>,
|
||||||
|
_data: &mut crate::state::State,
|
||||||
|
_tool_descriptor: &TabletToolDescriptor,
|
||||||
|
_tablet: &Tablet,
|
||||||
|
serial: Serial,
|
||||||
|
) {
|
||||||
|
let mut internal = self.0.lock().unwrap();
|
||||||
|
internal
|
||||||
|
.state
|
||||||
|
.queue_event(Event::Mouse(MouseEvent::CursorEntered));
|
||||||
|
internal.last_tablet_serial = Some(serial);
|
||||||
|
*internal.last_seat.lock().unwrap() = Some((seat.clone(), serial));
|
||||||
|
internal.update(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn proximity_out(
|
||||||
|
&self,
|
||||||
|
_seat: &Seat<crate::state::State>,
|
||||||
|
_data: &mut crate::state::State,
|
||||||
|
_tool_descriptor: &TabletToolDescriptor,
|
||||||
|
) {
|
||||||
|
let mut internal = self.0.lock().unwrap();
|
||||||
|
internal.cursor_pos = None;
|
||||||
|
internal
|
||||||
|
.state
|
||||||
|
.queue_event(Event::Mouse(MouseEvent::CursorLeft));
|
||||||
|
internal.update(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn down(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<crate::state::State>,
|
||||||
|
_data: &mut crate::state::State,
|
||||||
|
_tool_descriptor: &TabletToolDescriptor,
|
||||||
|
event: &ToolDownEvent,
|
||||||
|
) {
|
||||||
|
let mut internal = self.0.lock().unwrap();
|
||||||
|
let id = Finger(0);
|
||||||
|
let Some(event_location) = internal.cursor_pos else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let position = IcedPoint::new(event_location.x as f32, event_location.y as f32);
|
||||||
|
internal
|
||||||
|
.state
|
||||||
|
.queue_event(Event::Touch(TouchEvent::FingerPressed { id, position }));
|
||||||
|
internal.last_tablet_serial = Some(event.serial);
|
||||||
|
*internal.last_seat.lock().unwrap() = Some((seat.clone(), event.serial));
|
||||||
|
internal.update(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn up(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<crate::state::State>,
|
||||||
|
_data: &mut crate::state::State,
|
||||||
|
_tool_descriptor: &TabletToolDescriptor,
|
||||||
|
_event: &ToolUpEvent,
|
||||||
|
) {
|
||||||
|
let mut internal = self.0.lock().unwrap();
|
||||||
|
let id = Finger(0);
|
||||||
|
if let Some(event_location) = internal.cursor_pos {
|
||||||
|
*internal.last_seat.lock().unwrap() =
|
||||||
|
Some((seat.clone(), internal.last_tablet_serial.unwrap()));
|
||||||
|
let position = IcedPoint::new(event_location.x as f32, event_location.y as f32);
|
||||||
|
internal
|
||||||
|
.state
|
||||||
|
.queue_event(Event::Touch(TouchEvent::FingerLifted { id, position }));
|
||||||
|
internal.update(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn motion(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<crate::state::State>,
|
||||||
|
_data: &mut crate::state::State,
|
||||||
|
_tool_descriptor: &TabletToolDescriptor,
|
||||||
|
event: &ToolMotionEvent,
|
||||||
|
) {
|
||||||
|
let mut internal = self.0.lock().unwrap();
|
||||||
|
let event_location = event.location.downscale(internal.additional_scale);
|
||||||
|
let position = IcedPoint::new(event_location.x as f32, event_location.y as f32);
|
||||||
|
internal
|
||||||
|
.state
|
||||||
|
.queue_event(Event::Mouse(MouseEvent::CursorMoved { position }));
|
||||||
|
internal.cursor_pos = Some(event_location);
|
||||||
|
*internal.last_seat.lock().unwrap() =
|
||||||
|
Some((seat.clone(), internal.last_tablet_serial.unwrap()));
|
||||||
|
internal.update(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn axis(
|
||||||
|
&self,
|
||||||
|
_seat: &Seat<crate::state::State>,
|
||||||
|
_data: &mut crate::state::State,
|
||||||
|
_tool_descriptor: &TabletToolDescriptor,
|
||||||
|
_frame: ToolAxisFrame,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fn button(
|
||||||
|
&self,
|
||||||
|
seat: &Seat<crate::state::State>,
|
||||||
|
_data: &mut crate::state::State,
|
||||||
|
_tool_descriptor: &TabletToolDescriptor,
|
||||||
|
event: &ToolButtonEvent,
|
||||||
|
) {
|
||||||
|
let mut internal = self.0.lock().unwrap();
|
||||||
|
let button = match event.button {
|
||||||
|
0x14b => MouseButton::Right,
|
||||||
|
0x14c => MouseButton::Middle,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
internal.state.queue_event(Event::Mouse(match event.state {
|
||||||
|
ButtonState::Pressed => MouseEvent::ButtonPressed(button),
|
||||||
|
ButtonState::Released => MouseEvent::ButtonReleased(button),
|
||||||
|
}));
|
||||||
|
*internal.last_seat.lock().unwrap() = Some((seat.clone(), event.serial));
|
||||||
|
internal.update(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn frame(
|
||||||
|
&self,
|
||||||
|
_seat: &Seat<crate::state::State>,
|
||||||
|
_data: &mut crate::state::State,
|
||||||
|
_tool_descriptor: &TabletToolDescriptor,
|
||||||
|
_time: InputTime,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<P: Program + Send + 'static> PointerTarget<crate::state::State> for IcedElement<P> {
|
impl<P: Program + Send + 'static> PointerTarget<crate::state::State> for IcedElement<P> {
|
||||||
fn enter(
|
fn enter(
|
||||||
&self,
|
&self,
|
||||||
seat: &Seat<crate::state::State>,
|
seat: &Seat<crate::state::State>,
|
||||||
_data: &mut crate::state::State,
|
_data: &mut crate::state::State,
|
||||||
event: &MotionEvent,
|
event: &PointerMotionEvent,
|
||||||
) {
|
) {
|
||||||
let mut internal = self.0.lock().unwrap();
|
let mut internal = self.0.lock().unwrap();
|
||||||
internal
|
internal
|
||||||
|
|
@ -507,7 +652,7 @@ impl<P: Program + Send + 'static> PointerTarget<crate::state::State> for IcedEle
|
||||||
&self,
|
&self,
|
||||||
seat: &Seat<crate::state::State>,
|
seat: &Seat<crate::state::State>,
|
||||||
_data: &mut crate::state::State,
|
_data: &mut crate::state::State,
|
||||||
event: &MotionEvent,
|
event: &PointerMotionEvent,
|
||||||
) {
|
) {
|
||||||
let mut internal = self.0.lock().unwrap();
|
let mut internal = self.0.lock().unwrap();
|
||||||
let event_location = event.location.downscale(internal.additional_scale);
|
let event_location = event.location.downscale(internal.additional_scale);
|
||||||
|
|
@ -532,7 +677,7 @@ impl<P: Program + Send + 'static> PointerTarget<crate::state::State> for IcedEle
|
||||||
&self,
|
&self,
|
||||||
seat: &Seat<crate::state::State>,
|
seat: &Seat<crate::state::State>,
|
||||||
_data: &mut crate::state::State,
|
_data: &mut crate::state::State,
|
||||||
event: &ButtonEvent,
|
event: &PointerButtonEvent,
|
||||||
) {
|
) {
|
||||||
let mut internal = self.0.lock().unwrap();
|
let mut internal = self.0.lock().unwrap();
|
||||||
let button = match event.button {
|
let button = match event.button {
|
||||||
|
|
@ -553,7 +698,7 @@ impl<P: Program + Send + 'static> PointerTarget<crate::state::State> for IcedEle
|
||||||
&self,
|
&self,
|
||||||
_seat: &Seat<crate::state::State>,
|
_seat: &Seat<crate::state::State>,
|
||||||
_data: &mut crate::state::State,
|
_data: &mut crate::state::State,
|
||||||
frame: AxisFrame,
|
frame: PointerAxisFrame,
|
||||||
) {
|
) {
|
||||||
let mut internal = self.0.lock().unwrap();
|
let mut internal = self.0.lock().unwrap();
|
||||||
internal
|
internal
|
||||||
|
|
@ -581,9 +726,10 @@ impl<P: Program + Send + 'static> PointerTarget<crate::state::State> for IcedEle
|
||||||
_seat: &Seat<crate::state::State>,
|
_seat: &Seat<crate::state::State>,
|
||||||
_data: &mut crate::state::State,
|
_data: &mut crate::state::State,
|
||||||
_serial: Serial,
|
_serial: Serial,
|
||||||
_time: u32,
|
_time: InputTime,
|
||||||
) {
|
) {
|
||||||
let mut internal = self.0.lock().unwrap();
|
let mut internal = self.0.lock().unwrap();
|
||||||
|
internal.cursor_pos = None;
|
||||||
internal
|
internal
|
||||||
.state
|
.state
|
||||||
.queue_event(Event::Mouse(MouseEvent::CursorLeft));
|
.queue_event(Event::Mouse(MouseEvent::CursorLeft));
|
||||||
|
|
@ -653,7 +799,7 @@ impl<P: Program + Send + 'static> TouchTarget<crate::state::State> for IcedEleme
|
||||||
&self,
|
&self,
|
||||||
seat: &Seat<crate::state::State>,
|
seat: &Seat<crate::state::State>,
|
||||||
_data: &mut crate::state::State,
|
_data: &mut crate::state::State,
|
||||||
event: &DownEvent,
|
event: &TouchDownEvent,
|
||||||
) {
|
) {
|
||||||
let mut internal = self.0.lock().unwrap();
|
let mut internal = self.0.lock().unwrap();
|
||||||
let id = Finger(i32::from(event.slot) as u64);
|
let id = Finger(i32::from(event.slot) as u64);
|
||||||
|
|
@ -673,7 +819,7 @@ impl<P: Program + Send + 'static> TouchTarget<crate::state::State> for IcedEleme
|
||||||
&self,
|
&self,
|
||||||
seat: &Seat<crate::state::State>,
|
seat: &Seat<crate::state::State>,
|
||||||
_data: &mut crate::state::State,
|
_data: &mut crate::state::State,
|
||||||
event: &UpEvent,
|
event: &TouchUpEvent,
|
||||||
) {
|
) {
|
||||||
let mut internal = self.0.lock().unwrap();
|
let mut internal = self.0.lock().unwrap();
|
||||||
let id = Finger(i32::from(event.slot) as u64);
|
let id = Finger(i32::from(event.slot) as u64);
|
||||||
|
|
@ -784,7 +930,7 @@ impl<P: Program + Send + 'static> KeyboardTarget<crate::state::State> for IcedEl
|
||||||
_key: KeysymHandle<'_>,
|
_key: KeysymHandle<'_>,
|
||||||
_state: KeyState,
|
_state: KeyState,
|
||||||
_serial: Serial,
|
_serial: Serial,
|
||||||
_time: u32,
|
_time: InputTime,
|
||||||
) {
|
) {
|
||||||
// TODO convert keys
|
// TODO convert keys
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,12 @@
|
||||||
use crate::{shell::grabs::SeatMoveGrabState, state::ClientState, utils::prelude::*};
|
use crate::{shell::grabs::SeatMoveGrabState, state::ClientState, utils::prelude::*};
|
||||||
use calloop::Interest;
|
use calloop::Interest;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::renderer::{
|
backend::{
|
||||||
element::{Kind, surface::KindEvaluation},
|
input::InputTime,
|
||||||
utils::{on_commit_buffer_handler, with_renderer_surface_state},
|
renderer::{
|
||||||
|
element::{Kind, surface::KindEvaluation},
|
||||||
|
utils::{on_commit_buffer_handler, with_renderer_surface_state},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
desktop::{LayerSurface, PopupKind, WindowSurfaceType, layer_map_for_output},
|
desktop::{LayerSurface, PopupKind, WindowSurfaceType, layer_map_for_output},
|
||||||
reexports::wayland_server::{Client, Resource, protocol::wl_surface::WlSurface},
|
reexports::wayland_server::{Client, Resource, protocol::wl_surface::WlSurface},
|
||||||
|
|
@ -325,9 +328,11 @@ impl CompositorHandler for State {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
std::mem::drop(shell);
|
std::mem::drop(shell);
|
||||||
seat.get_pointer()
|
seat.get_pointer().unwrap().unset_grab(
|
||||||
.unwrap()
|
self,
|
||||||
.unset_grab(self, SERIAL_COUNTER.next_serial(), 0);
|
SERIAL_COUNTER.next_serial(),
|
||||||
|
InputTime::now(),
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,18 +45,11 @@ pub fn surface_corners(states: &SurfaceData, size: Size<i32, Logical>) -> Option
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn surface_padding(states: &SurfaceData, size: Size<i32, Logical>) -> Option<[i32; 4]> {
|
pub fn surface_padding(states: &SurfaceData) -> Option<[i32; 4]> {
|
||||||
let mut guard = states.cached_state.get::<CacheablePadding>();
|
let mut guard = states.cached_state.get::<CacheablePadding>();
|
||||||
|
|
||||||
let padding = guard.current().0?;
|
let padding = guard.current().0?;
|
||||||
|
Some([padding.top, padding.right, padding.bottom, padding.left])
|
||||||
// guard against padding being too large
|
|
||||||
Some([
|
|
||||||
padding.top.min(size.h / 2),
|
|
||||||
padding.right.min(size.w / 2),
|
|
||||||
padding.bottom.min(size.h / 2),
|
|
||||||
padding.left.min(size.w / 2),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pad_rect(
|
pub fn pad_rect(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use std::{cell::RefCell, sync::Mutex};
|
use std::{cell::RefCell, sync::Mutex};
|
||||||
|
|
||||||
|
use cosmic_comp_config::DecorationPreference;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
desktop::Window,
|
desktop::Window,
|
||||||
reexports::{
|
reexports::{
|
||||||
|
|
@ -20,7 +21,7 @@ use smithay::{
|
||||||
};
|
};
|
||||||
use wayland_backend::protocol::WEnum;
|
use wayland_backend::protocol::WEnum;
|
||||||
|
|
||||||
use crate::state::State;
|
use crate::{shell::CosmicMapped, state::State};
|
||||||
|
|
||||||
pub struct PreferredDecorationMode(RefCell<Option<XdgMode>>);
|
pub struct PreferredDecorationMode(RefCell<Option<XdgMode>>);
|
||||||
|
|
||||||
|
|
@ -29,7 +30,7 @@ impl PreferredDecorationMode {
|
||||||
window
|
window
|
||||||
.user_data()
|
.user_data()
|
||||||
.get::<PreferredDecorationMode>()
|
.get::<PreferredDecorationMode>()
|
||||||
.is_none()
|
.is_none_or(|mode| mode.0.borrow().is_none())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mode(window: &Window) -> Option<XdgMode> {
|
pub fn mode(window: &Window) -> Option<XdgMode> {
|
||||||
|
|
@ -53,6 +54,72 @@ impl PreferredDecorationMode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn default_decoration(&self) -> DecorationPreference {
|
||||||
|
self.common.config.cosmic_conf.decoration_preference
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_decorations(&self) {
|
||||||
|
let shell = self.common.shell.read();
|
||||||
|
|
||||||
|
let update = |mapped: &CosmicMapped| {
|
||||||
|
let mode = if mapped.is_stack() {
|
||||||
|
XdgMode::ServerSide
|
||||||
|
} else {
|
||||||
|
XdgMode::from_preference(self.default_decoration())
|
||||||
|
};
|
||||||
|
for (window, _) in mapped.windows() {
|
||||||
|
if PreferredDecorationMode::is_unset(&window.0)
|
||||||
|
&& let Some(toplevel) = window.0.toplevel()
|
||||||
|
&& toplevel.with_committed_state(|state| {
|
||||||
|
state.is_some_and(|state| state.decoration_mode.is_some())
|
||||||
|
})
|
||||||
|
{
|
||||||
|
toplevel.with_pending_state(|state| {
|
||||||
|
state.decoration_mode = Some(mode);
|
||||||
|
});
|
||||||
|
toplevel.send_configure();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for set in shell.workspaces.sets.values() {
|
||||||
|
set.sticky_layer.mapped().for_each(update);
|
||||||
|
}
|
||||||
|
|
||||||
|
for space in shell.workspaces.spaces() {
|
||||||
|
space.mapped().for_each(update);
|
||||||
|
space
|
||||||
|
.minimized_windows
|
||||||
|
.iter()
|
||||||
|
.filter_map(|m| m.mapped())
|
||||||
|
.for_each(update);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait FromDecorationPreference {
|
||||||
|
fn from_preference(preference: DecorationPreference) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromDecorationPreference for XdgMode {
|
||||||
|
fn from_preference(preference: DecorationPreference) -> Self {
|
||||||
|
match preference {
|
||||||
|
DecorationPreference::ClientSide => XdgMode::ClientSide,
|
||||||
|
DecorationPreference::ServerSide => XdgMode::ServerSide,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromDecorationPreference for KdeMode {
|
||||||
|
fn from_preference(preference: DecorationPreference) -> Self {
|
||||||
|
match preference {
|
||||||
|
DecorationPreference::ClientSide => KdeMode::Client,
|
||||||
|
DecorationPreference::ServerSide => KdeMode::Server,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type KdeDecorationData = Mutex<KdeDecorationSurfaceState>;
|
pub type KdeDecorationData = Mutex<KdeDecorationSurfaceState>;
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct KdeDecorationSurfaceState {
|
pub struct KdeDecorationSurfaceState {
|
||||||
|
|
@ -67,7 +134,7 @@ impl XdgDecorationHandler for State {
|
||||||
let mode = if mapped.is_stack() {
|
let mode = if mapped.is_stack() {
|
||||||
XdgMode::ServerSide
|
XdgMode::ServerSide
|
||||||
} else {
|
} else {
|
||||||
XdgMode::ClientSide
|
XdgMode::from_preference(self.default_decoration())
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some((window, _)) = mapped
|
if let Some((window, _)) = mapped
|
||||||
|
|
@ -80,6 +147,10 @@ impl XdgDecorationHandler for State {
|
||||||
});
|
});
|
||||||
toplevel.send_configure();
|
toplevel.send_configure();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
toplevel.with_pending_state(|state| {
|
||||||
|
state.decoration_mode = Some(XdgMode::from_preference(self.default_decoration()))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -99,6 +170,13 @@ impl XdgDecorationHandler for State {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toplevel.with_pending_state(|state| state.decoration_mode = Some(mode));
|
toplevel.with_pending_state(|state| state.decoration_mode = Some(mode));
|
||||||
|
if let Some(pending) = shell
|
||||||
|
.pending_windows
|
||||||
|
.iter()
|
||||||
|
.find(|pending| pending.surface.0.toplevel().is_some_and(|t| t == &toplevel))
|
||||||
|
{
|
||||||
|
PreferredDecorationMode::update(&pending.surface.0, Some(mode));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,11 +188,28 @@ impl XdgDecorationHandler for State {
|
||||||
.find(|(window, _)| window.wl_surface().as_deref() == Some(toplevel.wl_surface()))
|
.find(|(window, _)| window.wl_surface().as_deref() == Some(toplevel.wl_surface()))
|
||||||
&& let Some(toplevel) = window.0.toplevel()
|
&& let Some(toplevel) = window.0.toplevel()
|
||||||
{
|
{
|
||||||
|
let mode = if mapped.is_stack() {
|
||||||
|
XdgMode::ServerSide
|
||||||
|
} else {
|
||||||
|
XdgMode::from_preference(self.default_decoration())
|
||||||
|
};
|
||||||
|
|
||||||
PreferredDecorationMode::update(&window.0, None);
|
PreferredDecorationMode::update(&window.0, None);
|
||||||
toplevel.with_pending_state(|state| {
|
toplevel.with_pending_state(|state| {
|
||||||
state.decoration_mode = None;
|
state.decoration_mode = Some(mode);
|
||||||
});
|
});
|
||||||
toplevel.send_configure();
|
toplevel.send_configure();
|
||||||
|
} else {
|
||||||
|
toplevel.with_pending_state(|state| {
|
||||||
|
state.decoration_mode = Some(XdgMode::from_preference(self.default_decoration()))
|
||||||
|
});
|
||||||
|
if let Some(pending) = shell
|
||||||
|
.pending_windows
|
||||||
|
.iter()
|
||||||
|
.find(|pending| pending.surface.0.toplevel().is_some_and(|t| t == &toplevel))
|
||||||
|
{
|
||||||
|
PreferredDecorationMode::update(&pending.surface.0, None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -129,10 +224,10 @@ impl KdeDecorationHandler for State {
|
||||||
if mapped.is_stack() {
|
if mapped.is_stack() {
|
||||||
KdeMode::Server
|
KdeMode::Server
|
||||||
} else {
|
} else {
|
||||||
KdeMode::Client
|
KdeMode::from_preference(self.default_decoration())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
KdeMode::Client
|
KdeMode::from_preference(self.default_decoration())
|
||||||
};
|
};
|
||||||
|
|
||||||
with_states(surface, |states| {
|
with_states(surface, |states| {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use std::{borrow::Borrow, collections::HashMap, sync::Mutex};
|
use std::{borrow::Borrow, sync::Mutex};
|
||||||
|
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
|
|
@ -452,23 +454,28 @@ fn constraints_for_renderer(
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|device| device.try_get_render_node().ok().flatten())
|
.and_then(|device| device.try_get_render_node().ok().flatten())
|
||||||
{
|
{
|
||||||
constraints.dma = Some(DmabufConstraints {
|
let mut formats = renderer
|
||||||
node,
|
.egl_context()
|
||||||
formats: renderer
|
.dmabuf_render_formats()
|
||||||
.egl_context()
|
.iter()
|
||||||
.dmabuf_render_formats()
|
.fold(
|
||||||
.iter()
|
IndexMap::<Fourcc, Vec<Modifier>>::new(),
|
||||||
.fold(
|
|mut map, format| {
|
||||||
HashMap::<Fourcc, Vec<Modifier>>::new(),
|
map.entry(format.code).or_default().push(format.modifier);
|
||||||
|mut map, format| {
|
map
|
||||||
map.entry(format.code).or_default().push(format.modifier);
|
},
|
||||||
map
|
)
|
||||||
},
|
.into_iter()
|
||||||
)
|
.collect::<Vec<_>>();
|
||||||
.into_iter()
|
formats.sort_by_key(|(code, _)| !is_preferred_capture_format(*code));
|
||||||
.collect::<Vec<_>>(),
|
constraints.dma = Some(DmabufConstraints { node, formats });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
constraints
|
constraints
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Formats most screencast consumers handle natively, offered first. WebRTC, for
|
||||||
|
/// example, swaps RGBA frames to BGRA on the CPU for every captured frame.
|
||||||
|
fn is_preferred_capture_format(code: Fourcc) -> bool {
|
||||||
|
matches!(code, Fourcc::Argb8888 | Fourcc::Xrgb8888)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
use smithay::input::keyboard::KeyboardHandle;
|
||||||
|
|
||||||
use crate::state::State;
|
use crate::state::State;
|
||||||
use crate::wayland::protocols::keyboard_layout::{KeyboardLayoutHandler, KeyboardLayoutState};
|
use crate::wayland::protocols::keyboard_layout::{KeyboardLayoutHandler, KeyboardLayoutState};
|
||||||
|
|
||||||
|
|
@ -7,4 +9,8 @@ impl KeyboardLayoutHandler for State {
|
||||||
fn keyboard_layout_state(&mut self) -> &mut KeyboardLayoutState {
|
fn keyboard_layout_state(&mut self) -> &mut KeyboardLayoutState {
|
||||||
&mut self.common.keyboard_layout_state
|
&mut self.common.keyboard_layout_state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn group_changed(&mut self, keyboard: &KeyboardHandle<Self>) {
|
||||||
|
self.broadcast_ei_keyboard_modifiers(keyboard);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,11 @@ pub fn set_all_surfaces_dpms_on(state: &mut State) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if changed {
|
if changed {
|
||||||
|
// Powering an output on because of input is activity. Without this the idle
|
||||||
|
// notifier stays idled, the idle daemon never sees `resumed`, and nothing
|
||||||
|
// powers the output off again until real input arrives.
|
||||||
|
let seat = state.common.shell.read().seats.last_active().clone();
|
||||||
|
state.common.idle_notifier_state.notify_activity(&seat);
|
||||||
OutputPowerState::refresh(state);
|
OutputPowerState::refresh(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,10 @@ use smithay::{
|
||||||
input::pointer::PointerHandle,
|
input::pointer::PointerHandle,
|
||||||
reexports::wayland_server::protocol::wl_surface::WlSurface,
|
reexports::wayland_server::protocol::wl_surface::WlSurface,
|
||||||
utils::{Logical, Point},
|
utils::{Logical, Point},
|
||||||
wayland::{pointer_constraints::PointerConstraintsHandler, seat::WaylandFocus},
|
wayland::{
|
||||||
|
pointer_constraints::{ConstraintRemove, PointerConstraintsHandler},
|
||||||
|
seat::WaylandFocus,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use smithay::wayland::pointer_constraints::{PointerConstraintRef, with_pointer_constraint};
|
pub use smithay::wayland::pointer_constraints::{PointerConstraintRef, with_pointer_constraint};
|
||||||
|
|
@ -76,23 +79,37 @@ impl PointerConstraintsHandler for State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_constraint(&mut self, surface: &WlSurface, pointer: &PointerHandle<Self>) {
|
fn remove_constraint(
|
||||||
if with_pointer_constraint(surface, pointer, |constraint| constraint.is_none()) {
|
&mut self,
|
||||||
let seat = self
|
surface: &WlSurface,
|
||||||
.common
|
pointer: &PointerHandle<Self>,
|
||||||
.shell
|
constraint_remove: ConstraintRemove,
|
||||||
.read()
|
) {
|
||||||
.seats
|
match constraint_remove {
|
||||||
.iter()
|
ConstraintRemove::PointerLeave(_) => {
|
||||||
.find(|s| s.get_pointer().as_ref() == Some(pointer))
|
// If the constraint was broken by the pointer forcibly leaving the surface, then it doesn't
|
||||||
.cloned();
|
// make much sense to warp it.
|
||||||
|
}
|
||||||
|
ConstraintRemove::Destroyed(constraint) => {
|
||||||
|
let Some(seat) = self
|
||||||
|
.common
|
||||||
|
.shell
|
||||||
|
.read()
|
||||||
|
.seats
|
||||||
|
.iter()
|
||||||
|
.find(|s| s.get_pointer().as_ref() == Some(pointer))
|
||||||
|
.cloned()
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some((hint_surface, hint_location)) = seat.pointer_constraint_hint() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(seat) = seat
|
if hint_surface == *surface {
|
||||||
&& let Some((hint_surface, hint_location)) = seat.pointer_constraint_hint()
|
self.apply_cursor_hint(surface, pointer, hint_location, Some(&constraint));
|
||||||
&& hint_surface == *surface
|
seat.set_pointer_constraint_hint(None);
|
||||||
{
|
}
|
||||||
self.apply_cursor_hint(surface, pointer, hint_location);
|
|
||||||
seat.set_pointer_constraint_hint(None);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ impl PointerWarpHandler for State {
|
||||||
drop(shell);
|
drop(shell);
|
||||||
|
|
||||||
if let Some(pointer_handle) = pointer_handle {
|
if let Some(pointer_handle) = pointer_handle {
|
||||||
self.apply_cursor_hint(&surface, &pointer_handle, pos);
|
self.apply_cursor_hint(&surface, &pointer_handle, pos, None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,11 @@ impl SessionLockHandler for State {
|
||||||
let mut shell = self.common.shell.write();
|
let mut shell = self.common.shell.write();
|
||||||
shell.session_lock = None;
|
shell.session_lock = None;
|
||||||
|
|
||||||
|
let seats = shell.seats.iter().cloned().collect::<Vec<_>>();
|
||||||
|
for seat in &seats {
|
||||||
|
self.common.idle_notifier_state.notify_activity(seat);
|
||||||
|
}
|
||||||
|
|
||||||
for output in shell.outputs() {
|
for output in shell.outputs() {
|
||||||
self.backend.schedule_render(output);
|
self.backend.schedule_render(output);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use crate::state::State;
|
use crate::{shell::focus::target::PointerFocusTarget, state::State};
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::input::TabletToolDescriptor, input::pointer::CursorImageStatus,
|
backend::input::TabletToolDescriptor, input::pointer::CursorImageStatus,
|
||||||
input::tablet::TabletSeatHandler, reexports::wayland_server::protocol::wl_surface,
|
input::tablet::TabletSeatHandler,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl TabletSeatHandler for State {
|
impl TabletSeatHandler for State {
|
||||||
type ToolFocus = wl_surface::WlSurface;
|
type ToolFocus = PointerFocusTarget;
|
||||||
|
|
||||||
fn tablet_tool_image(&mut self, _tool: &TabletToolDescriptor, _image: CursorImageStatus) {
|
fn tablet_tool_image(&mut self, _tool: &TabletToolDescriptor, _image: CursorImageStatus) {
|
||||||
// TODO display cursor for each tablet tool
|
// TODO display cursor for each tablet tool
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
|
backend::input::InputTime,
|
||||||
desktop::{WindowSurfaceType, layer_map_for_output},
|
desktop::{WindowSurfaceType, layer_map_for_output},
|
||||||
input::{Seat, pointer::MotionEvent},
|
input::{Seat, pointer::MotionEvent},
|
||||||
output::Output,
|
output::Output,
|
||||||
|
|
@ -124,7 +125,7 @@ impl ToplevelManagementHandler for State {
|
||||||
&MotionEvent {
|
&MotionEvent {
|
||||||
location: new_pos.to_f64().as_logical(),
|
location: new_pos.to_f64().as_logical(),
|
||||||
serial,
|
serial,
|
||||||
time: self.common.clock.now().as_millis(),
|
time: InputTime::now(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
ptr.frame(self);
|
ptr.frame(self);
|
||||||
|
|
|
||||||
|
|
@ -243,7 +243,7 @@ impl State {
|
||||||
{
|
{
|
||||||
for mapped in current_workspace
|
for mapped in current_workspace
|
||||||
.mapped()
|
.mapped()
|
||||||
.filter(|m| m.maximized_state.lock().unwrap().is_some())
|
.filter(|m| m.maximized_state.lock().unwrap().is_some() && *m != &element)
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
shell::{CosmicSurface, PendingWindow, focus::target::KeyboardFocusTarget, grabs::ReleaseMode},
|
shell::{
|
||||||
|
CosmicSurface, PendingWindow,
|
||||||
|
focus::target::KeyboardFocusTarget,
|
||||||
|
grabs::{GrabType, ReleaseMode},
|
||||||
|
},
|
||||||
utils::prelude::*,
|
utils::prelude::*,
|
||||||
};
|
};
|
||||||
use smithay::desktop::layer_map_for_output;
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
|
backend::input::InputTime,
|
||||||
desktop::{
|
desktop::{
|
||||||
PopupGrab, PopupKeyboardGrab, PopupKind, PopupPointerGrab, PopupUngrabStrategy,
|
PopupGrab, PopupKeyboardGrab, PopupKind, PopupPointerGrab, PopupUngrabStrategy,
|
||||||
WindowSurfaceType, find_popup_root_surface,
|
WindowSurfaceType, find_popup_root_surface,
|
||||||
|
|
@ -26,6 +30,7 @@ use smithay::{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use smithay::{desktop::layer_map_for_output, input::tablet::TabletSeatTrait};
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
|
|
@ -50,6 +55,7 @@ impl XdgShellHandler for State {
|
||||||
surface,
|
surface,
|
||||||
seat,
|
seat,
|
||||||
fullscreen: None,
|
fullscreen: None,
|
||||||
|
minimized: false,
|
||||||
maximized: false,
|
maximized: false,
|
||||||
sticky: false,
|
sticky: false,
|
||||||
})
|
})
|
||||||
|
|
@ -189,12 +195,17 @@ impl XdgShellHandler for State {
|
||||||
true,
|
true,
|
||||||
) {
|
) {
|
||||||
std::mem::drop(shell);
|
std::mem::drop(shell);
|
||||||
if grab.is_touch_grab() {
|
match grab.grab_type() {
|
||||||
seat.get_touch().unwrap().set_grab(self, grab, serial);
|
GrabType::Touch => seat.get_touch().unwrap().set_grab(self, grab, serial),
|
||||||
} else {
|
GrabType::Pointer => seat
|
||||||
seat.get_pointer()
|
.get_pointer()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.set_grab(self, grab, serial, focus)
|
.set_grab(self, grab, serial, focus),
|
||||||
|
GrabType::TabletTool => seat
|
||||||
|
.tablet_seat()
|
||||||
|
.get_tool(grab.tool().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.set_grab(self, grab, InputTime::now(), serial, focus),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -217,12 +228,17 @@ impl XdgShellHandler for State {
|
||||||
true,
|
true,
|
||||||
) {
|
) {
|
||||||
std::mem::drop(shell);
|
std::mem::drop(shell);
|
||||||
if grab.is_touch_grab() {
|
match grab.grab_type() {
|
||||||
seat.get_touch().unwrap().set_grab(self, grab, serial)
|
GrabType::Touch => seat.get_touch().unwrap().set_grab(self, grab, serial),
|
||||||
} else {
|
GrabType::Pointer => seat
|
||||||
seat.get_pointer()
|
.get_pointer()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.set_grab(self, grab, serial, focus)
|
.set_grab(self, grab, serial, focus),
|
||||||
|
GrabType::TabletTool => seat
|
||||||
|
.tablet_seat()
|
||||||
|
.get_tool(grab.tool().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.set_grab(self, grab, InputTime::now(), serial, focus),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,19 @@ use cosmic_protocols::corner_radius::v1::server::cosmic_corner_radius_toplevel_v
|
||||||
use cosmic_protocols::corner_radius::v1::server::{
|
use cosmic_protocols::corner_radius::v1::server::{
|
||||||
cosmic_corner_radius_manager_v1, cosmic_corner_radius_toplevel_v1,
|
cosmic_corner_radius_manager_v1, cosmic_corner_radius_toplevel_v1,
|
||||||
};
|
};
|
||||||
use smithay::desktop::utils::bbox_from_surface_tree;
|
use smithay::backend::renderer::buffer_dimensions;
|
||||||
|
use smithay::backend::renderer::utils::RendererSurfaceStateUserData;
|
||||||
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_popup::XdgPopup;
|
use smithay::reexports::wayland_protocols::xdg::shell::server::xdg_popup::XdgPopup;
|
||||||
use smithay::reexports::wayland_protocols_wlr::layer_shell::v1::server::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1;
|
use smithay::reexports::wayland_protocols_wlr::layer_shell::v1::server::zwlr_layer_surface_v1::ZwlrLayerSurfaceV1;
|
||||||
use smithay::reexports::wayland_server::New;
|
use smithay::reexports::wayland_server::New;
|
||||||
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface;
|
||||||
use smithay::utils::{HookId, Logical, Point, Rectangle};
|
use smithay::utils::{HookId, Logical, Rectangle};
|
||||||
use smithay::wayland::compositor::Cacheable;
|
|
||||||
use smithay::wayland::compositor::add_pre_commit_hook;
|
use smithay::wayland::compositor::add_pre_commit_hook;
|
||||||
use smithay::wayland::compositor::with_states;
|
use smithay::wayland::compositor::with_states;
|
||||||
|
use smithay::wayland::compositor::{BufferAssignment, Cacheable, SurfaceAttributes};
|
||||||
use smithay::wayland::shell::wlr_layer::WlrLayerShellHandler;
|
use smithay::wayland::shell::wlr_layer::WlrLayerShellHandler;
|
||||||
use smithay::wayland::shell::xdg::{SurfaceCachedState, XdgShellSurfaceUserData};
|
use smithay::wayland::shell::xdg::{SurfaceCachedState, XdgShellSurfaceUserData};
|
||||||
|
use smithay::wayland::viewporter::ViewportCachedState;
|
||||||
use smithay::{
|
use smithay::{
|
||||||
reexports::{
|
reexports::{
|
||||||
wayland_protocols::xdg::shell::server::xdg_toplevel::XdgToplevel,
|
wayland_protocols::xdg::shell::server::xdg_toplevel::XdgToplevel,
|
||||||
|
|
@ -655,7 +657,6 @@ fn pad_rect(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn layer_radius_hook<D: 'static>(_state: &mut D, _dh: &DisplayHandle, surface: &WlSurface) {
|
fn layer_radius_hook<D: 'static>(_state: &mut D, _dh: &DisplayHandle, surface: &WlSurface) {
|
||||||
let bbox = bbox_from_surface_tree(surface, Point::default());
|
|
||||||
with_states(surface, |surface_data| {
|
with_states(surface, |surface_data| {
|
||||||
let corners = *surface_data
|
let corners = *surface_data
|
||||||
.cached_state
|
.cached_state
|
||||||
|
|
@ -665,8 +666,40 @@ fn layer_radius_hook<D: 'static>(_state: &mut D, _dh: &DisplayHandle, surface: &
|
||||||
.cached_state
|
.cached_state
|
||||||
.get::<CacheablePadding>()
|
.get::<CacheablePadding>()
|
||||||
.pending();
|
.pending();
|
||||||
|
|
||||||
|
// The wl_surface size after this commit is:
|
||||||
|
// - viewport destination if set
|
||||||
|
// - else the logical size of the buffer that will be current attached now
|
||||||
|
// - else existing
|
||||||
|
let size = surface_data
|
||||||
|
.cached_state
|
||||||
|
.get::<ViewportCachedState>()
|
||||||
|
.pending()
|
||||||
|
.size()
|
||||||
|
.or_else(|| {
|
||||||
|
let mut guard = surface_data.cached_state.get::<SurfaceAttributes>();
|
||||||
|
let attrs = guard.pending();
|
||||||
|
match &attrs.buffer {
|
||||||
|
Some(BufferAssignment::NewBuffer(buffer)) => buffer_dimensions(buffer)
|
||||||
|
.map(|d| d.to_logical(attrs.buffer_scale, attrs.buffer_transform.into())),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
surface_data
|
||||||
|
.data_map
|
||||||
|
.get::<RendererSurfaceStateUserData>()
|
||||||
|
.and_then(|state| state.lock().unwrap().surface_size())
|
||||||
|
});
|
||||||
|
let Some(size) = size else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
let empty = Padding::default();
|
let empty = Padding::default();
|
||||||
let Some(padded_box) = pad_rect(bbox, padding.0.as_ref().unwrap_or(&empty)) else {
|
let Some(padded_box) = pad_rect(
|
||||||
|
Rectangle::from_size(size),
|
||||||
|
padding.0.as_ref().unwrap_or(&empty),
|
||||||
|
) else {
|
||||||
if let Some(hook) = surface_data.data_map.get::<LayerHookId>() {
|
if let Some(hook) = surface_data.data_map.get::<LayerHookId>() {
|
||||||
let hook_ref = hook.lock().unwrap();
|
let hook_ref = hook.lock().unwrap();
|
||||||
if let Some((_, obj)) = hook_ref.as_ref()
|
if let Some((_, obj)) = hook_ref.as_ref()
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,10 @@ use smithay::{
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use wayland_backend::server::{ClientId, GlobalId};
|
use wayland_backend::server::{ClientId, GlobalId};
|
||||||
|
|
||||||
pub trait KeyboardLayoutHandler {
|
pub trait KeyboardLayoutHandler: SeatHandler {
|
||||||
fn keyboard_layout_state(&mut self) -> &mut KeyboardLayoutState;
|
fn keyboard_layout_state(&mut self) -> &mut KeyboardLayoutState;
|
||||||
|
/// Group has been changed through the protocol
|
||||||
|
fn group_changed(&mut self, keyboard: &KeyboardHandle<Self>);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -57,6 +59,7 @@ impl KeyboardLayoutState {
|
||||||
let active_layout = handle.with_xkb_state(state, |context| {
|
let active_layout = handle.with_xkb_state(state, |context| {
|
||||||
context.xkb().lock().unwrap().active_layout()
|
context.xkb().lock().unwrap().active_layout()
|
||||||
});
|
});
|
||||||
|
state.group_changed(handle);
|
||||||
if *last_layout != Some(active_layout) {
|
if *last_layout != Some(active_layout) {
|
||||||
keyboard_layout.group(active_layout.0);
|
keyboard_layout.group(active_layout.0);
|
||||||
*last_layout = Some(active_layout);
|
*last_layout = Some(active_layout);
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ pub mod keyboard_layout;
|
||||||
pub mod output_configuration;
|
pub mod output_configuration;
|
||||||
pub mod output_power;
|
pub mod output_power;
|
||||||
pub mod overlap_notify;
|
pub mod overlap_notify;
|
||||||
|
pub mod session_lock_layer;
|
||||||
pub mod toplevel_info;
|
pub mod toplevel_info;
|
||||||
pub mod toplevel_management;
|
pub mod toplevel_management;
|
||||||
pub mod workspace;
|
pub mod workspace;
|
||||||
|
|
|
||||||
136
src/wayland/protocols/session_lock_layer.rs
Normal file
136
src/wayland/protocols/session_lock_layer.rs
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
use cosmic_protocols::session_lock_layer::v1::server::cosmic_session_lock_layer_manager_v1;
|
||||||
|
use smithay::{
|
||||||
|
reexports::wayland_server::{
|
||||||
|
Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, backend::GlobalId,
|
||||||
|
protocol::wl_surface,
|
||||||
|
},
|
||||||
|
wayland::{
|
||||||
|
Dispatch2, GlobalDispatch2,
|
||||||
|
compositor::{Cacheable, with_states},
|
||||||
|
shell::wlr_layer::WlrLayerShellHandler,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SessionLockLayerData;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SessionLockLayerState {
|
||||||
|
global: GlobalId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionLockLayerState {
|
||||||
|
pub fn new<D, F>(dh: &DisplayHandle, client_filter: F) -> Self
|
||||||
|
where
|
||||||
|
F: for<'a> Fn(&'a Client) -> bool + Clone + Send + Sync + 'static,
|
||||||
|
D: GlobalDispatch<
|
||||||
|
cosmic_session_lock_layer_manager_v1::CosmicSessionLockLayerManagerV1,
|
||||||
|
SessionLockLayerGlobalData,
|
||||||
|
> + 'static,
|
||||||
|
{
|
||||||
|
let global = dh
|
||||||
|
.create_global::<D, cosmic_session_lock_layer_manager_v1::CosmicSessionLockLayerManagerV1, _>(
|
||||||
|
1,
|
||||||
|
SessionLockLayerGlobalData {
|
||||||
|
filter: Box::new(client_filter.clone()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
Self { global }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn global_id(&self) -> GlobalId {
|
||||||
|
self.global.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct SessionLockLayerGlobalData {
|
||||||
|
filter: Box<dyn for<'a> Fn(&'a Client) -> bool + Send + Sync>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D> GlobalDispatch2<cosmic_session_lock_layer_manager_v1::CosmicSessionLockLayerManagerV1, D>
|
||||||
|
for SessionLockLayerGlobalData
|
||||||
|
where
|
||||||
|
D: Dispatch<
|
||||||
|
cosmic_session_lock_layer_manager_v1::CosmicSessionLockLayerManagerV1,
|
||||||
|
SessionLockLayerData,
|
||||||
|
>,
|
||||||
|
{
|
||||||
|
fn bind(
|
||||||
|
&self,
|
||||||
|
_state: &mut D,
|
||||||
|
_handle: &DisplayHandle,
|
||||||
|
_client: &Client,
|
||||||
|
resource: New<cosmic_session_lock_layer_manager_v1::CosmicSessionLockLayerManagerV1>,
|
||||||
|
data_init: &mut DataInit<'_, D>,
|
||||||
|
) {
|
||||||
|
data_init.init(resource, SessionLockLayerData);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_view(&self, client: &Client) -> bool {
|
||||||
|
(self.filter)(client)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
struct SessionLockLayerSurfaceData {
|
||||||
|
show_on_lock: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cacheable for SessionLockLayerSurfaceData {
|
||||||
|
fn commit(&mut self, _dh: &DisplayHandle) -> Self {
|
||||||
|
*self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn merge_into(self, into: &mut Self, _dh: &DisplayHandle) {
|
||||||
|
*into = self;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn layer_show_on_lock(wl_surface: &wl_surface::WlSurface) -> bool {
|
||||||
|
with_states(wl_surface, |states| {
|
||||||
|
if states.cached_state.has::<SessionLockLayerSurfaceData>() {
|
||||||
|
let mut state = states.cached_state.get::<SessionLockLayerSurfaceData>();
|
||||||
|
state.current().show_on_lock
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D> Dispatch2<cosmic_session_lock_layer_manager_v1::CosmicSessionLockLayerManagerV1, D>
|
||||||
|
for SessionLockLayerData
|
||||||
|
where
|
||||||
|
D: WlrLayerShellHandler,
|
||||||
|
{
|
||||||
|
fn request(
|
||||||
|
&self,
|
||||||
|
state: &mut D,
|
||||||
|
_: &Client,
|
||||||
|
_: &cosmic_session_lock_layer_manager_v1::CosmicSessionLockLayerManagerV1,
|
||||||
|
request: cosmic_session_lock_layer_manager_v1::Request,
|
||||||
|
_: &DisplayHandle,
|
||||||
|
_: &mut DataInit<'_, D>,
|
||||||
|
) {
|
||||||
|
let (layer, value) = match request {
|
||||||
|
cosmic_session_lock_layer_manager_v1::Request::SetShowOnLock { layer } => (layer, true),
|
||||||
|
cosmic_session_lock_layer_manager_v1::Request::UnsetShowOnLock { layer } => {
|
||||||
|
(layer, false)
|
||||||
|
}
|
||||||
|
cosmic_session_lock_layer_manager_v1::Request::Destroy => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(layer) = state
|
||||||
|
.shell_state()
|
||||||
|
.layer_surfaces()
|
||||||
|
.find(|surface| surface.shell_surface() == &layer)
|
||||||
|
{
|
||||||
|
with_states(layer.wl_surface(), |states| {
|
||||||
|
let mut state = states.cached_state.get::<SessionLockLayerSurfaceData>();
|
||||||
|
state.pending().show_on_lock = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -418,6 +418,12 @@ where
|
||||||
handle.closed();
|
handle.closed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Remove the foreign handle too, so new subscribers cannot
|
||||||
|
// discover a toplevel whose window has already been destroyed.
|
||||||
|
if let Some(handle) = state.foreign_handle.take() {
|
||||||
|
self.foreign_toplevel_list.remove_toplevel(&handle);
|
||||||
|
}
|
||||||
|
*state = Default::default();
|
||||||
dirty = true;
|
dirty = true;
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,9 @@ use std::{
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::render::cursor::{Cursor, load_cursor_env, load_cursor_theme},
|
backend::render::cursor::{Cursor, load_cursor_env, load_cursor_theme},
|
||||||
shell::{
|
shell::{
|
||||||
CosmicSurface, PendingWindow, Shell, focus::target::KeyboardFocusTarget, grabs::ReleaseMode,
|
CosmicSurface, PendingWindow, Shell,
|
||||||
|
focus::target::KeyboardFocusTarget,
|
||||||
|
grabs::{GrabType, ReleaseMode},
|
||||||
},
|
},
|
||||||
state::State,
|
state::State,
|
||||||
utils::prelude::*,
|
utils::prelude::*,
|
||||||
|
|
@ -20,7 +22,7 @@ use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
allocator::Fourcc,
|
allocator::Fourcc,
|
||||||
drm::DrmNode,
|
drm::DrmNode,
|
||||||
input::{ButtonState, KeyState, Keycode},
|
input::{ButtonState, InputTime, KeyState, Keycode},
|
||||||
renderer::{
|
renderer::{
|
||||||
Bind, Frame, Offscreen, Renderer,
|
Bind, Frame, Offscreen, Renderer,
|
||||||
element::{
|
element::{
|
||||||
|
|
@ -32,7 +34,7 @@ use smithay::{
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
desktop::space::SpaceElement,
|
desktop::space::SpaceElement,
|
||||||
input::{keyboard::ModifiersState, pointer::CursorIcon},
|
input::{keyboard::ModifiersState, pointer::CursorIcon, tablet::TabletSeatTrait},
|
||||||
reexports::{wayland_server::Client, x11rb::protocol::xproto::Window as X11Window},
|
reexports::{wayland_server::Client, x11rb::protocol::xproto::Window as X11Window},
|
||||||
utils::{
|
utils::{
|
||||||
Buffer as BufferCoords, Logical, Point, Rectangle, SERIAL_COUNTER, Serial, Size, Transform,
|
Buffer as BufferCoords, Logical, Point, Rectangle, SERIAL_COUNTER, Serial, Size, Transform,
|
||||||
|
|
@ -396,7 +398,7 @@ impl Common {
|
||||||
state: KeyState,
|
state: KeyState,
|
||||||
modifiers: ModifiersState,
|
modifiers: ModifiersState,
|
||||||
serial: Serial,
|
serial: Serial,
|
||||||
time: u32,
|
time: InputTime,
|
||||||
) {
|
) {
|
||||||
let config = self.config.cosmic_conf.xwayland_eavesdropping.keyboard;
|
let config = self.config.cosmic_conf.xwayland_eavesdropping.keyboard;
|
||||||
if config == EavesdroppingKeyboardMode::None {
|
if config == EavesdroppingKeyboardMode::None {
|
||||||
|
|
@ -458,7 +460,7 @@ impl Common {
|
||||||
|
|
||||||
tracing::trace!("Forwaring key {} {:?} to xwayland", code.raw() - 8, state);
|
tracing::trace!("Forwaring key {} {:?} to xwayland", code.raw() - 8, state);
|
||||||
for wl_keyboard in keyboard.client_keyboards(&xstate.client) {
|
for wl_keyboard in keyboard.client_keyboards(&xstate.client) {
|
||||||
wl_keyboard.key(serial.into(), time, code.raw() - 8, state.into());
|
wl_keyboard.key(serial.into(), time.millis(), code.raw() - 8, state.into());
|
||||||
if xstate.last_modifier_state != Some(modifiers) {
|
if xstate.last_modifier_state != Some(modifiers) {
|
||||||
xstate.last_modifier_state = Some(modifiers);
|
xstate.last_modifier_state = Some(modifiers);
|
||||||
wl_keyboard.modifiers(
|
wl_keyboard.modifiers(
|
||||||
|
|
@ -478,7 +480,7 @@ impl Common {
|
||||||
button: u32,
|
button: u32,
|
||||||
state: ButtonState,
|
state: ButtonState,
|
||||||
serial: Serial,
|
serial: Serial,
|
||||||
time: u32,
|
time: InputTime,
|
||||||
) {
|
) {
|
||||||
if !self.config.cosmic_conf.xwayland_eavesdropping.pointer {
|
if !self.config.cosmic_conf.xwayland_eavesdropping.pointer {
|
||||||
return;
|
return;
|
||||||
|
|
@ -518,7 +520,7 @@ impl Common {
|
||||||
|
|
||||||
tracing::trace!("Forwaring ptr button {} {:?} to Xwayland", button, state);
|
tracing::trace!("Forwaring ptr button {} {:?} to Xwayland", button, state);
|
||||||
for wl_pointer in pointer.client_pointers(&xstate.client) {
|
for wl_pointer in pointer.client_pointers(&xstate.client) {
|
||||||
wl_pointer.button(serial.into(), time, button, state.into());
|
wl_pointer.button(serial.into(), time.millis(), button, state.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -808,6 +810,7 @@ impl XwmHandler for State {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
let fullscreen = window.is_fullscreen().then(|| seat.active_output());
|
let fullscreen = window.is_fullscreen().then(|| seat.active_output());
|
||||||
|
let minimized = window.is_hidden();
|
||||||
let maximized = window.is_maximized();
|
let maximized = window.is_maximized();
|
||||||
if let Some(pending) = shell
|
if let Some(pending) = shell
|
||||||
.pending_windows
|
.pending_windows
|
||||||
|
|
@ -816,6 +819,7 @@ impl XwmHandler for State {
|
||||||
{
|
{
|
||||||
pending.seat = seat;
|
pending.seat = seat;
|
||||||
pending.fullscreen = fullscreen;
|
pending.fullscreen = fullscreen;
|
||||||
|
pending.minimized = minimized;
|
||||||
pending.maximized = maximized;
|
pending.maximized = maximized;
|
||||||
} else {
|
} else {
|
||||||
let surface = CosmicSurface::from(window);
|
let surface = CosmicSurface::from(window);
|
||||||
|
|
@ -823,6 +827,7 @@ impl XwmHandler for State {
|
||||||
surface,
|
surface,
|
||||||
seat,
|
seat,
|
||||||
fullscreen,
|
fullscreen,
|
||||||
|
minimized,
|
||||||
maximized,
|
maximized,
|
||||||
sticky: false,
|
sticky: false,
|
||||||
})
|
})
|
||||||
|
|
@ -1042,17 +1047,29 @@ impl XwmHandler for State {
|
||||||
true,
|
true,
|
||||||
) {
|
) {
|
||||||
std::mem::drop(shell);
|
std::mem::drop(shell);
|
||||||
if grab.is_touch_grab() {
|
match grab.grab_type() {
|
||||||
seat.get_touch()
|
GrabType::Touch => {
|
||||||
.unwrap()
|
seat.get_touch()
|
||||||
.set_grab(self, grab, SERIAL_COUNTER.next_serial())
|
.unwrap()
|
||||||
} else {
|
.set_grab(self, grab, SERIAL_COUNTER.next_serial())
|
||||||
seat.get_pointer().unwrap().set_grab(
|
}
|
||||||
|
GrabType::Pointer => seat.get_pointer().unwrap().set_grab(
|
||||||
self,
|
self,
|
||||||
grab,
|
grab,
|
||||||
SERIAL_COUNTER.next_serial(),
|
SERIAL_COUNTER.next_serial(),
|
||||||
focus,
|
focus,
|
||||||
)
|
),
|
||||||
|
GrabType::TabletTool => seat
|
||||||
|
.tablet_seat()
|
||||||
|
.get_tool(grab.tool().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.set_grab(
|
||||||
|
self,
|
||||||
|
grab,
|
||||||
|
InputTime::now(),
|
||||||
|
SERIAL_COUNTER.next_serial(),
|
||||||
|
focus,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1073,17 +1090,29 @@ impl XwmHandler for State {
|
||||||
true,
|
true,
|
||||||
) {
|
) {
|
||||||
std::mem::drop(shell);
|
std::mem::drop(shell);
|
||||||
if grab.is_touch_grab() {
|
match grab.grab_type() {
|
||||||
seat.get_touch()
|
GrabType::Touch => {
|
||||||
.unwrap()
|
seat.get_touch()
|
||||||
.set_grab(self, grab, SERIAL_COUNTER.next_serial())
|
.unwrap()
|
||||||
} else {
|
.set_grab(self, grab, SERIAL_COUNTER.next_serial())
|
||||||
seat.get_pointer().unwrap().set_grab(
|
}
|
||||||
|
GrabType::Pointer => seat.get_pointer().unwrap().set_grab(
|
||||||
self,
|
self,
|
||||||
grab,
|
grab,
|
||||||
SERIAL_COUNTER.next_serial(),
|
SERIAL_COUNTER.next_serial(),
|
||||||
focus,
|
focus,
|
||||||
)
|
),
|
||||||
|
GrabType::TabletTool => seat
|
||||||
|
.tablet_seat()
|
||||||
|
.get_tool(grab.tool().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.set_grab(
|
||||||
|
self,
|
||||||
|
grab,
|
||||||
|
InputTime::now(),
|
||||||
|
SERIAL_COUNTER.next_serial(),
|
||||||
|
focus,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue