Compare commits
No commits in common. "local/yoda-main" and "post-1.8-merge-20260910" have entirely different histories.
local/yoda
...
post-1.8-m
32 changed files with 298 additions and 1139 deletions
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
|
@ -1,10 +1,8 @@
|
|||
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.
|
||||
- [ ] I have disclosed use of any AI generated code in my commit messages.
|
||||
- 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,12 +13,18 @@ jobs:
|
|||
- name: Checkout sources
|
||||
uses: actions/checkout@v6
|
||||
- name: Rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
rustflags: ""
|
||||
- name: Cargo cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
|
||||
- name: Format
|
||||
run: cargo fmt --all -- --check
|
||||
run: cargo +stable fmt --all -- --check
|
||||
|
||||
clippy-check:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -26,14 +32,20 @@ jobs:
|
|||
- name: Checkout sources
|
||||
uses: actions/checkout@v6
|
||||
- name: Rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: clippy
|
||||
rustflags: ""
|
||||
- name: Cargo cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
|
||||
- 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
|
||||
- name: Clippy
|
||||
run: cargo clippy --all-features -- -D warnings
|
||||
run: cargo +stable clippy --all-features -- -D warnings
|
||||
|
||||
check-features:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -41,15 +53,20 @@ jobs:
|
|||
- name: Checkout sources
|
||||
uses: actions/checkout@v6
|
||||
- name: Rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Cargo cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
rustflags: ""
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
|
||||
- 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: cargo check --no-default-features
|
||||
- run: cargo check --features debug
|
||||
- run: cargo check --features profile-with-tracy
|
||||
- run: cargo check --features profile-with-tracy-gpu
|
||||
- run: cargo +stable check --no-default-features
|
||||
- run: cargo +stable check --features debug
|
||||
- run: cargo +stable check --features profile-with-tracy
|
||||
- run: cargo +stable check --features profile-with-tracy-gpu
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -57,11 +74,21 @@ jobs:
|
|||
- name: Checkout sources
|
||||
uses: actions/checkout@v6
|
||||
- name: Rust toolchain
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- name: Cargo cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
rustflags: ""
|
||||
path: |
|
||||
~/.cargo/registry
|
||||
~/.cargo/git
|
||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.toml') }}
|
||||
- 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
|
||||
- name: Build cache
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: target
|
||||
key: ${{ runner.os }}-build-${{ hashFiles('**/Cargo.toml') }}
|
||||
- name: Build and install Libseat
|
||||
run: |
|
||||
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
14
.github/workflows/require-checklist.yml
vendored
|
|
@ -1,14 +0,0 @@
|
|||
# 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
|
||||
39
Cargo.lock
generated
39
Cargo.lock
generated
|
|
@ -21,7 +21,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
|||
[[package]]
|
||||
name = "accesskit"
|
||||
version = "0.22.0"
|
||||
source = "git+https://github.com/wash2/accesskit?rev=6c20249#6c20249d3b9fd45e7002efad9f6b6171a9257912"
|
||||
source = "git+https://github.com/wash2/accesskit?tag=cosmic-0.14#f0599eed5f18111228266fe3f28991cc48b5964f"
|
||||
dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
|
@ -29,7 +29,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "accesskit_consumer"
|
||||
version = "0.32.0"
|
||||
source = "git+https://github.com/wash2/accesskit?rev=6c20249#6c20249d3b9fd45e7002efad9f6b6171a9257912"
|
||||
source = "git+https://github.com/wash2/accesskit?tag=cosmic-0.14#f0599eed5f18111228266fe3f28991cc48b5964f"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"hashbrown 0.16.1",
|
||||
|
|
@ -38,7 +38,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "accesskit_macos"
|
||||
version = "0.23.0"
|
||||
source = "git+https://github.com/wash2/accesskit?rev=6c20249#6c20249d3b9fd45e7002efad9f6b6171a9257912"
|
||||
source = "git+https://github.com/wash2/accesskit?tag=cosmic-0.14#f0599eed5f18111228266fe3f28991cc48b5964f"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_consumer",
|
||||
|
|
@ -51,7 +51,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "accesskit_windows"
|
||||
version = "0.30.0"
|
||||
source = "git+https://github.com/wash2/accesskit?rev=6c20249#6c20249d3b9fd45e7002efad9f6b6171a9257912"
|
||||
source = "git+https://github.com/wash2/accesskit?tag=cosmic-0.14#f0599eed5f18111228266fe3f28991cc48b5964f"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_consumer",
|
||||
|
|
@ -64,7 +64,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "accesskit_winit"
|
||||
version = "0.30.0"
|
||||
source = "git+https://github.com/wash2/accesskit?rev=6c20249#6c20249d3b9fd45e7002efad9f6b6171a9257912"
|
||||
source = "git+https://github.com/wash2/accesskit?tag=cosmic-0.14#f0599eed5f18111228266fe3f28991cc48b5964f"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_macos",
|
||||
|
|
@ -1012,7 +1012,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cosmic-randr-shell"
|
||||
version = "1.10.0"
|
||||
version = "1.3.0"
|
||||
dependencies = [
|
||||
"kdl",
|
||||
"slotmap",
|
||||
|
|
@ -1021,7 +1021,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cosmic-settings-config"
|
||||
version = "1.10.0"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmic-config",
|
||||
"ron 0.11.0",
|
||||
|
|
@ -1040,7 +1040,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cosmic-settings-daemon-config"
|
||||
version = "1.10.0"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmic-config",
|
||||
"cosmic-theme",
|
||||
|
|
@ -1564,7 +1564,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1963,8 +1963,8 @@ dependencies = [
|
|||
"libc",
|
||||
"log",
|
||||
"rustversion",
|
||||
"windows-link 0.1.3",
|
||||
"windows-result 0.3.4",
|
||||
"windows-link 0.2.1",
|
||||
"windows-result 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2329,7 +2329,7 @@ dependencies = [
|
|||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core 0.61.2",
|
||||
"windows-core 0.62.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2833,7 +2833,7 @@ dependencies = [
|
|||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde_core",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3072,7 +3072,6 @@ dependencies = [
|
|||
"log",
|
||||
"palette",
|
||||
"phf 0.13.1",
|
||||
"roxmltree 0.20.0",
|
||||
"rust-embed",
|
||||
"serde",
|
||||
"slotmap",
|
||||
|
|
@ -3136,9 +3135,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
|||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.25"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d"
|
||||
checksum = "8d8f1ea3f21fd3405dcaf6c9b5c1630af9afc422d9073ea39c5f6d6c772e08ed"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"libc",
|
||||
|
|
@ -4811,7 +4810,7 @@ dependencies = [
|
|||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4824,7 +4823,7 @@ dependencies = [
|
|||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.12.1",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -5433,7 +5432,7 @@ dependencies = [
|
|||
"getrandom 0.4.1",
|
||||
"once_cell",
|
||||
"rustix 1.1.4",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -6547,7 +6546,7 @@ version = "0.1.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ tracing = { version = "0.1.44", features = [
|
|||
"max_level_debug",
|
||||
"release_max_level_info",
|
||||
] }
|
||||
tracing-journald = { version = "0.3.2", optional = true }
|
||||
tracing-journald = "0.3.2"
|
||||
tracing-subscriber = { version = "0.3.22", features = [
|
||||
"env-filter",
|
||||
"tracing-log",
|
||||
|
|
@ -125,7 +125,7 @@ optional = true
|
|||
debug = ["egui", "egui_plot", "smithay-egui", "anyhow/backtrace"]
|
||||
default = ["systemd"]
|
||||
logind = ["logind-zbus"]
|
||||
systemd = ["libsystemd", "logind", "tracing-journald"]
|
||||
systemd = ["libsystemd", "logind"]
|
||||
profile-with-tracy = ["profiling/profile-with-tracy", "tracy-client/default"]
|
||||
profile-with-tracy-gpu = ["profile-with-tracy", "smithay/tracy_gpu_profiling"]
|
||||
|
||||
|
|
|
|||
67
Makefile
Normal file
67
Makefile
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
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
31
README.md
|
|
@ -1,31 +0,0 @@
|
|||
# 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
53
cargo.just
|
|
@ -1,53 +0,0 @@
|
|||
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,7 +3,6 @@
|
|||
use cosmic_config::{CosmicConfigEntry, cosmic_config_derive::CosmicConfigEntry};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::input::TouchpadOverride;
|
||||
|
||||
|
|
@ -107,8 +106,8 @@ pub struct CosmicCompConfig {
|
|||
pub edge_snap_threshold: u32,
|
||||
pub accessibility_zoom: ZoomConfig,
|
||||
pub appearance_settings: AppearanceConfig,
|
||||
/// When the cursor hides itself: idle, fullscreen idle, typing, touch
|
||||
pub cursor_hide: CursorHideConfig,
|
||||
/// Hide the cursor after this many seconds of pointer inactivity (None disables)
|
||||
pub cursor_hide_timeout: Option<u32>,
|
||||
/// Briefly magnify the cursor when the pointer is shaken, to help locate it
|
||||
pub cursor_shake_to_find: bool,
|
||||
pub activation_policy: ActivationPolicy,
|
||||
|
|
@ -149,7 +148,7 @@ impl Default for CosmicCompConfig {
|
|||
edge_snap_threshold: 0,
|
||||
accessibility_zoom: ZoomConfig::default(),
|
||||
appearance_settings: AppearanceConfig::default(),
|
||||
cursor_hide: CursorHideConfig::default(),
|
||||
cursor_hide_timeout: None,
|
||||
cursor_shake_to_find: true,
|
||||
activation_policy: ActivationPolicy::default(),
|
||||
decoration_preference: DecorationPreference::default(),
|
||||
|
|
@ -199,81 +198,6 @@ fn default_repeat_delay() -> u32 {
|
|||
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)]
|
||||
pub struct ZoomConfig {
|
||||
pub start_on_login: bool,
|
||||
|
|
@ -339,109 +263,3 @@ pub enum XwaylandDescaling {
|
|||
#[default]
|
||||
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,7 +6,6 @@ Build-Depends:
|
|||
cargo,
|
||||
cmake,
|
||||
debhelper-compat (=10),
|
||||
just,
|
||||
libegl1-mesa-dev,
|
||||
libfontconfig-dev,
|
||||
libgbm-dev,
|
||||
|
|
|
|||
12
debian/rules
vendored
12
debian/rules
vendored
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/make -f
|
||||
|
||||
DESTDIR = debian/cosmic-comp
|
||||
export sysconfdir = "/usr/share"
|
||||
export VENDOR ?= 1
|
||||
CLEAN ?= 1
|
||||
|
||||
|
|
@ -9,18 +9,12 @@ CLEAN ?= 1
|
|||
|
||||
override_dh_auto_clean:
|
||||
ifeq ($(CLEAN),1)
|
||||
ischroot && just clean || just clean-dist
|
||||
ischroot && make clean || make distclean
|
||||
endif
|
||||
ifeq ($(VENDOR),1)
|
||||
ischroot || just vendor
|
||||
ischroot || make vendor
|
||||
endif
|
||||
|
||||
override_dh_auto_build:
|
||||
just build-vendored
|
||||
|
||||
override_dh_auto_install:
|
||||
just rootdir=$(DESTDIR) install
|
||||
|
||||
override_dh_installinit:
|
||||
dh_installinit -r
|
||||
|
||||
|
|
|
|||
64
justfile
64
justfile
|
|
@ -1,64 +0,0 @@
|
|||
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}}
|
||||
|
|
@ -615,13 +615,7 @@ impl State {
|
|||
.clone();
|
||||
|
||||
let mut feedback =
|
||||
DmabufFeedbackBuilder::new(primary_node.dev_id(), primary_formats.clone())
|
||||
.add_preference_tranche(
|
||||
primary_node.dev_id(),
|
||||
TrancheFlags::Sampling,
|
||||
primary_formats.iter().cloned(),
|
||||
3..=6,
|
||||
);
|
||||
DmabufFeedbackBuilder::new(primary_node.dev_id(), primary_formats.clone());
|
||||
for dev in kms
|
||||
.drm_devices
|
||||
.values()
|
||||
|
|
|
|||
|
|
@ -5,11 +5,9 @@ use crate::{
|
|||
element::AsGlowRenderer,
|
||||
wayland::{SurfaceRenderElement, push_render_elements_from_surface_tree},
|
||||
},
|
||||
shell::focus::target::KeyboardFocusTarget,
|
||||
utils::prelude::*,
|
||||
wayland::handlers::compositor::FRAME_TIME_FILTER,
|
||||
};
|
||||
use cosmic_comp_config::HideDecision;
|
||||
use keyframe::{ease, functions::EaseInOutCubic};
|
||||
use resvg::{tiny_skia, usvg};
|
||||
use serde::Deserialize;
|
||||
|
|
@ -453,32 +451,6 @@ 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 struct CursorStateInner {
|
||||
current_cursor: Option<CursorIcon>,
|
||||
|
|
@ -490,10 +462,9 @@ pub struct CursorStateInner {
|
|||
current_image: Option<Image>,
|
||||
image_cache: Vec<CachedFrame>,
|
||||
|
||||
hidden: Option<HideReason>,
|
||||
hidden: bool,
|
||||
idle_timer: Option<RegistrationToken>,
|
||||
last_armed: Option<Instant>,
|
||||
last_pointer_activity: Instant,
|
||||
|
||||
// shake-to-find
|
||||
shake_path: VecDeque<PathSample>,
|
||||
|
|
@ -723,8 +694,7 @@ impl Default for CursorStateInner {
|
|||
current_image: None,
|
||||
image_cache: Vec::new(),
|
||||
|
||||
hidden: None,
|
||||
last_pointer_activity: Instant::now(),
|
||||
hidden: false,
|
||||
idle_timer: None,
|
||||
last_armed: None,
|
||||
|
||||
|
|
@ -777,7 +747,7 @@ pub fn draw_cursor<R>(
|
|||
let mut state_ref = seat_userdata.get::<CursorState>().unwrap().lock().unwrap();
|
||||
let state = &mut *state_ref;
|
||||
|
||||
if state.hidden.is_some() {
|
||||
if state.hidden {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -867,175 +837,68 @@ pub fn draw_cursor<R>(
|
|||
|
||||
const ACTIVITY_THROTTLE: Duration = Duration::from_millis(100);
|
||||
|
||||
/// Reveal the cursor if this event should, and (re)arm the idle-hide timer.
|
||||
/// Returns true if the cursor was previously hidden and is now visible.
|
||||
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;
|
||||
/// Reveal the cursor and (re)arm the idle-hide timer; returns true if it was previously hidden
|
||||
pub fn notify_cursor_activity(state: &State, seat: &Seat<State>) -> bool {
|
||||
let timeout = state.common.config.cosmic_conf.cursor_hide_timeout;
|
||||
let loop_handle = &state.common.event_loop_handle;
|
||||
let cursor_state = seat.user_data().get::<CursorState>().unwrap();
|
||||
let now = Instant::now();
|
||||
|
||||
let (old_token, since_activity) = {
|
||||
let (was_hidden, old_token) = {
|
||||
let mut inner = cursor_state.lock().unwrap();
|
||||
if inner.hidden.is_some() {
|
||||
return;
|
||||
}
|
||||
// Coalesce bursts from high-frequency pointers.
|
||||
let throttled = inner.idle_timer.is_some()
|
||||
let was_hidden = inner.hidden;
|
||||
inner.hidden = false;
|
||||
|
||||
let throttled = timeout.is_some()
|
||||
&& !was_hidden
|
||||
&& inner.idle_timer.is_some()
|
||||
&& inner
|
||||
.last_armed
|
||||
.is_some_and(|t| now.duration_since(t) < ACTIVITY_THROTTLE);
|
||||
if throttled {
|
||||
return;
|
||||
return was_hidden;
|
||||
}
|
||||
let since_activity = now.duration_since(inner.last_pointer_activity);
|
||||
|
||||
let old_token = inner.idle_timer.take();
|
||||
inner.last_armed = None;
|
||||
(inner.idle_timer.take(), since_activity)
|
||||
(was_hidden, old_token)
|
||||
};
|
||||
|
||||
if let Some(token) = old_token {
|
||||
loop_handle.remove(token);
|
||||
}
|
||||
|
||||
let Some(delay) = config.arm_delay(since_activity) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let timer = Timer::from_duration(delay);
|
||||
let timer_seat = seat.clone();
|
||||
match loop_handle.insert_source(timer, move |_, _, state| on_idle_timer(state, &timer_seat)) {
|
||||
Ok(token) => {
|
||||
if let Some(secs) = timeout {
|
||||
let timer = Timer::from_duration(Duration::from_secs(secs as u64));
|
||||
let seat = seat.clone();
|
||||
if let Ok(token) = loop_handle.insert_source(timer, move |_, _, state| {
|
||||
hide_cursor(state, &seat);
|
||||
TimeoutAction::Drop
|
||||
}) {
|
||||
let mut inner = cursor_state.lock().unwrap();
|
||||
inner.idle_timer = Some(token);
|
||||
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
|
||||
}
|
||||
|
||||
/// Whether the seat is focusing a fullscreen surface. A fullscreen window that
|
||||
/// is not focused can be stacked behind others, so its presence on the output
|
||||
/// says nothing about what the user is looking at.
|
||||
fn fullscreen_focused(seat: &Seat<State>) -> bool {
|
||||
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)
|
||||
}
|
||||
fn hide_cursor(state: &mut State, seat: &Seat<State>) {
|
||||
if let Some(ptr) = seat.get_pointer()
|
||||
&& ptr.is_grabbed()
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 mut inner = cursor_state.lock().unwrap();
|
||||
// Every caller has already disposed of the timer source, so clear the
|
||||
// bookkeeping before the grab check rather than only on success.
|
||||
inner.hidden = true;
|
||||
inner.idle_timer = 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();
|
||||
for output in outputs {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use crate::{
|
|||
},
|
||||
};
|
||||
use anyhow::Context;
|
||||
use cosmic_config::{ConfigGet, ConfigSet, CosmicConfigEntry};
|
||||
use cosmic_config::{ConfigGet, CosmicConfigEntry};
|
||||
use cosmic_settings_config::window_rules::ApplicationException;
|
||||
use cosmic_settings_config::{Shortcuts, shortcuts, window_rules};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -49,8 +49,8 @@ mod types;
|
|||
use cosmic::config::CosmicTk;
|
||||
pub use cosmic_comp_config::EdidProduct;
|
||||
use cosmic_comp_config::{
|
||||
ActivationPolicy, AppearanceConfig, CosmicCompConfig, CursorHideConfig, DecorationPreference,
|
||||
KeyboardConfig, TileBehavior, XkbConfig, XwaylandDescaling, XwaylandEavesdropping, ZoomConfig,
|
||||
ActivationPolicy, AppearanceConfig, CosmicCompConfig, DecorationPreference, KeyboardConfig,
|
||||
TileBehavior, XkbConfig, XwaylandDescaling, XwaylandEavesdropping, ZoomConfig,
|
||||
input::{DeviceState as InputDeviceState, InputConfig, TouchpadOverride},
|
||||
output::comp::{
|
||||
OutputConfig, OutputInfo, OutputState, OutputsConfig, TransformDef, load_outputs,
|
||||
|
|
@ -172,7 +172,7 @@ pub enum ColorFilter {
|
|||
}
|
||||
|
||||
impl Config {
|
||||
pub fn load(loop_handle: &LoopHandle<'_, State>, kiosk_mode: bool) -> Config {
|
||||
pub fn load(loop_handle: &LoopHandle<'_, State>) -> Config {
|
||||
let config = cosmic_config::Config::new("com.system76.CosmicComp", 1).unwrap();
|
||||
let source = cosmic_config::calloop::ConfigWatchSource::new(&config).unwrap();
|
||||
loop_handle
|
||||
|
|
@ -182,7 +182,7 @@ impl Config {
|
|||
.expect("Failed to add cosmic-config to the event loop");
|
||||
let xdg = xdg::BaseDirectories::new();
|
||||
|
||||
let mut cosmic_comp_config =
|
||||
let cosmic_comp_config =
|
||||
CosmicCompConfig::get_entry(&config).unwrap_or_else(|(errs, c)| {
|
||||
if cfg!(debug_assertions) {
|
||||
for err in errs {
|
||||
|
|
@ -192,18 +192,6 @@ impl Config {
|
|||
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
|
||||
if let Ok(tk_config) = cosmic_config::Config::new("com.system76.CosmicTk", 1) {
|
||||
fn handle_new_toolkit_config(config: CosmicTk, state: &mut State) {
|
||||
|
|
@ -260,48 +248,40 @@ impl Config {
|
|||
|
||||
// Source key bindings from com.system76.CosmicSettings.Shortcuts
|
||||
let settings_context = shortcuts::context().expect("Failed to load shortcuts config");
|
||||
let mut system_actions = Default::default();
|
||||
let mut shortcuts = Default::default();
|
||||
// Kiosk mode disables shortcuts
|
||||
if !kiosk_mode {
|
||||
system_actions = shortcuts::system_actions(&settings_context);
|
||||
shortcuts = shortcuts::shortcuts(&settings_context);
|
||||
let system_actions = shortcuts::system_actions(&settings_context);
|
||||
let shortcuts = shortcuts::shortcuts(&settings_context);
|
||||
|
||||
// Listen for updates to the keybindings config.
|
||||
match cosmic_config::calloop::ConfigWatchSource::new(&settings_context) {
|
||||
Ok(source) => {
|
||||
if let Err(err) =
|
||||
loop_handle.insert_source(source, |(config, keys), (), state| {
|
||||
for key in keys {
|
||||
match key.as_str() {
|
||||
// Reload the keyboard shortcuts config.
|
||||
"custom" | "defaults" => {
|
||||
state.common.config.shortcuts =
|
||||
shortcuts::shortcuts(&config);
|
||||
}
|
||||
|
||||
"system_actions" => {
|
||||
state.common.config.system_actions =
|
||||
shortcuts::system_actions(&config);
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
// Listen for updates to the keybindings config.
|
||||
match cosmic_config::calloop::ConfigWatchSource::new(&settings_context) {
|
||||
Ok(source) => {
|
||||
if let Err(err) = loop_handle.insert_source(source, |(config, keys), (), state| {
|
||||
for key in keys {
|
||||
match key.as_str() {
|
||||
// Reload the keyboard shortcuts config.
|
||||
"custom" | "defaults" => {
|
||||
state.common.config.shortcuts = shortcuts::shortcuts(&config);
|
||||
}
|
||||
})
|
||||
{
|
||||
warn!(
|
||||
?err,
|
||||
"Failed to watch com.system76.CosmicSettings.Shortcuts config"
|
||||
);
|
||||
|
||||
"system_actions" => {
|
||||
state.common.config.system_actions =
|
||||
shortcuts::system_actions(&config);
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}) {
|
||||
warn!(
|
||||
?err,
|
||||
"Failed to watch com.system76.CosmicSettings.Shortcuts config"
|
||||
);
|
||||
}
|
||||
Err(err) => warn!(
|
||||
?err,
|
||||
"failed to create config watch source for com.system76.CosmicSettings.Shortcuts"
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
Err(err) => warn!(
|
||||
?err,
|
||||
"failed to create config watch source for com.system76.CosmicSettings.Shortcuts"
|
||||
),
|
||||
};
|
||||
|
||||
let window_rules_context =
|
||||
window_rules::context().expect("Failed to load window rules config");
|
||||
|
|
@ -1003,21 +983,15 @@ fn config_changed(config: cosmic_config::Config, keys: Vec<String>, state: &mut
|
|||
let new = get_config::<bool>(&config, "cursor_shake_to_find");
|
||||
state.common.config.cosmic_conf.cursor_shake_to_find = 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.
|
||||
"cursor_hide_timeout" => {
|
||||
let new = get_config::<Option<u32>>(&config, "cursor_hide_timeout");
|
||||
if new != state.common.config.cosmic_conf.cursor_hide_timeout {
|
||||
state.common.config.cosmic_conf.cursor_hide_timeout = new;
|
||||
let seats: Vec<_> = state.common.shell.read().seats.iter().cloned().collect();
|
||||
let mut needs_render = false;
|
||||
for seat in seats {
|
||||
needs_render |= crate::backend::render::cursor::notify_cursor_activity(
|
||||
state,
|
||||
&seat,
|
||||
crate::backend::render::cursor::PointerEventKind::Motion,
|
||||
);
|
||||
needs_render |=
|
||||
crate::backend::render::cursor::notify_cursor_activity(state, &seat);
|
||||
}
|
||||
if needs_render {
|
||||
let outputs: Vec<_> =
|
||||
|
|
|
|||
104
src/input/mod.rs
104
src/input/mod.rs
|
|
@ -1,10 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::{
|
||||
backend::render::{
|
||||
ElementFilter,
|
||||
cursor::{PointerEventKind, notify_cursor_activity},
|
||||
},
|
||||
backend::render::{ElementFilter, cursor::notify_cursor_activity},
|
||||
config::{
|
||||
Action, Config, PrivateAction,
|
||||
key_bindings::{
|
||||
|
|
@ -22,7 +19,7 @@ use crate::{
|
|||
Stage, render_input_order,
|
||||
target::{KeyboardFocusTarget, PointerFocusTarget},
|
||||
},
|
||||
grabs::{MenuGrab, MoveGrab, ReleaseMode, ResizeEdge, ResizeGrab},
|
||||
grabs::{ReleaseMode, ResizeEdge},
|
||||
layout::{
|
||||
floating::ResizeGrabMarker,
|
||||
tiling::{NodeDesc, SwapWindowGrab, TilingLayout},
|
||||
|
|
@ -52,7 +49,7 @@ use smithay::{
|
|||
PointerAxisEvent, ProximityState, TabletToolButtonEvent, TabletToolEvent,
|
||||
TabletToolProximityEvent, TabletToolTipEvent, TabletToolTipState, TouchEvent,
|
||||
},
|
||||
desktop::{WindowSurfaceType, utils::under_from_surface_tree},
|
||||
desktop::{PopupKeyboardGrab, WindowSurfaceType, utils::under_from_surface_tree},
|
||||
input::{
|
||||
Seat,
|
||||
keyboard::KeyboardHandle,
|
||||
|
|
@ -223,16 +220,7 @@ impl State {
|
|||
) where
|
||||
<B as InputBackend>::Device: 'static,
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
crate::wayland::handlers::output_power::set_all_surfaces_dpms_on(self);
|
||||
|
||||
use smithay::backend::input::Event;
|
||||
match event {
|
||||
|
|
@ -339,26 +327,6 @@ impl State {
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -372,7 +340,7 @@ impl State {
|
|||
.cloned()
|
||||
{
|
||||
self.common.idle_notifier_state.notify_activity(&seat);
|
||||
notify_cursor_activity(self, &seat, PointerEventKind::Motion);
|
||||
notify_cursor_activity(self, &seat);
|
||||
let current_output = seat.active_output();
|
||||
|
||||
if self.common.config.cosmic_conf.cursor_shake_to_find
|
||||
|
|
@ -746,7 +714,7 @@ impl State {
|
|||
.cloned();
|
||||
if let Some(seat) = maybe_seat {
|
||||
self.common.idle_notifier_state.notify_activity(&seat);
|
||||
notify_cursor_activity(self, &seat, PointerEventKind::Motion);
|
||||
notify_cursor_activity(self, &seat);
|
||||
let (output, position) = if matches!(&backend_id, InputBackendId::Ei(_)) {
|
||||
// EI absolute coordinates are in the compositor's *global*
|
||||
// logical space: each advertised region carries its output's
|
||||
|
|
@ -841,7 +809,7 @@ impl State {
|
|||
return;
|
||||
};
|
||||
self.common.idle_notifier_state.notify_activity(&seat);
|
||||
notify_cursor_activity(self, &seat, PointerEventKind::Other);
|
||||
notify_cursor_activity(self, &seat);
|
||||
|
||||
let current_focus = seat.get_keyboard().unwrap().current_focus();
|
||||
let shortcuts_inhibited = current_focus.as_ref().is_some_and(|f| {
|
||||
|
|
@ -1092,7 +1060,7 @@ impl State {
|
|||
.cloned();
|
||||
if let Some(seat) = maybe_seat {
|
||||
self.common.idle_notifier_state.notify_activity(&seat);
|
||||
notify_cursor_activity(self, &seat, PointerEventKind::Other);
|
||||
notify_cursor_activity(self, &seat);
|
||||
|
||||
if self.source_modifiers(&backend_id, &seat).logo
|
||||
&& self
|
||||
|
|
@ -1494,18 +1462,11 @@ impl State {
|
|||
};
|
||||
let under = State::surface_under(position, &output, &shell)
|
||||
.map(|(target, pos)| (target, pos.as_logical()));
|
||||
let focus_target = State::element_under(position, &output, &shell, &seat);
|
||||
|
||||
std::mem::drop(shell);
|
||||
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
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(
|
||||
self,
|
||||
under,
|
||||
|
|
@ -1516,14 +1477,6 @@ impl State {
|
|||
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, .. } => {
|
||||
|
|
@ -1639,7 +1592,7 @@ impl State {
|
|||
.cloned()
|
||||
{
|
||||
self.common.idle_notifier_state.notify_activity(&seat);
|
||||
notify_cursor_activity(self, &seat, PointerEventKind::Motion);
|
||||
notify_cursor_activity(self, &seat);
|
||||
let Some(output) =
|
||||
mapped_output_for_device(&self.common.config, &shell, &event.device())
|
||||
.cloned()
|
||||
|
|
@ -1743,7 +1696,7 @@ impl State {
|
|||
.cloned()
|
||||
{
|
||||
self.common.idle_notifier_state.notify_activity(&seat);
|
||||
notify_cursor_activity(self, &seat, PointerEventKind::Motion);
|
||||
notify_cursor_activity(self, &seat);
|
||||
let Some(output) =
|
||||
mapped_output_for_device(&self.common.config, &shell, &event.device())
|
||||
.cloned()
|
||||
|
|
@ -1892,7 +1845,7 @@ impl State {
|
|||
.cloned();
|
||||
if let Some(seat) = maybe_seat {
|
||||
self.common.idle_notifier_state.notify_activity(&seat);
|
||||
notify_cursor_activity(self, &seat, PointerEventKind::Other);
|
||||
notify_cursor_activity(self, &seat);
|
||||
|
||||
let serial = SERIAL_COUNTER.next_serial();
|
||||
let output = seat.active_output();
|
||||
|
|
@ -1945,7 +1898,7 @@ impl State {
|
|||
.cloned();
|
||||
if let Some(seat) = maybe_seat {
|
||||
self.common.idle_notifier_state.notify_activity(&seat);
|
||||
notify_cursor_activity(self, &seat, PointerEventKind::Other);
|
||||
notify_cursor_activity(self, &seat);
|
||||
if let Some(tool) = seat.tablet_seat().get_tool(&event.tool()) {
|
||||
tool.button(
|
||||
self,
|
||||
|
|
@ -2143,21 +2096,6 @@ impl State {
|
|||
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)
|
||||
&& !seat.get_keyboard().unwrap().is_grabbed()
|
||||
&& !shortcuts_inhibited
|
||||
|
|
@ -2353,15 +2291,17 @@ impl State {
|
|||
|
||||
let keyboard = seat.get_keyboard().unwrap();
|
||||
let pointer = seat.get_pointer().unwrap();
|
||||
// Escape only cancels grabs the compositor started for its own interactions
|
||||
// For keyboard: SwapWindowGrab
|
||||
// For pointer: MoveGrab, ResizeGrab, and MenuGrab
|
||||
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>()
|
||||
// We're only interested in filtering keyboard grabs if we initiated them.
|
||||
// The easiest way to check that is to check the type of the grab.
|
||||
let keyboard_grabbed = keyboard.with_grab(|_serial, grab| {
|
||||
grab.is::<SwapWindowGrab>() || grab.is::<PopupKeyboardGrab<State>>()
|
||||
}) == Some(true);
|
||||
let is_grabbed = keyboard_grabbed || pointer_grabbed;
|
||||
// A virtual-keyboard key can arrive while the seat's pointer is grabbed by that
|
||||
// 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();
|
||||
//this should fall back to active output since there may not be a focused output
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@ Project home page: https://github.com/pop-os/cosmic-comp
|
|||
Options:
|
||||
-h, --help Show this message
|
||||
--no-xwayland Run without Xwayland
|
||||
-V, --version Show the version of cosmic-comp"#
|
||||
-v, --version Show the version of cosmic-comp"#
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@ use std::str::FromStr;
|
|||
|
||||
use anyhow::Result;
|
||||
|
||||
#[cfg(feature = "systemd")]
|
||||
use tracing::warn;
|
||||
use tracing::{debug, info};
|
||||
#[cfg(feature = "systemd")]
|
||||
use tracing::{debug, info, warn};
|
||||
use tracing_journald as journald;
|
||||
use tracing_subscriber::{EnvFilter, filter::Directive, fmt, prelude::*};
|
||||
|
||||
|
|
@ -32,7 +29,6 @@ pub fn init_logger() -> Result<()> {
|
|||
|
||||
let fmt_layer = fmt::layer().compact();
|
||||
|
||||
#[cfg(feature = "systemd")]
|
||||
match journald::layer() {
|
||||
Ok(journald_layer) => tracing_subscriber::registry()
|
||||
.with(fmt_layer)
|
||||
|
|
@ -47,11 +43,6 @@ pub fn init_logger() -> Result<()> {
|
|||
warn!(?err, "Failed to init journald logging.");
|
||||
}
|
||||
};
|
||||
#[cfg(not(feature = "systemd"))]
|
||||
tracing_subscriber::registry()
|
||||
.with(fmt_layer)
|
||||
.with(filter)
|
||||
.init();
|
||||
log_panics::init();
|
||||
|
||||
info!("Version: {}", std::env!("CARGO_PKG_VERSION"));
|
||||
|
|
|
|||
|
|
@ -430,10 +430,6 @@ impl CosmicMapped {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn is_sticky(&self) -> bool {
|
||||
self.active_window().is_sticky()
|
||||
}
|
||||
|
||||
pub fn pending_size(&self) -> Option<Size<i32, Logical>> {
|
||||
match &self.element {
|
||||
CosmicMappedInternal::Stack(s) => s.pending_size(),
|
||||
|
|
|
|||
|
|
@ -2,18 +2,12 @@ use crate::{
|
|||
shell::{CosmicSurface, MinimizedWindow, Shell, Trigger, element::CosmicMapped},
|
||||
state::{Common, State},
|
||||
utils::prelude::*,
|
||||
wayland::{
|
||||
handlers::{xdg_shell::PopupGrabData, xwayland_keyboard_grab::XWaylandGrabSeatData},
|
||||
protocols::session_lock_layer::layer_show_on_lock,
|
||||
},
|
||||
wayland::handlers::{xdg_shell::PopupGrabData, xwayland_keyboard_grab::XWaylandGrabSeatData},
|
||||
};
|
||||
use indexmap::IndexSet;
|
||||
use smithay::{
|
||||
backend::input::InputTime,
|
||||
desktop::{
|
||||
LayerSurface, PopupUngrabStrategy, WindowSurfaceType, find_popup_root_surface,
|
||||
layer_map_for_output,
|
||||
},
|
||||
desktop::{PopupUngrabStrategy, layer_map_for_output},
|
||||
input::{Seat, pointer::MotionEvent},
|
||||
output::Output,
|
||||
reexports::wayland_server::{Resource, protocol::wl_surface::WlSurface},
|
||||
|
|
@ -255,17 +249,8 @@ impl Shell {
|
|||
let workspace = if let Some(workspace) = workspace {
|
||||
self.workspaces.space_for_handle_mut(&workspace.0).unwrap()
|
||||
} else {
|
||||
let output = match &target {
|
||||
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()))
|
||||
//should this be the active output or the focused output?
|
||||
self.active_space_mut(&seat.focused_or_active_output())
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
|
|
@ -642,42 +627,19 @@ fn focus_target_is_valid(
|
|||
output: &Output,
|
||||
target: KeyboardFocusTarget,
|
||||
) -> bool {
|
||||
// If a session lock is active, only lock surfaces and lock layers can be focused
|
||||
// If a session lock is active, only lock surfaces can be focused
|
||||
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(_));
|
||||
}
|
||||
|
||||
// If an exclusive layer shell surface exists (on any output), only exclusive
|
||||
// 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) {
|
||||
let is_exclusive_on_layer = |layer_surface: &LayerSurface| {
|
||||
return if let KeyboardFocusTarget::LayerSurface(layer_surface) = target {
|
||||
let data = layer_surface.cached_state();
|
||||
(data.keyboard_interactivity, data.layer) == (KeyboardInteractivity::Exclusive, layer)
|
||||
};
|
||||
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,
|
||||
} else {
|
||||
false
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ use crate::{
|
|||
prelude::OutputExt,
|
||||
quirks::{WORKSPACE_OVERVIEW_NAMESPACE, workspace_overview_is_open},
|
||||
},
|
||||
wayland::protocols::{session_lock_layer::layer_show_on_lock, workspace::WorkspaceHandle},
|
||||
wayland::protocols::workspace::WorkspaceHandle,
|
||||
};
|
||||
|
||||
pub enum Stage<'a> {
|
||||
|
|
@ -77,8 +77,6 @@ fn render_input_order_internal<R: 'static>(
|
|||
element_filter: ElementFilter,
|
||||
mut callback: impl FnMut(Stage) -> ControlFlow<Result<R, OutputNoMode>, ()>,
|
||||
) -> ControlFlow<Result<R, OutputNoMode>, ()> {
|
||||
// NOTE: Keep in sync with other surface iteration functions
|
||||
|
||||
if shell
|
||||
.zoom_state
|
||||
.as_ref()
|
||||
|
|
@ -89,92 +87,7 @@ fn render_input_order_internal<R: 'static>(
|
|||
|
||||
// Session Lock
|
||||
if let Some(session_lock) = &shell.session_lock {
|
||||
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(());
|
||||
return callback(Stage::SessionLock(session_lock.surfaces.get(output)));
|
||||
}
|
||||
|
||||
// Overlay-level layer shell
|
||||
|
|
|
|||
|
|
@ -579,12 +579,10 @@ impl PointerGrab<State> for MenuGrab {
|
|||
PointerTarget::motion(&element.iced, &self.seat, state, &new_event);
|
||||
}
|
||||
} else {
|
||||
// Keep the root menu hovered while a submenu is open
|
||||
let keep_root = usize::from(elements.len() > 1);
|
||||
elements
|
||||
.iter_mut()
|
||||
.filter(|element| element.pointer_entered)
|
||||
.skip(keep_root)
|
||||
.skip(1)
|
||||
.for_each(|element| {
|
||||
PointerTarget::leave(
|
||||
&element.iced,
|
||||
|
|
@ -1022,11 +1020,10 @@ impl TabletToolGrab<State> for MenuGrab {
|
|||
);
|
||||
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)
|
||||
.skip(1)
|
||||
.for_each(|element| {
|
||||
TabletToolTarget::proximity_out(
|
||||
&element.iced,
|
||||
|
|
|
|||
|
|
@ -3271,34 +3271,12 @@ impl TilingLayout {
|
|||
}
|
||||
}
|
||||
|
||||
let (_, inner) = self.gaps();
|
||||
let mut result = None;
|
||||
let mut fork = None;
|
||||
let mut lookup = Some(root.clone());
|
||||
while let Some(node) = lookup {
|
||||
let data = tree.get(&node).unwrap().data();
|
||||
if data.geometry().contains(location) {
|
||||
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;
|
||||
|
|
@ -3345,16 +3323,25 @@ impl TilingLayout {
|
|||
)
|
||||
})
|
||||
}
|
||||
Some((_, Data::Group { .. })) => {
|
||||
let (id, idx) = fork?;
|
||||
let Data::Group {
|
||||
Some((
|
||||
id,
|
||||
Data::Group {
|
||||
orientation,
|
||||
last_geometry,
|
||||
..
|
||||
} = tree.get(&id).unwrap().data().clone()
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
},
|
||||
)) => {
|
||||
let idx = tree
|
||||
.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((
|
||||
ResizeForkTarget {
|
||||
node: id.clone(),
|
||||
|
|
|
|||
|
|
@ -19,10 +19,7 @@ use crate::{
|
|||
utils,
|
||||
wayland::{
|
||||
handlers::data_device::{self, get_dnd_icon},
|
||||
protocols::{
|
||||
session_lock_layer::layer_show_on_lock,
|
||||
workspace::{State as WState, WorkspaceCapabilities},
|
||||
},
|
||||
protocols::workspace::{State as WState, WorkspaceCapabilities},
|
||||
},
|
||||
};
|
||||
use cosmic_comp_config::{
|
||||
|
|
@ -516,12 +513,6 @@ impl WorkspaceSet {
|
|||
}
|
||||
}
|
||||
|
||||
fn refresh_focus_stacks(&mut self) {
|
||||
for workspace in &mut self.workspaces {
|
||||
workspace.refresh_focus_stack();
|
||||
}
|
||||
}
|
||||
|
||||
fn activate(
|
||||
&mut self,
|
||||
idx: usize,
|
||||
|
|
@ -626,7 +617,6 @@ impl WorkspaceSet {
|
|||
self.workspaces[self.active].refresh();
|
||||
}
|
||||
self.sticky_layer.refresh();
|
||||
self.refresh_focus_stacks();
|
||||
}
|
||||
|
||||
fn add_empty_workspace(&mut self, state: &mut WorkspaceUpdateGuard<State>) {
|
||||
|
|
@ -2056,27 +2046,12 @@ impl Shell {
|
|||
}
|
||||
|
||||
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((output, _)) = session_lock
|
||||
return session_lock
|
||||
.surfaces
|
||||
.iter()
|
||||
.find(|(_, v)| v.wl_surface() == surface)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
.map(|(k, _)| k);
|
||||
}
|
||||
|
||||
self.outputs()
|
||||
|
|
|
|||
|
|
@ -526,16 +526,10 @@ impl Workspace {
|
|||
.mapped()
|
||||
.chain(self.tiling_layer.mapped().map(|(w, _)| w))
|
||||
.chain(move_mapped.iter())
|
||||
.chain(
|
||||
self.minimized_windows
|
||||
.iter()
|
||||
.flat_map(MinimizedWindow::mapped),
|
||||
)
|
||||
};
|
||||
stack.retain(|w| match w {
|
||||
FocusTarget::Fullscreen(s) => fullscreen_surfaces.contains(&s),
|
||||
// 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),
|
||||
FocusTarget::Window(w) => mapped().any(|m| w == m),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1773,17 +1767,13 @@ impl Workspace {
|
|||
|
||||
self.floating_layer.render(
|
||||
renderer,
|
||||
render_focus
|
||||
.then(|| {
|
||||
focused.as_ref().and_then(|target| {
|
||||
if let FocusTarget::Window(mapped) = target {
|
||||
Some(mapped)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
.flatten(),
|
||||
focused.as_ref().and_then(|target| {
|
||||
if let FocusTarget::Window(mapped) = target {
|
||||
Some(mapped)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
resize_indicator.clone(),
|
||||
indicator_thickness,
|
||||
alpha,
|
||||
|
|
|
|||
14
src/state.rs
14
src/state.rs
|
|
@ -23,7 +23,6 @@ use crate::{
|
|||
output_configuration::OutputConfigurationState,
|
||||
output_power::OutputPowerState,
|
||||
overlap_notify::OverlapNotifyState,
|
||||
session_lock_layer::SessionLockLayerState,
|
||||
toplevel_info::ToplevelInfoState,
|
||||
toplevel_management::{ManagementCapabilities, ToplevelManagementState},
|
||||
workspace::{WorkspaceState, WorkspaceUpdateGuard},
|
||||
|
|
@ -321,7 +320,6 @@ pub struct Common {
|
|||
pub xwayland_state: Option<XWaylandState>,
|
||||
pub xwayland_shell_state: XWaylandShellState,
|
||||
pub pointer_focus_state: Option<PointerFocusState>,
|
||||
pub session_lock_layer_state: SessionLockLayerState,
|
||||
|
||||
#[cfg(feature = "logind")]
|
||||
pub inhibit_lid_fd: Option<OwnedFd>,
|
||||
|
|
@ -667,7 +665,7 @@ impl State {
|
|||
.unwrap();
|
||||
|
||||
let clock = Clock::new();
|
||||
let config = Config::load(&handle, kiosk_command.is_some());
|
||||
let config = Config::load(&handle);
|
||||
let compositor_state = CompositorState::new::<Self>(dh);
|
||||
let corner_radius_state = CornerRadiusState::new::<Self>(dh);
|
||||
let data_device_state = DataDeviceState::new::<Self>(dh);
|
||||
|
|
@ -765,9 +763,6 @@ impl State {
|
|||
|
||||
let dbus_state = DBusState::init(&handle);
|
||||
|
||||
let session_lock_layer_state =
|
||||
SessionLockLayerState::new::<State, _>(dh, client_not_sandboxed);
|
||||
|
||||
State {
|
||||
common: Common {
|
||||
config,
|
||||
|
|
@ -835,7 +830,6 @@ impl State {
|
|||
pointer_focus_state: None,
|
||||
dbus_state,
|
||||
keyboard_layout_state,
|
||||
session_lock_layer_state,
|
||||
|
||||
#[cfg(feature = "logind")]
|
||||
inhibit_lid_fd: None,
|
||||
|
|
@ -974,8 +968,6 @@ impl Common {
|
|||
output: &Output,
|
||||
render_element_states: &RenderElementStates,
|
||||
) {
|
||||
// NOTE: Keep in sync with surface iteration in `render_input_order_internal`
|
||||
|
||||
let shell = self.shell.read();
|
||||
let processor = |namespace: Option<usize>| {
|
||||
move |surface: &WlSurface, states: &SurfaceData| {
|
||||
|
|
@ -1082,8 +1074,6 @@ impl Common {
|
|||
render_element_states: &RenderElementStates,
|
||||
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();
|
||||
|
||||
if let Some(session_lock) = shell.session_lock.as_ref()
|
||||
|
|
@ -1297,8 +1287,6 @@ impl Common {
|
|||
|
||||
#[profiling::function]
|
||||
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 should_send = |surface: &WlSurface, states: &SurfaceData| {
|
||||
// Do the standard primary scanout output check. For pointer surfaces it deduplicates
|
||||
|
|
|
|||
|
|
@ -519,7 +519,6 @@ impl<P: Program + Send + 'static> TabletToolTarget<crate::state::State> for Iced
|
|||
_tool_descriptor: &TabletToolDescriptor,
|
||||
) {
|
||||
let mut internal = self.0.lock().unwrap();
|
||||
internal.cursor_pos = None;
|
||||
internal
|
||||
.state
|
||||
.queue_event(Event::Mouse(MouseEvent::CursorLeft));
|
||||
|
|
@ -729,7 +728,6 @@ impl<P: Program + Send + 'static> PointerTarget<crate::state::State> for IcedEle
|
|||
_time: InputTime,
|
||||
) {
|
||||
let mut internal = self.0.lock().unwrap();
|
||||
internal.cursor_pos = None;
|
||||
internal
|
||||
.state
|
||||
.queue_event(Event::Mouse(MouseEvent::CursorLeft));
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::{borrow::Borrow, sync::Mutex};
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use std::{borrow::Borrow, collections::HashMap, sync::Mutex};
|
||||
|
||||
use smithay::{
|
||||
backend::{
|
||||
|
|
@ -454,28 +452,23 @@ fn constraints_for_renderer(
|
|||
.ok()
|
||||
.and_then(|device| device.try_get_render_node().ok().flatten())
|
||||
{
|
||||
let mut formats = renderer
|
||||
.egl_context()
|
||||
.dmabuf_render_formats()
|
||||
.iter()
|
||||
.fold(
|
||||
IndexMap::<Fourcc, Vec<Modifier>>::new(),
|
||||
|mut map, format| {
|
||||
map.entry(format.code).or_default().push(format.modifier);
|
||||
map
|
||||
},
|
||||
)
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
formats.sort_by_key(|(code, _)| !is_preferred_capture_format(*code));
|
||||
constraints.dma = Some(DmabufConstraints { node, formats });
|
||||
constraints.dma = Some(DmabufConstraints {
|
||||
node,
|
||||
formats: renderer
|
||||
.egl_context()
|
||||
.dmabuf_render_formats()
|
||||
.iter()
|
||||
.fold(
|
||||
HashMap::<Fourcc, Vec<Modifier>>::new(),
|
||||
|mut map, format| {
|
||||
map.entry(format.code).or_default().push(format.modifier);
|
||||
map
|
||||
},
|
||||
)
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>(),
|
||||
});
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,11 +21,6 @@ pub fn set_all_surfaces_dpms_on(state: &mut State) {
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ impl State {
|
|||
{
|
||||
for mapped in current_workspace
|
||||
.mapped()
|
||||
.filter(|m| m.maximized_state.lock().unwrap().is_some() && *m != &element)
|
||||
.filter(|m| m.maximized_state.lock().unwrap().is_some())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ pub mod keyboard_layout;
|
|||
pub mod output_configuration;
|
||||
pub mod output_power;
|
||||
pub mod overlap_notify;
|
||||
pub mod session_lock_layer;
|
||||
pub mod toplevel_info;
|
||||
pub mod toplevel_management;
|
||||
pub mod workspace;
|
||||
|
|
|
|||
|
|
@ -1,136 +0,0 @@
|
|||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue