Compare commits

..

No commits in common. "local/yoda-main" and "post-1.8-merge-20260910" have entirely different histories.

32 changed files with 298 additions and 1139 deletions

View file

@ -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).

View file

@ -13,12 +13,18 @@ jobs:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Rust toolchain - name: Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: dtolnay/rust-toolchain@stable
with: with:
components: rustfmt 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 - name: Format
run: cargo fmt --all -- --check run: cargo +stable fmt --all -- --check
clippy-check: clippy-check:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -26,14 +32,20 @@ jobs:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Rust toolchain - name: Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: dtolnay/rust-toolchain@stable
with: with:
components: clippy 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 - 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 clippy --all-features -- -D warnings run: cargo +stable clippy --all-features -- -D warnings
check-features: check-features:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -41,15 +53,20 @@ jobs:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Rust toolchain - name: Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: dtolnay/rust-toolchain@stable
- name: Cargo cache
uses: actions/cache@v5
with: with:
rustflags: "" 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
- run: cargo check --no-default-features - run: cargo +stable check --no-default-features
- run: cargo check --features debug - run: cargo +stable check --features debug
- run: cargo check --features profile-with-tracy - run: cargo +stable check --features profile-with-tracy
- run: cargo check --features profile-with-tracy-gpu - run: cargo +stable check --features profile-with-tracy-gpu
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -57,11 +74,21 @@ jobs:
- name: Checkout sources - name: Checkout sources
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Rust toolchain - name: Rust toolchain
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: dtolnay/rust-toolchain@stable
- name: Cargo cache
uses: actions/cache@v5
with: with:
rustflags: "" 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 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

View file

@ -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
View file

@ -21,7 +21,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
[[package]] [[package]]
name = "accesskit" name = "accesskit"
version = "0.22.0" 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 = [ dependencies = [
"uuid", "uuid",
] ]
@ -29,7 +29,7 @@ dependencies = [
[[package]] [[package]]
name = "accesskit_consumer" name = "accesskit_consumer"
version = "0.32.0" 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 = [ dependencies = [
"accesskit", "accesskit",
"hashbrown 0.16.1", "hashbrown 0.16.1",
@ -38,7 +38,7 @@ dependencies = [
[[package]] [[package]]
name = "accesskit_macos" name = "accesskit_macos"
version = "0.23.0" 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 = [ dependencies = [
"accesskit", "accesskit",
"accesskit_consumer", "accesskit_consumer",
@ -51,7 +51,7 @@ dependencies = [
[[package]] [[package]]
name = "accesskit_windows" name = "accesskit_windows"
version = "0.30.0" 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 = [ dependencies = [
"accesskit", "accesskit",
"accesskit_consumer", "accesskit_consumer",
@ -64,7 +64,7 @@ dependencies = [
[[package]] [[package]]
name = "accesskit_winit" name = "accesskit_winit"
version = "0.30.0" 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 = [ dependencies = [
"accesskit", "accesskit",
"accesskit_macos", "accesskit_macos",
@ -1012,7 +1012,7 @@ dependencies = [
[[package]] [[package]]
name = "cosmic-randr-shell" name = "cosmic-randr-shell"
version = "1.10.0" version = "1.3.0"
dependencies = [ dependencies = [
"kdl", "kdl",
"slotmap", "slotmap",
@ -1021,7 +1021,7 @@ dependencies = [
[[package]] [[package]]
name = "cosmic-settings-config" name = "cosmic-settings-config"
version = "1.10.0" version = "0.1.0"
dependencies = [ dependencies = [
"cosmic-config", "cosmic-config",
"ron 0.11.0", "ron 0.11.0",
@ -1040,7 +1040,7 @@ dependencies = [
[[package]] [[package]]
name = "cosmic-settings-daemon-config" name = "cosmic-settings-daemon-config"
version = "1.10.0" version = "0.1.0"
dependencies = [ dependencies = [
"cosmic-config", "cosmic-config",
"cosmic-theme", "cosmic-theme",
@ -1564,7 +1564,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.52.0", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@ -1963,8 +1963,8 @@ dependencies = [
"libc", "libc",
"log", "log",
"rustversion", "rustversion",
"windows-link 0.1.3", "windows-link 0.2.1",
"windows-result 0.3.4", "windows-result 0.4.1",
] ]
[[package]] [[package]]
@ -2329,7 +2329,7 @@ dependencies = [
"js-sys", "js-sys",
"log", "log",
"wasm-bindgen", "wasm-bindgen",
"windows-core 0.61.2", "windows-core 0.62.2",
] ]
[[package]] [[package]]
@ -2833,7 +2833,7 @@ dependencies = [
"portable-atomic", "portable-atomic",
"portable-atomic-util", "portable-atomic-util",
"serde_core", "serde_core",
"windows-sys 0.52.0", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@ -3072,7 +3072,6 @@ dependencies = [
"log", "log",
"palette", "palette",
"phf 0.13.1", "phf 0.13.1",
"roxmltree 0.20.0",
"rust-embed", "rust-embed",
"serde", "serde",
"slotmap", "slotmap",
@ -3136,9 +3135,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]] [[package]]
name = "libredox" name = "libredox"
version = "0.1.25" version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" checksum = "8d8f1ea3f21fd3405dcaf6c9b5c1630af9afc422d9073ea39c5f6d6c772e08ed"
dependencies = [ dependencies = [
"bitflags 2.13.2", "bitflags 2.13.2",
"libc", "libc",
@ -4811,7 +4810,7 @@ dependencies = [
"errno", "errno",
"libc", "libc",
"linux-raw-sys 0.4.15", "linux-raw-sys 0.4.15",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@ -4824,7 +4823,7 @@ dependencies = [
"errno", "errno",
"libc", "libc",
"linux-raw-sys 0.12.1", "linux-raw-sys 0.12.1",
"windows-sys 0.52.0", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@ -5433,7 +5432,7 @@ dependencies = [
"getrandom 0.4.1", "getrandom 0.4.1",
"once_cell", "once_cell",
"rustix 1.1.4", "rustix 1.1.4",
"windows-sys 0.52.0", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@ -6547,7 +6546,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.61.2",
] ]
[[package]] [[package]]

View file

@ -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 = { version = "0.3.2", optional = true } tracing-journald = "0.3.2"
tracing-subscriber = { version = "0.3.22", features = [ tracing-subscriber = { version = "0.3.22", features = [
"env-filter", "env-filter",
"tracing-log", "tracing-log",
@ -125,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", "tracing-journald"] systemd = ["libsystemd", "logind"]
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"]

67
Makefile Normal file
View 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"

View file

@ -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

View file

@ -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

View file

@ -3,7 +3,6 @@
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;
@ -107,8 +106,8 @@ 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,
/// When the cursor hides itself: idle, fullscreen idle, typing, touch /// Hide the cursor after this many seconds of pointer inactivity (None disables)
pub cursor_hide: CursorHideConfig, pub cursor_hide_timeout: Option<u32>,
/// Briefly magnify the cursor when the pointer is shaken, to help locate it /// Briefly magnify the cursor when the pointer is shaken, to help locate it
pub cursor_shake_to_find: bool, pub cursor_shake_to_find: bool,
pub activation_policy: ActivationPolicy, pub activation_policy: ActivationPolicy,
@ -149,7 +148,7 @@ 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: CursorHideConfig::default(), cursor_hide_timeout: None,
cursor_shake_to_find: true, cursor_shake_to_find: true,
activation_policy: ActivationPolicy::default(), activation_policy: ActivationPolicy::default(),
decoration_preference: DecorationPreference::default(), decoration_preference: DecorationPreference::default(),
@ -199,81 +198,6 @@ 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,
@ -339,109 +263,3 @@ 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
View file

@ -6,7 +6,6 @@ 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
View file

@ -1,6 +1,6 @@
#!/usr/bin/make -f #!/usr/bin/make -f
DESTDIR = debian/cosmic-comp export sysconfdir = "/usr/share"
export VENDOR ?= 1 export VENDOR ?= 1
CLEAN ?= 1 CLEAN ?= 1
@ -9,18 +9,12 @@ CLEAN ?= 1
override_dh_auto_clean: override_dh_auto_clean:
ifeq ($(CLEAN),1) ifeq ($(CLEAN),1)
ischroot && just clean || just clean-dist ischroot && make clean || make distclean
endif endif
ifeq ($(VENDOR),1) ifeq ($(VENDOR),1)
ischroot || just vendor ischroot || make 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

View file

@ -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}}

View file

@ -615,13 +615,7 @@ 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()

View file

@ -5,11 +5,9 @@ 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 keyframe::{ease, functions::EaseInOutCubic};
use resvg::{tiny_skia, usvg}; use resvg::{tiny_skia, usvg};
use serde::Deserialize; 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 type CursorState = Mutex<CursorStateInner>;
pub struct CursorStateInner { pub struct CursorStateInner {
current_cursor: Option<CursorIcon>, current_cursor: Option<CursorIcon>,
@ -490,10 +462,9 @@ pub struct CursorStateInner {
current_image: Option<Image>, current_image: Option<Image>,
image_cache: Vec<CachedFrame>, image_cache: Vec<CachedFrame>,
hidden: Option<HideReason>, hidden: bool,
idle_timer: Option<RegistrationToken>, idle_timer: Option<RegistrationToken>,
last_armed: Option<Instant>, last_armed: Option<Instant>,
last_pointer_activity: Instant,
// shake-to-find // shake-to-find
shake_path: VecDeque<PathSample>, shake_path: VecDeque<PathSample>,
@ -723,8 +694,7 @@ impl Default for CursorStateInner {
current_image: None, current_image: None,
image_cache: Vec::new(), image_cache: Vec::new(),
hidden: None, hidden: false,
last_pointer_activity: Instant::now(),
idle_timer: None, idle_timer: None,
last_armed: 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 mut state_ref = seat_userdata.get::<CursorState>().unwrap().lock().unwrap();
let state = &mut *state_ref; let state = &mut *state_ref;
if state.hidden.is_some() { if state.hidden {
return; return;
} }
@ -867,175 +837,68 @@ pub fn draw_cursor<R>(
const ACTIVITY_THROTTLE: Duration = Duration::from_millis(100); const ACTIVITY_THROTTLE: Duration = Duration::from_millis(100);
/// Reveal the cursor if this event should, and (re)arm the idle-hide timer. /// Reveal the cursor and (re)arm the idle-hide timer; returns true if it was previously hidden
/// Returns true if the cursor was previously hidden and is now visible. pub fn notify_cursor_activity(state: &State, seat: &Seat<State>) -> bool {
pub fn notify_cursor_activity(state: &State, seat: &Seat<State>, kind: PointerEventKind) -> bool { let timeout = state.common.config.cosmic_conf.cursor_hide_timeout;
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 (old_token, since_activity) = { let (was_hidden, old_token) = {
let mut inner = cursor_state.lock().unwrap(); let mut inner = cursor_state.lock().unwrap();
if inner.hidden.is_some() { let was_hidden = inner.hidden;
return; inner.hidden = false;
}
// Coalesce bursts from high-frequency pointers. let throttled = timeout.is_some()
let throttled = inner.idle_timer.is_some() && !was_hidden
&& 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; 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.last_armed = None;
(inner.idle_timer.take(), since_activity) (was_hidden, old_token)
}; };
if let Some(token) = old_token { if let Some(token) = old_token {
loop_handle.remove(token); loop_handle.remove(token);
} }
let Some(delay) = config.arm_delay(since_activity) else { if let Some(secs) = timeout {
return; 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| {
let timer = Timer::from_duration(delay); hide_cursor(state, &seat);
let timer_seat = seat.clone(); TimeoutAction::Drop
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
} }
/// Whether the seat is focusing a fullscreen surface. A fullscreen window that fn hide_cursor(state: &mut State, seat: &Seat<State>) {
/// is not focused can be stacked behind others, so its presence on the output if let Some(ptr) = seat.get_pointer()
/// says nothing about what the user is looking at. && ptr.is_grabbed()
fn fullscreen_focused(seat: &Seat<State>) -> bool { {
matches!( return;
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();
// Every caller has already disposed of the timer source, so clear the inner.hidden = true;
// 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));
}
}
}

View file

@ -10,7 +10,7 @@ use crate::{
}, },
}; };
use anyhow::Context; 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::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};
@ -49,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, CursorHideConfig, DecorationPreference, ActivationPolicy, AppearanceConfig, CosmicCompConfig, DecorationPreference, KeyboardConfig,
KeyboardConfig, TileBehavior, XkbConfig, XwaylandDescaling, XwaylandEavesdropping, ZoomConfig, 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,
@ -172,7 +172,7 @@ pub enum ColorFilter {
} }
impl Config { 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 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
@ -182,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 mut cosmic_comp_config = let 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 {
@ -192,18 +192,6 @@ 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) {
@ -260,48 +248,40 @@ 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 mut system_actions = Default::default(); let system_actions = shortcuts::system_actions(&settings_context);
let mut shortcuts = Default::default(); let shortcuts = shortcuts::shortcuts(&settings_context);
// 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) = if let Err(err) = loop_handle.insert_source(source, |(config, keys), (), state| {
loop_handle.insert_source(source, |(config, keys), (), state| { for key in keys {
for key in keys { match key.as_str() {
match key.as_str() { // Reload the keyboard shortcuts config.
// Reload the keyboard shortcuts config. "custom" | "defaults" => {
"custom" | "defaults" => { state.common.config.shortcuts = shortcuts::shortcuts(&config);
state.common.config.shortcuts =
shortcuts::shortcuts(&config);
}
"system_actions" => {
state.common.config.system_actions =
shortcuts::system_actions(&config);
}
_ => (),
}
} }
})
{ "system_actions" => {
warn!( state.common.config.system_actions =
?err, shortcuts::system_actions(&config);
"Failed to watch com.system76.CosmicSettings.Shortcuts config" }
);
_ => (),
}
} }
}) {
warn!(
?err,
"Failed to watch com.system76.CosmicSettings.Shortcuts config"
);
} }
Err(err) => warn!( }
?err, Err(err) => warn!(
"failed to create config watch source for com.system76.CosmicSettings.Shortcuts" ?err,
), "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");
@ -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"); let new = get_config::<bool>(&config, "cursor_shake_to_find");
state.common.config.cosmic_conf.cursor_shake_to_find = new; state.common.config.cosmic_conf.cursor_shake_to_find = new;
} }
"cursor_hide" => { "cursor_hide_timeout" => {
let new = get_config::<CursorHideConfig>(&config, "cursor_hide"); let new = get_config::<Option<u32>>(&config, "cursor_hide_timeout");
if new != state.common.config.cosmic_conf.cursor_hide { if new != state.common.config.cosmic_conf.cursor_hide_timeout {
state.common.config.cosmic_conf.cursor_hide = new; state.common.config.cosmic_conf.cursor_hide_timeout = 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 |= crate::backend::render::cursor::notify_cursor_activity( needs_render |=
state, crate::backend::render::cursor::notify_cursor_activity(state, &seat);
&seat,
crate::backend::render::cursor::PointerEventKind::Motion,
);
} }
if needs_render { if needs_render {
let outputs: Vec<_> = let outputs: Vec<_> =

View file

@ -1,10 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use crate::{ use crate::{
backend::render::{ backend::render::{ElementFilter, cursor::notify_cursor_activity},
ElementFilter,
cursor::{PointerEventKind, notify_cursor_activity},
},
config::{ config::{
Action, Config, PrivateAction, Action, Config, PrivateAction,
key_bindings::{ key_bindings::{
@ -22,7 +19,7 @@ use crate::{
Stage, render_input_order, Stage, render_input_order,
target::{KeyboardFocusTarget, PointerFocusTarget}, target::{KeyboardFocusTarget, PointerFocusTarget},
}, },
grabs::{MenuGrab, MoveGrab, ReleaseMode, ResizeEdge, ResizeGrab}, grabs::{ReleaseMode, ResizeEdge},
layout::{ layout::{
floating::ResizeGrabMarker, floating::ResizeGrabMarker,
tiling::{NodeDesc, SwapWindowGrab, TilingLayout}, tiling::{NodeDesc, SwapWindowGrab, TilingLayout},
@ -52,7 +49,7 @@ use smithay::{
PointerAxisEvent, ProximityState, TabletToolButtonEvent, TabletToolEvent, PointerAxisEvent, ProximityState, TabletToolButtonEvent, TabletToolEvent,
TabletToolProximityEvent, TabletToolTipEvent, TabletToolTipState, TouchEvent, TabletToolProximityEvent, TabletToolTipEvent, TabletToolTipState, TouchEvent,
}, },
desktop::{WindowSurfaceType, utils::under_from_surface_tree}, desktop::{PopupKeyboardGrab, WindowSurfaceType, utils::under_from_surface_tree},
input::{ input::{
Seat, Seat,
keyboard::KeyboardHandle, keyboard::KeyboardHandle,
@ -223,16 +220,7 @@ impl State {
) where ) where
<B as InputBackend>::Device: 'static, <B as InputBackend>::Device: 'static,
{ {
// An input device going away is not user activity, e.g. a wireless keyboard crate::wayland::handlers::output_power::set_all_surfaces_dpms_on(self);
// 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 {
@ -339,26 +327,6 @@ 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);
}
} }
} }
@ -372,7 +340,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, PointerEventKind::Motion); notify_cursor_activity(self, &seat);
let current_output = seat.active_output(); let current_output = seat.active_output();
if self.common.config.cosmic_conf.cursor_shake_to_find if self.common.config.cosmic_conf.cursor_shake_to_find
@ -746,7 +714,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, PointerEventKind::Motion); notify_cursor_activity(self, &seat);
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
@ -841,7 +809,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, PointerEventKind::Other); notify_cursor_activity(self, &seat);
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| {
@ -1092,7 +1060,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, PointerEventKind::Other); notify_cursor_activity(self, &seat);
if self.source_modifiers(&backend_id, &seat).logo if self.source_modifiers(&backend_id, &seat).logo
&& self && self
@ -1494,18 +1462,11 @@ 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,
@ -1516,14 +1477,6 @@ impl State {
time: event.time(), 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, .. } => {
@ -1639,7 +1592,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, PointerEventKind::Motion); notify_cursor_activity(self, &seat);
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()
@ -1743,7 +1696,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, PointerEventKind::Motion); notify_cursor_activity(self, &seat);
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()
@ -1892,7 +1845,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, PointerEventKind::Other); notify_cursor_activity(self, &seat);
let serial = SERIAL_COUNTER.next_serial(); let serial = SERIAL_COUNTER.next_serial();
let output = seat.active_output(); let output = seat.active_output();
@ -1945,7 +1898,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, PointerEventKind::Other); notify_cursor_activity(self, &seat);
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,
@ -2143,21 +2096,6 @@ 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
@ -2353,15 +2291,17 @@ 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();
// Escape only cancels grabs the compositor started for its own interactions // We're only interested in filtering keyboard grabs if we initiated them.
// For keyboard: SwapWindowGrab // The easiest way to check that is to check the type of the grab.
// For pointer: MoveGrab, ResizeGrab, and MenuGrab let keyboard_grabbed = keyboard.with_grab(|_serial, grab| {
let keyboard_grabbed = grab.is::<SwapWindowGrab>() || grab.is::<PopupKeyboardGrab<State>>()
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);
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(); 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

View file

@ -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"#
); );
} }

View file

@ -4,10 +4,7 @@ use std::str::FromStr;
use anyhow::Result; use anyhow::Result;
#[cfg(feature = "systemd")] use tracing::{debug, info, warn};
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::*};
@ -32,7 +29,6 @@ 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)
@ -47,11 +43,6 @@ 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"));

View file

@ -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>> { 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(),

View file

@ -2,18 +2,12 @@ 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::{ wayland::handlers::{xdg_shell::PopupGrabData, xwayland_keyboard_grab::XWaylandGrabSeatData},
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::{
backend::input::InputTime, backend::input::InputTime,
desktop::{ desktop::{PopupUngrabStrategy, layer_map_for_output},
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},
@ -255,17 +249,8 @@ 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 {
let output = match &target { //should this be the active output or the focused output?
FocusTarget::Window(mapped) => self self.active_space_mut(&seat.focused_or_active_output())
.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()
}; };
@ -642,42 +627,19 @@ fn focus_target_is_valid(
output: &Output, output: &Output,
target: KeyboardFocusTarget, target: KeyboardFocusTarget,
) -> bool { ) -> 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 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) {
let is_exclusive_on_layer = |layer_surface: &LayerSurface| { return if let KeyboardFocusTarget::LayerSurface(layer_surface) = target {
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 {
return match target { false
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,
}; };
} }

View file

@ -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::{session_lock_layer::layer_show_on_lock, workspace::WorkspaceHandle}, wayland::protocols::workspace::WorkspaceHandle,
}; };
pub enum Stage<'a> { pub enum Stage<'a> {
@ -77,8 +77,6 @@ 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()
@ -89,92 +87,7 @@ 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 {
for (layer, popup, location) in layer_popups(output, Layer::Overlay, element_filter) { return callback(Stage::SessionLock(session_lock.surfaces.get(output)));
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

View file

@ -579,12 +579,10 @@ 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(keep_root) .skip(1)
.for_each(|element| { .for_each(|element| {
PointerTarget::leave( PointerTarget::leave(
&element.iced, &element.iced,
@ -1022,11 +1020,10 @@ impl TabletToolGrab<State> for MenuGrab {
); );
self.last_tablet_idx = Some(i); self.last_tablet_idx = Some(i);
} else { } else {
let keep_root = usize::from(elements.len() > 1);
elements elements
.iter_mut() .iter_mut()
.filter(|element| element.tablet_entered.is_some()) .filter(|element| element.tablet_entered.is_some())
.skip(keep_root) .skip(1)
.for_each(|element| { .for_each(|element| {
TabletToolTarget::proximity_out( TabletToolTarget::proximity_out(
&element.iced, &element.iced,

View file

@ -3271,34 +3271,12 @@ 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;
@ -3345,16 +3323,25 @@ impl TilingLayout {
) )
}) })
} }
Some((_, Data::Group { .. })) => { Some((
let (id, idx) = fork?; id,
let Data::Group { Data::Group {
orientation, orientation,
last_geometry, 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(( Some((
ResizeForkTarget { ResizeForkTarget {
node: id.clone(), node: id.clone(),

View file

@ -19,10 +19,7 @@ use crate::{
utils, utils,
wayland::{ wayland::{
handlers::data_device::{self, get_dnd_icon}, handlers::data_device::{self, get_dnd_icon},
protocols::{ protocols::workspace::{State as WState, WorkspaceCapabilities},
session_lock_layer::layer_show_on_lock,
workspace::{State as WState, WorkspaceCapabilities},
},
}, },
}; };
use cosmic_comp_config::{ 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( fn activate(
&mut self, &mut self,
idx: usize, idx: usize,
@ -626,7 +617,6 @@ 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>) {
@ -2056,27 +2046,12 @@ 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 {
if let Some((output, _)) = session_lock return 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()

View file

@ -526,16 +526,10 @@ 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),
// Sticky windows live in the `WorkspaceSet` but are shown on whichever workspace is current FocusTarget::Window(w) => mapped().any(|m| w == m),
FocusTarget::Window(w) => w.is_sticky() || mapped().any(|m| w == m),
}); });
} }
} }
@ -1773,17 +1767,13 @@ impl Workspace {
self.floating_layer.render( self.floating_layer.render(
renderer, renderer,
render_focus focused.as_ref().and_then(|target| {
.then(|| { if let FocusTarget::Window(mapped) = target {
focused.as_ref().and_then(|target| { Some(mapped)
if let FocusTarget::Window(mapped) = target { } else {
Some(mapped) None
} else { }
None }),
}
})
})
.flatten(),
resize_indicator.clone(), resize_indicator.clone(),
indicator_thickness, indicator_thickness,
alpha, alpha,

View file

@ -23,7 +23,6 @@ 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},
@ -321,7 +320,6 @@ 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>,
@ -667,7 +665,7 @@ impl State {
.unwrap(); .unwrap();
let clock = Clock::new(); 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 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);
@ -765,9 +763,6 @@ 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,
@ -835,7 +830,6 @@ 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,
@ -974,8 +968,6 @@ 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| {
@ -1082,8 +1074,6 @@ 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()
@ -1297,8 +1287,6 @@ 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

View file

@ -519,7 +519,6 @@ impl<P: Program + Send + 'static> TabletToolTarget<crate::state::State> for Iced
_tool_descriptor: &TabletToolDescriptor, _tool_descriptor: &TabletToolDescriptor,
) { ) {
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));
@ -729,7 +728,6 @@ impl<P: Program + Send + 'static> PointerTarget<crate::state::State> for IcedEle
_time: InputTime, _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));

View file

@ -1,8 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only // SPDX-License-Identifier: GPL-3.0-only
use std::{borrow::Borrow, sync::Mutex}; use std::{borrow::Borrow, collections::HashMap, sync::Mutex};
use indexmap::IndexMap;
use smithay::{ use smithay::{
backend::{ backend::{
@ -454,28 +452,23 @@ 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())
{ {
let mut formats = renderer constraints.dma = Some(DmabufConstraints {
.egl_context() node,
.dmabuf_render_formats() formats: renderer
.iter() .egl_context()
.fold( .dmabuf_render_formats()
IndexMap::<Fourcc, Vec<Modifier>>::new(), .iter()
|mut map, format| { .fold(
map.entry(format.code).or_default().push(format.modifier); HashMap::<Fourcc, Vec<Modifier>>::new(),
map |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)); .into_iter()
constraints.dma = Some(DmabufConstraints { node, formats }); .collect::<Vec<_>>(),
});
} }
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)
}

View file

@ -21,11 +21,6 @@ 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);
} }
} }

View file

@ -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() && *m != &element) .filter(|m| m.maximized_state.lock().unwrap().is_some())
.cloned() .cloned()
.collect::<Vec<_>>() .collect::<Vec<_>>()
.into_iter() .into_iter()

View file

@ -8,7 +8,6 @@ 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;

View file

@ -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;
});
}
}
}